From 270d10da982b3d276ab1446f8dd9f123d1a3fa73 Mon Sep 17 00:00:00 2001 From: Armand Tresova Date: Thu, 13 Aug 2026 17:07:35 -0400 Subject: [PATCH 01/36] Added .distignore - Moved code of conduct - Added plugin-check to wp-env - Updated readme --- .distignore | 34 +++++++++++++++++++ .../CODE_OF_CONDUCT.md | 0 .wp-env.json | 4 ++- readme.txt | 4 +-- 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 .distignore rename CODE_OF_CONDUCT.md => .github/CODE_OF_CONDUCT.md (100%) diff --git a/.distignore b/.distignore new file mode 100644 index 0000000..09c71ef --- /dev/null +++ b/.distignore @@ -0,0 +1,34 @@ +/.wordpress-org +/.git +/.github +/.cursor +/node_modules +/tests +/bin +/vendor/bin +/vendor/squizlabs +/vendor/phpunit +/vendor/phpstan +/vendor/dealerdirect +/vendor/wp-coding-standards +/vendor/phpcompatibility +/vendor/sirbrillig +/vendor/phpcsstandards + +.distignore +.gitignore +.editorconfig +.wp-env.json +.phpcs.xml +phpcs.xml +phpunit.xml +phpstan.neon +package.json +package-lock.json +bun.lock +CODE_OF_CONDUCT.md +CITATION.cff +notes +*.bak +.env +.env.example diff --git a/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to .github/CODE_OF_CONDUCT.md diff --git a/.wp-env.json b/.wp-env.json index 29a63b6..b2b9780 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -2,7 +2,9 @@ "core": null, "phpVersion": "7.4", "themes": ["WordPress/twentytwentyfive"], - "plugins": [], + "plugins": [ + "https://downloads.wordpress.org/plugin/plugin-check.zip" + ], "mappings": { "wp-content/plugins/cooked": "." }, diff --git a/readme.txt b/readme.txt index 359755a..9ab9944 100644 --- a/readme.txt +++ b/readme.txt @@ -1,8 +1,8 @@ === Cooked - Recipe Management === Contributors: xjsv, boxystudio Tags: recipe, recipes, food, cooking, nutrition -Requires at least: 5.0.0 -Tested up to: 7.0 +Requires at least: 6.8 +Tested up to: 7.1 Stable tag: 1.16.0 Requires PHP: 7.4 License: GPLv2 or later From 5ad79adf96f36c10ef4529d27c1eddda6991043f Mon Sep 17 00:00:00 2001 From: Armand Tresova Date: Fri, 14 Aug 2026 13:23:43 -0400 Subject: [PATCH 02/36] Plugin Check (PCP) - Fixes PCP Wave 1: i18n comments, domains, headers --- cooked.php | 2 +- includes/class.cooked-ajax.php | 1 + includes/class.cooked-csv-import.php | 1 + includes/class.cooked-migration.php | 3 ++- includes/class.cooked-post-types.php | 4 ++-- includes/class.cooked-recipe-meta.php | 1 + includes/class.cooked-seo.php | 1 + includes/class.cooked-settings.php | 1 + includes/class.cooked-shortcodes.php | 3 +++ includes/class.cooked-updates.php | 2 ++ includes/widgets/init.php | 4 ++++ templates/admin/welcome.php | 2 +- 12 files changed, 20 insertions(+), 5 deletions(-) diff --git a/cooked.php b/cooked.php index ea0c1d0..a5857c3 100644 --- a/cooked.php +++ b/cooked.php @@ -11,7 +11,7 @@ * License: GPLv2 or later * License URI: https://www.gnu.org/licenses/gpl-2.0.html * Requires at least: 6.8 - * Tested up to: 7.0 + * Tested up to: 7.1 * Requires PHP: 7.4 * Contributors: xjsv, boxystudio * Tags: recipe, recipes, food, cooking, nutrition diff --git a/includes/class.cooked-ajax.php b/includes/class.cooked-ajax.php index 2f01fe9..b2e6096 100644 --- a/includes/class.cooked-ajax.php +++ b/includes/class.cooked-ajax.php @@ -450,6 +450,7 @@ public function process_csv() { if ($results['success'] > 0) { wp_send_json_success([ 'message' => sprintf( + /* translators: %d: number of recipes imported */ __('Successfully imported %d recipe(s).', 'cooked'), $results['success'] ), diff --git a/includes/class.cooked-csv-import.php b/includes/class.cooked-csv-import.php index 31adb38..89ec973 100644 --- a/includes/class.cooked-csv-import.php +++ b/includes/class.cooked-csv-import.php @@ -120,6 +120,7 @@ public static function import_from_file( $file_path ) { } } catch ( Exception $e ) { $error_msg = $e->getMessage(); + /* translators: 1: CSV row number, 2: error message */ $results['errors'][] = sprintf( __( 'Row %1$d: %2$s', 'cooked' ), $row_number, $error_msg ); if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { diff --git a/includes/class.cooked-migration.php b/includes/class.cooked-migration.php index 3b49039..4bb0936 100644 --- a/includes/class.cooked-migration.php +++ b/includes/class.cooked-migration.php @@ -88,7 +88,7 @@ public static function old_recipes_message() { $class = 'notice notice-error'; /* translators: for displaying singular or plural versions depending on the number of recipes. */ $message = sprintf( esc_html( _n( 'There is %1$s recipe that is from an older version of Cooked. Please %2$s to migrate this recipe.', 'There are %1$s recipes that are from an older version of Cooked. Please %2$s to migrate these recipes.', $total, 'cooked' ) ), '' . number_format( $total ) . '', '' . __( 'click here', 'cooked' ) . '' ); - printf('

%2$s

', esc_attr($class), $message); + printf('

%2$s

', esc_attr($class), wp_kses_post($message)); } } } @@ -105,6 +105,7 @@ public static function get_cooked_classic_recipes() { 'posts_per_page' => -1, 'post_status' => 'any', 'fields' => 'ids', + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => [ 'settings_clause' => [ 'key' => '_recipe_settings', diff --git a/includes/class.cooked-post-types.php b/includes/class.cooked-post-types.php index 7a98ea5..bc10ff8 100644 --- a/includes/class.cooked-post-types.php +++ b/includes/class.cooked-post-types.php @@ -446,8 +446,8 @@ public static function get() { $post_types = apply_filters( 'cooked_post_types', [ 'cp_recipe' => [ 'labels' => [ - 'name' => _x('Recipes', 'cooked'), - 'singular_name' => _x('Recipe', 'cooked'), + 'name' => _x( 'Recipes', 'post type general name', 'cooked' ), + 'singular_name' => _x( 'Recipe', 'post type singular name', 'cooked' ), 'menu_name' => __('Recipes', 'cooked'), 'name_admin_bar' => __('Recipe', 'cooked'), 'add_new' => __('Add New', 'cooked'), diff --git a/includes/class.cooked-recipe-meta.php b/includes/class.cooked-recipe-meta.php index d1fd51c..3a3bea4 100644 --- a/includes/class.cooked-recipe-meta.php +++ b/includes/class.cooked-recipe-meta.php @@ -214,6 +214,7 @@ public function recipe_embed_shortcode_admin_notice() { printf( '

%s

', sprintf( + /* translators: %s: recipe embed shortcode */ __( 'This recipe is set up to include itself in the Recipe Template (containing shortcode %s), which can break the page. Remove the embed that references this same recipe.', 'cooked' ), $shortcode ) diff --git a/includes/class.cooked-seo.php b/includes/class.cooked-seo.php index 5c8d4f3..4961cc9 100644 --- a/includes/class.cooked-seo.php +++ b/includes/class.cooked-seo.php @@ -80,6 +80,7 @@ public static function schema_values( $recipe = false ) { $directions[] = [ '@type' => 'HowToStep', + /* translators: %d: step number */ 'name' => sprintf(__('Step %d', 'cooked'), $number), 'text' => $direction_cleaned, 'url' => get_permalink($rpost) . '#cooked-single-direction-step-' . $number, diff --git a/includes/class.cooked-settings.php b/includes/class.cooked-settings.php index 9d1250d..1d50d02 100644 --- a/includes/class.cooked-settings.php +++ b/includes/class.cooked-settings.php @@ -127,6 +127,7 @@ function browse_page_missing_notice() { $class = 'notice notice-warning is-dismissible'; $message = sprintf( '' . __( 'Cooked Plugin Setup', 'cooked' ) . ' ' . + /* translators: %s: Browse/Search Recipes Page link */ __( 'To display your recipes properly, please set up your %s.', 'cooked' ), '' . __( 'Browse/Search Recipes Page', 'cooked' ) . '' ); diff --git a/includes/class.cooked-shortcodes.php b/includes/class.cooked-shortcodes.php index e55aba0..53d29a5 100644 --- a/includes/class.cooked-shortcodes.php +++ b/includes/class.cooked-shortcodes.php @@ -356,6 +356,7 @@ public function cooked_recipe_shortcode( $atts, $content = null ) { private static function recipe_embed_blocked_message( $recipe_id ) { $shortcode = '[cooked-recipe id="' . intval( $recipe_id ) . '"]'; $message = '' . sprintf( + /* translators: %s: recipe embed shortcode */ __( 'This recipe could not be displayed because it is set up to include itself (containing shortcode %s). Remove the duplicate recipe embed from the Recipe Template.', 'cooked' ), $shortcode ) . ''; @@ -1193,8 +1194,10 @@ public function cooked_related_recipes_shortcode($atts, $content = null) { // Check if it's a different post type $post_check = get_post($recipe_id); if ($post_check && $post_check->post_type !== 'cp_recipe') { + /* translators: %d: post ID */ $error_msg .= ' ' . sprintf(__('The specified ID (%d) is not a recipe.', 'cooked'), $recipe_id); } elseif (!$post_check) { + /* translators: %d: post ID */ $error_msg .= ' ' . sprintf(__('No post found with ID %d.', 'cooked'), $recipe_id); } return ''; diff --git a/includes/class.cooked-updates.php b/includes/class.cooked-updates.php index 5bd9ae4..1038d64 100644 --- a/includes/class.cooked-updates.php +++ b/includes/class.cooked-updates.php @@ -204,9 +204,11 @@ public static function run_tool( $tool_name ) { } } if ( ! isset( $allowed[ $tool_name ] ) ) { + /* translators: %s: site health tool name */ return new \WP_Error( 'cooked_tool_invalid', sprintf( __( 'Unknown tool: %s.', 'cooked' ), $tool_name ) ); } if ( ! method_exists( __CLASS__, $tool_name ) ) { + /* translators: %s: PHP method name */ return new \WP_Error( 'cooked_tool_missing', sprintf( __( 'Tool method %s does not exist.', 'cooked' ), $tool_name ) ); } diff --git a/includes/widgets/init.php b/includes/widgets/init.php index 90cec5c..1e6ec21 100644 --- a/includes/widgets/init.php +++ b/includes/widgets/init.php @@ -1,4 +1,8 @@ ' . __( 'Settings', 'cooked' ) . '', From 53cca7250956be56fb821eaae4d2e0fab90f366b Mon Sep 17 00:00:00 2001 From: Armand Tresova Date: Fri, 14 Aug 2026 13:24:02 -0400 Subject: [PATCH 03/36] License Box Style Updates --- assets/admin/css/style.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/assets/admin/css/style.css b/assets/admin/css/style.css index a9bab1f..d8fe714 100644 --- a/assets/admin/css/style.css +++ b/assets/admin/css/style.css @@ -159,7 +159,10 @@ body.post-type-cp_recipe .mce-fullscreen { #cooked_recipe_settings .recipe-setting-block .cooked-html-block h3.cooked-settings-title { width:100%; color:#333; } #cooked_recipe_settings .recipe-setting-block .cooked-html-block.valid { background:#fff; border:2px solid #0aa780; box-shadow:none; border-radius:5px; } #cooked_recipe_settings .recipe-setting-block .cooked-html-block.valid > .cooked-settings-title { color:#0aa780; } -#cooked_recipe_settings .recipe-setting-block .cooked-html-block.expired { border:2px solid #ca4a20; } +#cooked_recipe_settings .recipe-setting-block .cooked-html-block.expired, +#cooked_recipe_settings .recipe-setting-block .cooked-html-block.invalid { border:2px solid #ca4a20; } +#cooked_recipe_settings .recipe-setting-block .cooked-html-block.expired > .cooked-settings-title, +#cooked_recipe_settings .recipe-setting-block .cooked-html-block.invalid > .cooked-settings-title { color:#ca4a20; } /* Heading Element Styles */ #cooked-ingredients-builder .cooked-ingredient-block:hover > .cooked-heading-name, From 0c2578ff0dbe9e1010aa9b3d39d04ce6182235f5 Mon Sep 17 00:00:00 2001 From: Armand Tresova Date: Fri, 14 Aug 2026 14:18:07 -0400 Subject: [PATCH 04/36] Plugin Check (PCP) - Fixes PCP Wave 2: mechanical substitutions --- includes/class.cooked-enqueues.php | 10 +- includes/class.cooked-pixwell.php | 6 +- includes/class.cooked-recipe-meta.php | 306 +++++++++++++------------- templates/admin/import.php | 2 +- templates/admin/pro.php | 32 +-- templates/admin/settings.php | 2 +- templates/admin/welcome.php | 10 +- 7 files changed, 184 insertions(+), 184 deletions(-) diff --git a/includes/class.cooked-enqueues.php b/includes/class.cooked-enqueues.php index 8cb4671..142686d 100644 --- a/includes/class.cooked-enqueues.php +++ b/includes/class.cooked-enqueues.php @@ -62,17 +62,17 @@ public function enqueues($hook) { wp_enqueue_style('cooked-icons', COOKED_URL . 'assets/css/icons' . $min . '.css', [], COOKED_VERSION); wp_enqueue_style('cooked-styling', COOKED_URL . 'assets/css/style' . $min . '.css', [], COOKED_VERSION ); wp_register_style('cooked-fotorama', COOKED_URL . 'assets/css/fotorama/fotorama.min.css', [], '4.6.4'); - wp_register_script('cooked-fotorama', COOKED_URL . 'assets/js/fotorama/fotorama' . $min . '.js', ['jquery'], '4.6.4'); - wp_register_script('cooked-timer', COOKED_URL . 'assets/js/timer/jquery.simple.timer' . $min . '.js', ['jquery'], '0.0.5'); - wp_register_script('cooked-nosleep', COOKED_URL . 'assets/js/nosleep/NoSleep' . $min . '.js', [], '0.12.0'); + wp_register_script('cooked-fotorama', COOKED_URL . 'assets/js/fotorama/fotorama' . $min . '.js', ['jquery'], '4.6.4', true); + wp_register_script('cooked-timer', COOKED_URL . 'assets/js/timer/jquery.simple.timer' . $min . '.js', ['jquery'], '0.0.5', true); + wp_register_script('cooked-nosleep', COOKED_URL . 'assets/js/nosleep/NoSleep' . $min . '.js', [], '0.12.0', true); // Compatibility with the Bridge Theme. if (!defined('QODE_ROOT')) { - wp_register_script('cooked-appear', COOKED_URL . 'assets/js/appear/jquery.appear' . $min . '.js', ['jquery'], '0.3.6'); + wp_register_script('cooked-appear', COOKED_URL . 'assets/js/appear/jquery.appear' . $min . '.js', ['jquery'], '0.3.6', true); } wp_enqueue_script('wp-sanitize'); - wp_register_script('cooked-functions', COOKED_URL . 'assets/js/cooked-functions' . $min . '.js', ['jquery', 'wp-sanitize'], COOKED_VERSION); + wp_register_script('cooked-functions', COOKED_URL . 'assets/js/cooked-functions' . $min . '.js', ['jquery', 'wp-sanitize'], COOKED_VERSION, true); wp_localize_script('cooked-functions', 'cooked_functions_i18n_js_vars', $cooked_i18n_js_vars); wp_add_inline_script( 'cooked-functions', 'const cooked_functions_js_vars = ' . json_encode( $cooked_js_vars ) . ';', 'before' ); } diff --git a/includes/class.cooked-pixwell.php b/includes/class.cooked-pixwell.php index fa4f775..5e97f53 100644 --- a/includes/class.cooked-pixwell.php +++ b/includes/class.cooked-pixwell.php @@ -55,9 +55,9 @@ public function forced_dark_mode_notice() { ?>

- - - Auto.', 'cooked' ); ?> + + + Auto.', 'cooked' ), [ 'strong' => [] ] ); ?>

×
-

+

@@ -286,8 +286,8 @@ function cooked_recipe_shortcodes_content() {
-

-

+

+

@@ -299,8 +299,8 @@ function cooked_recipe_shortcodes_content() {
-

-

+

+

@@ -308,14 +308,14 @@ function cooked_recipe_shortcodes_content() {

"style"

-

+

"width"

-

+

@@ -325,14 +325,14 @@ function cooked_recipe_shortcodes_content() {

"hide_excerpt"

-

+

"hide_author"

-

+

@@ -342,14 +342,14 @@ function cooked_recipe_shortcodes_content() {

"hide_image"

-

+

"hide_title"

-

+

@@ -359,17 +359,17 @@ function cooked_recipe_shortcodes_content() {
-

+

- id ()
- width ()
- style ()
- hide_image ()
- hide_title ()
- hide_excerpt ()
- hide_author () + id ()
+ width ()
+ style ()
+ hide_image ()
+ hide_title ()
+ hide_excerpt ()
+ hide_author ()

-

+

[cooked-recipe-card id="" width="300px" style="modern"]

@@ -494,7 +494,7 @@ function cooked_render_recipe_fields( $post_id ) {
-

+

-

-

+

+

@@ -520,7 +520,7 @@ function cooked_render_recipe_fields( $post_id ) {
-

' . __( 'Default Recipe Template','cooked') . '' . __( 'Choose from the options below to use this layout as the default for new recipes or for all recipes.', 'cooked') . '' . __( 'Save as Default','cooked' ) . '  ' . __( 'Apply to All','cooked' ) . '0 / 0' ); ?>" class="button cooked-layout-save-default">' . __( 'Recipe Template','cooked') . '' . __( 'Using the built-in recipe shortcodes found on the "Shortcodes" tab, you can create the layout of your recipe below. Use the "Save as Default" button to save your template.','cooked') ); ?>">

+

' . __( 'Default Recipe Template','cooked') . '' . __( 'Choose from the options below to use this layout as the default for new recipes or for all recipes.', 'cooked') . '' . __( 'Save as Default','cooked' ) . '  ' . __( 'Apply to All','cooked' ) . '0 / 0' ); ?>" class="button cooked-layout-save-default">' . __( 'Recipe Template','cooked') . '' . __( 'Using the built-in recipe shortcodes found on the "Shortcodes" tab, you can create the layout of your recipe below. Use the "Save as Default" button to save your template.','cooked') ); ?>">

@@ -538,7 +538,7 @@ function cooked_render_recipe_fields( $post_id ) {
-

+

@@ -561,7 +561,7 @@ function cooked_render_recipe_fields( $post_id ) {

-

+

@@ -572,7 +572,7 @@ function cooked_render_recipe_fields( $post_id ) {
-

+

-

+

- +
-

+

- +
-

+

- +
-

+

- - - + + +
@@ -664,7 +664,7 @@ function cooked_render_recipe_fields( $post_id ) {
- +
@@ -673,7 +673,7 @@ function cooked_render_recipe_fields( $post_id ) {
- +
@@ -686,7 +686,7 @@ function cooked_render_recipe_fields( $post_id ) {
- +
@@ -697,11 +697,11 @@ function cooked_render_recipe_fields( $post_id ) {
- +
- + +
@@ -765,7 +765,7 @@ function cooked_render_recipe_fields( $post_id ) {
- +
@@ -778,7 +778,7 @@ function cooked_render_recipe_fields( $post_id ) {
- +
@@ -792,10 +792,10 @@ function cooked_render_recipe_fields( $post_id ) {

- -   + +   -   +  

@@ -823,7 +823,7 @@ function cooked_render_recipe_fields( $post_id ) {
- +
@@ -832,7 +832,7 @@ function cooked_render_recipe_fields( $post_id ) {
- +
@@ -845,7 +845,7 @@ function cooked_render_recipe_fields( $post_id ) {
- +
@@ -853,11 +853,11 @@ function cooked_render_recipe_fields( $post_id ) {
- +
- + +
- + - +
@@ -1000,7 +1000,7 @@ function cooked_render_recipe_fields( $post_id ) {
- +
@@ -1016,10 +1016,10 @@ function cooked_render_recipe_fields( $post_id ) {

- -   + +   -   +  

@@ -1028,7 +1028,7 @@ function cooked_render_recipe_fields( $post_id ) {
- +
@@ -1038,7 +1038,7 @@ function cooked_render_recipe_fields( $post_id ) {
- +
@@ -1048,11 +1048,11 @@ function cooked_render_recipe_fields( $post_id ) {
- +
- + $gtype ): echo ''; @@ -1229,7 +1229,7 @@ function cooked_render_recipe_fields( $post_id ) {

-

+

- +
@@ -1283,8 +1283,8 @@ function cooked_render_recipe_fields( $post_id ) {
-

-

+

+

@@ -1295,7 +1295,7 @@ function cooked_render_recipe_fields( $post_id ) { /* translators: "include and exclude" section title */ echo sprintf( __( '"%1$s" and "%2$s"', 'cooked' ), 'include', 'exclude' ); ?>

-

+

@@ -1304,7 +1304,7 @@ function cooked_render_recipe_fields( $post_id ) {

-

+

@@ -1314,7 +1314,7 @@ function cooked_render_recipe_fields( $post_id ) {
-

+

-

-

+

+

"checkboxes"

-

+

@@ -1360,10 +1360,10 @@ function cooked_render_recipe_fields( $post_id ) {
-

+

- true ()
- false () + true ()
+ false ()

@@ -1376,14 +1376,14 @@ function cooked_render_recipe_fields( $post_id ) {
-

-

+

+

"numbers"

-

+

@@ -1391,10 +1391,10 @@ function cooked_render_recipe_fields( $post_id ) {
-

+

- true ()
- false () + true ()
+ false ()

@@ -1407,8 +1407,8 @@ function cooked_render_recipe_fields( $post_id ) {
-

-

+

+

@@ -1416,9 +1416,9 @@ function cooked_render_recipe_fields( $post_id ) {
-

+

- +

@@ -1431,8 +1431,8 @@ function cooked_render_recipe_fields( $post_id ) {
-

-

+

+

@@ -1440,9 +1440,9 @@ function cooked_render_recipe_fields( $post_id ) {
-

+

- +

@@ -1457,8 +1457,8 @@ function cooked_render_recipe_fields( $post_id ) {
-

-

+

+

@@ -1466,14 +1466,14 @@ function cooked_render_recipe_fields( $post_id ) {

"width"

-

+

"ratio"

-

+

@@ -1483,14 +1483,14 @@ function cooked_render_recipe_fields( $post_id ) {

"nav"

-

+

"allowfullscreen"

-

+

@@ -1500,7 +1500,7 @@ function cooked_render_recipe_fields( $post_id ) {
-

+

width
-

-

+

+

@@ -1545,9 +1545,9 @@ function cooked_render_recipe_fields( $post_id ) {
-

+

- +

@@ -1560,14 +1560,14 @@ function cooked_render_recipe_fields( $post_id ) {
-

-

+

+

"show_header"

-

+

@@ -1575,10 +1575,10 @@ function cooked_render_recipe_fields( $post_id ) {
-

+

- true ()
- false () + true ()
+ false ()

@@ -1591,8 +1591,8 @@ function cooked_render_recipe_fields( $post_id ) {
-

-

+

+

' />
@@ -1603,14 +1603,14 @@ function cooked_render_recipe_fields( $post_id ) { /* translators: "seconds, minutes and hours" section title */ echo sprintf( __( '"%1$s", "%2$s" and "%3$s"','cooked' ), 'seconds','minutes','hours' ); ?>

-

+

"desc"

-

+

@@ -1620,12 +1620,12 @@ function cooked_render_recipe_fields( $post_id ) {
-

+

- seconds ()
- minutes ()
- hours ()
- desc () + seconds ()
+ minutes ()
+ hours ()
+ desc ()

@@ -1638,19 +1638,19 @@ function cooked_render_recipe_fields( $post_id ) {
-

-

+

+

-

+

-

+

- +

@@ -1663,8 +1663,8 @@ function cooked_render_recipe_fields( $post_id ) {
-

-

+

+

@@ -1672,14 +1672,14 @@ function cooked_render_recipe_fields( $post_id ) {

"id"

-

+

"title"

-

+

@@ -1689,14 +1689,14 @@ function cooked_render_recipe_fields( $post_id ) {

"limit"

-

+

"columns"

-

+

@@ -1706,14 +1706,14 @@ function cooked_render_recipe_fields( $post_id ) {

"hide_image"

-

+

"hide_excerpt"

-

+

@@ -1723,38 +1723,38 @@ function cooked_render_recipe_fields( $post_id ) {

"hide_author"

-

+

"match_*"

-

+

-

-

+

+

-

+

- id ()
- title ()
- limit ()
- columns ()
- hide_image ()
- hide_excerpt ()
- hide_author ()
- match_* () + id ()
+ title ()
+ limit ()
+ columns ()
+ hide_image ()
+ hide_excerpt ()
+ hide_author ()
+ match_* ()

-

+

[cooked-related-recipes limit="4" columns="2" title="Related Recipes"]

diff --git a/templates/admin/import.php b/templates/admin/import.php index acf87a2..b1e7508 100644 --- a/templates/admin/import.php +++ b/templates/admin/import.php @@ -3,7 +3,7 @@
-    +   
    -
  • -
  • -
  • -
  • -
  • +
  • +
  • +
  • +
  • +
    -
  • -
  • -
  • -
  • -
  • -
  • +
  • +
  • +
  • +
  • +
  • +
    -
  • -
  • -
  • -
  • -
  • +
  • +
  • +
  • +
  • +
diff --git a/templates/admin/settings.php b/templates/admin/settings.php index 8afe9be..bad6051 100644 --- a/templates/admin/settings.php +++ b/templates/admin/settings.php @@ -7,7 +7,7 @@
-    +   
-

+

    -
  •   
  • -
  •   
  • -
  •   
  • -
  •   
  • +
  •   
  • +
  •   
  • +
  •   
  • +
  •   
  •   
From 79624d913113d706594b5fd37bb17fa6d35c09a8 Mon Sep 17 00:00:00 2001 From: Armand Tresova Date: Fri, 14 Aug 2026 14:36:56 -0400 Subject: [PATCH 05/36] Plugin Check (PCP) - Fixes PCP Wave 3: i18n string literals only --- includes/class.cooked-measurements.php | 83 +++++++++++++------------- includes/class.cooked-recipe-meta.php | 10 ++-- includes/class.cooked-seo.php | 20 +++---- includes/class.cooked-shortcodes.php | 10 ++-- 4 files changed, 62 insertions(+), 61 deletions(-) diff --git a/includes/class.cooked-measurements.php b/includes/class.cooked-measurements.php index ce70d19..28f6a4c 100644 --- a/includes/class.cooked-measurements.php +++ b/includes/class.cooked-measurements.php @@ -211,6 +211,10 @@ public static function nutrition_facts() { // Use the "cooked_nutrition_facts" filter to add your own nutrition facts. // Reference: https://www.fda.gov/food/nutrition-facts-label/daily-value-nutrition-and-supplement-facts-labels + $unit_g = __( 'g', 'cooked' ); + $unit_mg = __( 'mg', 'cooked' ); + $unit_mcg = __( 'mcg', 'cooked' ); + $nutrition_facts = apply_filters('cooked_nutrition_facts', [ 'top' => [ 'servings' => [ @@ -234,67 +238,67 @@ public static function nutrition_facts() { 'fat' => [ 'name' => __('Total Fat', 'cooked'), 'type' => 'number', - 'measurement' => 'g', + 'measurement' => $unit_g, 'pdv' => apply_filters('cooked_pdv_fat', 78), 'subs' => [ 'sat_fat' => [ 'name' => __('Saturated Fat', 'cooked'), 'type' => 'number', - 'measurement' => 'g', + 'measurement' => $unit_g, 'pdv' => apply_filters('cooked_pdv_satfat', 20) ], 'trans_fat' => [ 'name' => __('Trans Fat', 'cooked'), 'nutrition_info_name' => __('Trans Fat', 'cooked'), 'type' => 'number', - 'measurement' => 'g' + 'measurement' => $unit_g ], 'monounsaturated_fat' => [ 'name' => __('Monounsaturated Fat', 'cooked'), 'type' => 'number', - 'measurement' => 'g' + 'measurement' => $unit_g ], 'polyunsaturated_fat' => [ 'name' => __('Polyunsaturated Fat', 'cooked'), 'type' => 'number', - 'measurement' => 'g' + 'measurement' => $unit_g ] ] ], 'cholesterol' => [ 'name' => __('Cholesterol', 'cooked'), 'type' => 'number', - 'measurement' => 'mg', + 'measurement' => $unit_mg, 'pdv' => apply_filters('cooked_pdv_cholesterol', 300) ], 'sodium' => [ 'name' => __('Sodium', 'cooked'), 'type' => 'number', - 'measurement' => 'mg', + 'measurement' => $unit_mg, 'pdv' => apply_filters('cooked_pdv_sodium', 2300) ], 'carbs' => [ 'name' => (isset($_cooked_settings['carb_format']) && $_cooked_settings['carb_format'] == 'total' ? __('Total Carbohydrate', 'cooked') : __('Net Carbohydrate', 'cooked')), 'type' => 'number', - 'measurement' => 'g', + 'measurement' => $unit_g, 'pdv' => apply_filters('cooked_pdv_carbs', 275), 'subs' => [ 'fiber' => [ 'name' => __('Dietary Fiber', 'cooked'), 'type' => 'number', - 'measurement' => 'g', + 'measurement' => $unit_g, 'pdv' => apply_filters('cooked_pdv_fiber', 28) ], 'sugars' => [ 'name' => __('Total Sugars', 'cooked'), 'type' => 'number', - 'measurement' => 'g', + 'measurement' => $unit_g, //'pdv' => apply_filters('cooked_pdv_sugars', 28) ], 'added_sugars' => [ 'name' => __('Added Sugars', 'cooked'), 'type' => 'number', - 'measurement' => 'g', + 'measurement' => $unit_g, 'pdv' => apply_filters('cooked_pdv_added_sugars', 50) ] ] @@ -302,7 +306,7 @@ public static function nutrition_facts() { 'protein' => [ 'name' => __('Protein', 'cooked'), 'type' => 'number', - 'measurement' => 'g', + 'measurement' => $unit_g, //'pdv' => apply_filters('cooked_pdv_protein', 50) ] ], @@ -311,165 +315,162 @@ public static function nutrition_facts() { 'vitamin_a' => [ 'name' => __('Vitamin A', 'cooked'), 'type' => 'number', - 'measurement' => 'mcg', + 'measurement' => $unit_mcg, 'pdv' => apply_filters('cooked_pdv_vitamin_a', 900) ], 'vitamin_c' => [ 'name' => __('Vitamin C', 'cooked'), 'type' => 'number', - 'measurement' => 'mg', + 'measurement' => $unit_mg, 'pdv' => apply_filters('cooked_pdv_vitamin_c', 90) ], 'calcium' => [ 'name' => __('Calcium', 'cooked'), 'type' => 'number', - 'measurement' => 'mg', + 'measurement' => $unit_mg, 'pdv' => apply_filters('cooked_pdv_calcium', 1300) ], 'iron' => [ 'name' => __('Iron', 'cooked'), 'type' => 'number', - 'measurement' => 'mg', + 'measurement' => $unit_mg, 'pdv' => apply_filters('cooked_pdv_iron', 18) ], 'potassium' => [ 'name' => __('Potassium', 'cooked'), 'type' => 'number', - 'measurement' => 'mg', + 'measurement' => $unit_mg, 'pdv' => apply_filters('cooked_pdv_potassium', 4700) ], 'vitamin_d' => [ 'name' => __('Vitamin D', 'cooked'), 'type' => 'number', - 'measurement' => 'mcg', + 'measurement' => $unit_mcg, 'pdv' => apply_filters('cooked_pdv_vitamin_d', 20) ], 'vitamin_e' => [ 'name' => __('Vitamin E', 'cooked'), 'type' => 'number', - 'measurement' => 'mg', + 'measurement' => $unit_mg, 'pdv' => apply_filters('cooked_pdv_vitamin_e', 15) ], 'vitamin_k' => [ 'name' => __('Vitamin K', 'cooked'), 'type' => 'number', - 'measurement' => 'mcg', + 'measurement' => $unit_mcg, 'pdv' => apply_filters('cooked_pdv_vitamin_k', 120) ], 'thiamin' => [ 'name' => __('Thiamin', 'cooked'), 'type' => 'number', - 'measurement' => 'mg', + 'measurement' => $unit_mg, 'pdv' => apply_filters('cooked_pdv_thiamin', 1.2) ], 'riboflavin' => [ 'name' => __('Riboflavin', 'cooked'), 'type' => 'number', - 'measurement' => 'mg', + 'measurement' => $unit_mg, 'pdv' => apply_filters('cooked_pdv_riboflavin', 1.3) ], 'niacin' => [ 'name' => __('Niacin', 'cooked'), 'type' => 'number', - 'measurement' => 'mg', + 'measurement' => $unit_mg, 'pdv' => apply_filters('cooked_pdv_niacin', 16) ], 'vitamin_b6' => [ 'name' => __('Vitamin B6', 'cooked'), 'type' => 'number', - 'measurement' => 'mg', + 'measurement' => $unit_mg, 'pdv' => apply_filters('cooked_pdv_vitamin_b6', 1.7) ], 'folate' => [ 'name' => __('Folate', 'cooked'), 'type' => 'number', - 'measurement' => 'mcg', + 'measurement' => $unit_mcg, 'pdv' => apply_filters('cooked_pdv_folate', 400) ], 'vitamin_b12' => [ 'name' => __('Vitamin B12', 'cooked'), 'type' => 'number', - 'measurement' => 'mg', + 'measurement' => $unit_mg, 'pdv' => apply_filters('cooked_pdv_vitamin_b12', 2.4) ], 'biotin' => [ 'name' => __('Biotin', 'cooked'), 'type' => 'number', - 'measurement' => 'mcg', + 'measurement' => $unit_mcg, 'pdv' => apply_filters('cooked_pdv_biotin', 30) ], 'pantothenic_acid' => [ 'name' => __('Pantothenic Acid', 'cooked'), 'type' => 'number', - 'measurement' => 'mg', + 'measurement' => $unit_mg, 'pdv' => apply_filters('cooked_pdv_pantothenic_acid', 5) ], 'phosphorus' => [ 'name' => __('Phosphorus', 'cooked'), 'type' => 'number', - 'measurement' => 'mg', + 'measurement' => $unit_mg, 'pdv' => apply_filters('cooked_pdv_phosphorus', 1250) ], 'iodine' => [ 'name' => __('Iodine', 'cooked'), 'type' => 'number', - 'measurement' => 'mcg', + 'measurement' => $unit_mcg, 'pdv' => apply_filters('cooked_pdv_iodine', 150) ], 'magnesium' => [ 'name' => __('Magnesium', 'cooked'), 'type' => 'number', - 'measurement' => 'mg', + 'measurement' => $unit_mg, 'pdv' => apply_filters('cooked_pdv_magnesium', 420) ], 'zinc' => [ 'name' => __('Zinc', 'cooked'), 'type' => 'number', - 'measurement' => 'mg', + 'measurement' => $unit_mg, 'pdv' => apply_filters('cooked_pdv_zinc', 11) ], 'selenium' => [ 'name' => __('Selenium', 'cooked'), 'type' => 'number', - 'measurement' => 'mcg', + 'measurement' => $unit_mcg, 'pdv' => apply_filters('cooked_pdv_selenium', 55) ], 'copper' => [ 'name' => __('Copper', 'cooked'), 'type' => 'number', - 'measurement' => 'mg', + 'measurement' => $unit_mg, 'pdv' => apply_filters('cooked_pdv_copper', 0.9) ], 'manganese' => [ 'name' => __('Manganese', 'cooked'), 'type' => 'number', - 'measurement' => 'mg', + 'measurement' => $unit_mg, 'pdv' => apply_filters('cooked_pdv_manganese', 2.3) ], 'chromium' => [ 'name' => __('Chromium', 'cooked'), 'type' => 'number', - 'measurement' => 'mcg', + 'measurement' => $unit_mcg, 'pdv' => apply_filters('cooked_pdv_chromium', 35) ], 'molybdenum' => [ 'name' => __('Molybdenum', 'cooked'), 'type' => 'number', - 'measurement' => 'mcg', + 'measurement' => $unit_mcg, 'pdv' => apply_filters('cooked_pdv_molybdenum', 45) ], 'chloride' => [ 'name' => __('Chloride', 'cooked'), 'type' => 'number', - 'measurement' => 'mg', + 'measurement' => $unit_mg, 'pdv' => apply_filters('cooked_pdv_chloride', 2300) ] ] ]); - // Ensures "mcg" is in the translation catalog (g and mg are cataloged via get()). - __( 'mcg', 'cooked' ); - return $nutrition_facts; } diff --git a/includes/class.cooked-recipe-meta.php b/includes/class.cooked-recipe-meta.php index 64826da..e6d4517 100644 --- a/includes/class.cooked-recipe-meta.php +++ b/includes/class.cooked-recipe-meta.php @@ -1156,7 +1156,7 @@ function cooked_render_recipe_fields( $post_id ) { foreach ( $nutrition_facts as $slug => $nf ): echo '
  • '; - echo '' . esc_html($nf['name']) . ' ___' . ( isset($nf['measurement']) ? '' . esc_html__( $nf['measurement'], 'cooked' ) . '' : '' ); + echo '' . esc_html($nf['name']) . ' ___' . ( isset($nf['measurement']) ? '' . esc_html( $nf['measurement'] ) . '' : '' ); echo ( isset( $nf['pdv'] ) ? '0%' : '' ); if ( isset($nf['subs']) ): @@ -1164,16 +1164,16 @@ function cooked_render_recipe_fields( $post_id ) { echo '
      '; if ($sub_slug === 'trans_fat'): echo '
    • '; - echo $sub_nf['nutrition_info_name'] . ' ___' . ( isset($sub_nf['measurement']) ? '' . esc_html__( $sub_nf['measurement'], 'cooked' ) . '' : '' ); + echo $sub_nf['nutrition_info_name'] . ' ___' . ( isset($sub_nf['measurement']) ? '' . esc_html( $sub_nf['measurement'] ) . '' : '' ); echo '
    • '; elseif ($sub_slug === 'added_sugars'): echo '
      • '; - echo __('Includes', 'cooked') . ' ___' . ( isset($sub_nf['measurement']) ? '' . esc_html__( $sub_nf['measurement'], 'cooked' ) . '' : '' ) . ' ' . esc_html($sub_nf['name']); + echo __('Includes', 'cooked') . ' ___' . ( isset($sub_nf['measurement']) ? '' . esc_html( $sub_nf['measurement'] ) . '' : '' ) . ' ' . esc_html($sub_nf['name']); echo ( isset( $sub_nf['pdv'] ) ? '0%' : '' ); echo '
      '; else: echo '
    • '; - echo $sub_nf['name'] . ' ___' . ( isset($sub_nf['measurement']) ? '' . esc_html__( $sub_nf['measurement'], 'cooked' ) . '' : '' ); + echo $sub_nf['name'] . ' ___' . ( isset($sub_nf['measurement']) ? '' . esc_html( $sub_nf['measurement'] ) . '' : '' ); echo ( isset( $sub_nf['pdv'] ) ? '0%' : '' ); echo '
    • '; endif; @@ -1190,7 +1190,7 @@ function cooked_render_recipe_fields( $post_id ) { $nf ): echo '
    • '; - echo $nf['name'] . ' ___' . ( isset($nf['measurement']) ? '' . esc_html__( $nf['measurement'], 'cooked' ) . '' : '' ); + echo $nf['name'] . ' ___' . ( isset($nf['measurement']) ? '' . esc_html( $nf['measurement'] ) . '' : '' ); echo ( isset( $nf['pdv'] ) ? '0%' : '' ); echo '
    • '; endforeach; ?> diff --git a/includes/class.cooked-seo.php b/includes/class.cooked-seo.php index 4961cc9..690a745 100644 --- a/includes/class.cooked-seo.php +++ b/includes/class.cooked-seo.php @@ -108,7 +108,7 @@ public static function schema_values( $recipe = false ) { $unsaturatedFatAmount = (isset($recipe['nutrition']['monounsaturated_fat']) && $recipe['nutrition']['monounsaturated_fat'] ? $recipe['nutrition']['monounsaturated_fat'] : 0) + (isset($recipe['nutrition']['polyunsaturated_fat']) && $recipe['nutrition']['polyunsaturated_fat'] ? $recipe['nutrition']['polyunsaturated_fat'] : 0); if ($unsaturatedFatAmount): - $unsaturatedFatContent = $unsaturatedFatAmount . ' ' . __( $_nutrition_facts['main']['fat']['subs']['monounsaturated_fat']['measurement'], 'cooked' ); + $unsaturatedFatContent = $unsaturatedFatAmount . ' ' . $_nutrition_facts['main']['fat']['subs']['monounsaturated_fat']['measurement']; else: $unsaturatedFatContent = ''; endif; @@ -148,16 +148,16 @@ public static function schema_values( $recipe = false ) { 'nutrition' => [ '@type' => 'NutritionInformation', 'calories' => (isset($recipe['nutrition']['calories']) && $recipe['nutrition']['calories'] ? $recipe['nutrition']['calories'] . ' ' . strtolower($_nutrition_facts['mid']['calories']['name']) : 0), - 'carbohydrateContent' => (isset($recipe['nutrition']['carbs']) && $recipe['nutrition']['carbs'] ? $recipe['nutrition']['carbs'] . ' ' . __( $_nutrition_facts['main']['carbs']['measurement'], 'cooked' ) : ''), - 'cholesterolContent' => (isset($recipe['nutrition']['cholesterol']) && $recipe['nutrition']['cholesterol'] ? $recipe['nutrition']['cholesterol'] . ' ' . __( $_nutrition_facts['main']['cholesterol']['measurement'], 'cooked' ) : ''), - 'fatContent' => (isset($recipe['nutrition']['fat']) && $recipe['nutrition']['fat'] ? $recipe['nutrition']['fat'] . ' ' . __( $_nutrition_facts['main']['fat']['measurement'], 'cooked' ) : ''), - 'fiberContent' => (isset($recipe['nutrition']['fiber']) && $recipe['nutrition']['fiber'] ? $recipe['nutrition']['fiber'] . ' ' . __( $_nutrition_facts['main']['carbs']['subs']['fiber']['measurement'], 'cooked' ) : ''), - 'proteinContent' => (isset($recipe['nutrition']['protein']) && $recipe['nutrition']['protein'] ? $recipe['nutrition']['protein'] . ' ' . __( $_nutrition_facts['main']['protein']['measurement'], 'cooked' ) : ''), - 'saturatedFatContent' => (isset($recipe['nutrition']['sat_fat']) && $recipe['nutrition']['sat_fat'] ? $recipe['nutrition']['sat_fat'] . ' ' . __( $_nutrition_facts['main']['fat']['subs']['sat_fat']['measurement'], 'cooked' ) : ''), + 'carbohydrateContent' => (isset($recipe['nutrition']['carbs']) && $recipe['nutrition']['carbs'] ? $recipe['nutrition']['carbs'] . ' ' . $_nutrition_facts['main']['carbs']['measurement'] : ''), + 'cholesterolContent' => (isset($recipe['nutrition']['cholesterol']) && $recipe['nutrition']['cholesterol'] ? $recipe['nutrition']['cholesterol'] . ' ' . $_nutrition_facts['main']['cholesterol']['measurement'] : ''), + 'fatContent' => (isset($recipe['nutrition']['fat']) && $recipe['nutrition']['fat'] ? $recipe['nutrition']['fat'] . ' ' . $_nutrition_facts['main']['fat']['measurement'] : ''), + 'fiberContent' => (isset($recipe['nutrition']['fiber']) && $recipe['nutrition']['fiber'] ? $recipe['nutrition']['fiber'] . ' ' . $_nutrition_facts['main']['carbs']['subs']['fiber']['measurement'] : ''), + 'proteinContent' => (isset($recipe['nutrition']['protein']) && $recipe['nutrition']['protein'] ? $recipe['nutrition']['protein'] . ' ' . $_nutrition_facts['main']['protein']['measurement'] : ''), + 'saturatedFatContent' => (isset($recipe['nutrition']['sat_fat']) && $recipe['nutrition']['sat_fat'] ? $recipe['nutrition']['sat_fat'] . ' ' . $_nutrition_facts['main']['fat']['subs']['sat_fat']['measurement'] : ''), 'servingSize' => (isset($recipe['nutrition']['serving_size']) && $recipe['nutrition']['serving_size'] ? $recipe['nutrition']['serving_size'] . ' ' . strtolower($_nutrition_facts['top']['servings']['name']) : ''), - 'sodiumContent' => (isset($recipe['nutrition']['sodium']) && $recipe['nutrition']['sodium'] ? $recipe['nutrition']['sodium'] . ' ' . __( $_nutrition_facts['main']['sodium']['measurement'], 'cooked' ) : ''), - 'sugarContent' => (isset($recipe['nutrition']['sugars']) && $recipe['nutrition']['sugars'] ? $recipe['nutrition']['sugars'] . ' ' . __( $_nutrition_facts['main']['carbs']['subs']['sugars']['measurement'], 'cooked' ) : ''), - 'transFatContent' => (isset($recipe['nutrition']['trans_fat']) && $recipe['nutrition']['trans_fat'] ? $recipe['nutrition']['trans_fat'] . ' ' . __( $_nutrition_facts['main']['fat']['subs']['trans_fat']['measurement'], 'cooked' ) : ''), + 'sodiumContent' => (isset($recipe['nutrition']['sodium']) && $recipe['nutrition']['sodium'] ? $recipe['nutrition']['sodium'] . ' ' . $_nutrition_facts['main']['sodium']['measurement'] : ''), + 'sugarContent' => (isset($recipe['nutrition']['sugars']) && $recipe['nutrition']['sugars'] ? $recipe['nutrition']['sugars'] . ' ' . $_nutrition_facts['main']['carbs']['subs']['sugars']['measurement'] : ''), + 'transFatContent' => (isset($recipe['nutrition']['trans_fat']) && $recipe['nutrition']['trans_fat'] ? $recipe['nutrition']['trans_fat'] . ' ' . $_nutrition_facts['main']['fat']['subs']['trans_fat']['measurement'] : ''), 'unsaturatedFatContent' => $unsaturatedFatContent, ], 'recipeInstructions' => $directions, diff --git a/includes/class.cooked-shortcodes.php b/includes/class.cooked-shortcodes.php index 53d29a5..c02c26d 100644 --- a/includes/class.cooked-shortcodes.php +++ b/includes/class.cooked-shortcodes.php @@ -1018,7 +1018,7 @@ public function cooked_nutrition_shortcode($atts, $content = null) { if ( isset( $nutrition_facts[$slug] ) && $nutrition_facts[$slug] || isset( $nutrition_facts[$slug] ) && $nutrition_facts[$slug] === '0' ): echo '
      '; - echo '' . $nf['name'] . ' ' . esc_html( $nutrition_facts[$slug] ) . '' . ( isset($nf['measurement']) ? '' . esc_html__( $nf['measurement'], 'cooked' ) . '' : '' ); + echo '' . $nf['name'] . ' ' . esc_html( $nutrition_facts[$slug] ) . '' . ( isset($nf['measurement']) ? '' . esc_html( $nf['measurement'] ) . '' : '' ); echo ( isset( $nf['pdv'] ) && $nutrition_facts[$slug] ? '' . ceil( ( esc_html( $nutrition_facts[$slug] ) / $nf['pdv'] ) * 100 ) . '%' : '' ); if ( isset($nf['subs']) ): @@ -1027,16 +1027,16 @@ public function cooked_nutrition_shortcode($atts, $content = null) { echo '
      '; if ($sub_slug === 'trans_fat'): echo '
      '; - echo $sub_nf['nutrition_info_name'] . ' ' . esc_html( $nutrition_facts[$sub_slug] ) . '' . ( isset($sub_nf['measurement']) ? '' . esc_html__( $sub_nf['measurement'], 'cooked' ) . '' : '' ); + echo $sub_nf['nutrition_info_name'] . ' ' . esc_html( $nutrition_facts[$sub_slug] ) . '' . ( isset($sub_nf['measurement']) ? '' . esc_html( $sub_nf['measurement'] ) . '' : '' ); echo '
      '; elseif ($sub_slug === 'added_sugars'): echo '
      '; - echo __('Includes', 'cooked') . ' ' . esc_html( $nutrition_facts[$sub_slug] ) . '' . ( isset($sub_nf['measurement']) ? '' . esc_html__( $sub_nf['measurement'], 'cooked' ) . '' : '' ) . ' ' . esc_html($sub_nf['name']); + echo __('Includes', 'cooked') . ' ' . esc_html( $nutrition_facts[$sub_slug] ) . '' . ( isset($sub_nf['measurement']) ? '' . esc_html( $sub_nf['measurement'] ) . '' : '' ) . ' ' . esc_html($sub_nf['name']); echo ( isset( $sub_nf['pdv'] ) ? '' . ceil( ( esc_html( $nutrition_facts[$sub_slug] ) / $sub_nf['pdv'] ) * 100 ) . '%' : '' ); echo '
      '; else: echo '
      '; - echo esc_html( $sub_nf['name'] ) . ' ' . esc_html( $nutrition_facts[$sub_slug] ) . '' . ( isset($sub_nf['measurement']) ? '' . esc_html__( $sub_nf['measurement'], 'cooked' ) . '' : '' ); + echo esc_html( $sub_nf['name'] ) . ' ' . esc_html( $nutrition_facts[$sub_slug] ) . '' . ( isset($sub_nf['measurement']) ? '' . esc_html( $sub_nf['measurement'] ) . '' : '' ); echo ( isset( $sub_nf['pdv'] ) && $nutrition_facts[$sub_slug] ? '' . ceil( ( esc_html( $nutrition_facts[$sub_slug] ) / $sub_nf['pdv'] ) * 100 ) . '%' : '' ); echo '
      '; endif; @@ -1066,7 +1066,7 @@ public function cooked_nutrition_shortcode($atts, $content = null) { foreach ( $bottom_facts as $slug => $nf ): if ( isset( $nutrition_facts[$slug] ) && $nutrition_facts[$slug] || isset( $nutrition_facts[$slug] ) && $nutrition_facts[$slug] === '0' ): echo '
      '; - echo '' . esc_html($nf['name']) . ' ' . esc_html( $nutrition_facts[$slug] ) . '' . ( isset($nf['measurement']) ? '' . esc_html__( $nf['measurement'], 'cooked' ) . '' : '' ); + echo '' . esc_html($nf['name']) . ' ' . esc_html( $nutrition_facts[$slug] ) . '' . ( isset($nf['measurement']) ? '' . esc_html( $nf['measurement'] ) . '' : '' ); echo ( isset( $nf['pdv'] ) ? '' . ceil( ( esc_html( $nutrition_facts[$slug] ) / $nf['pdv'] ) * 100 ) . '%' : '' ); echo '
      '; endif; From 2ab362729a09179f4e3bdcdd49d17be3e54a9e93 Mon Sep 17 00:00:00 2001 From: Armand Tresova Date: Fri, 14 Aug 2026 17:57:48 -0400 Subject: [PATCH 06/36] Tests Overhaul --- .gitignore | 7 +- bun.lock | 18 +- package.json | 9 +- tests/playwright/.env.example | 13 +- tests/playwright/bun.lock | 1158 --- tests/playwright/global-setup.ts | 11 + tests/playwright/package-lock.json | 7810 ----------------- tests/playwright/package.json | 21 - tests/playwright/playwright.config.ts | 97 +- .../1-create-recipe-front.spec.ts.bak | 269 - .../2-create-recipe-nutrition.spec.ts | 209 - .../3-related-recipes.spec.ts.bak | 711 -- .../1-create-recipe-front.spec.ts | 241 - .../2_contrib_user/2-rate-recipe.spec.ts | 235 - .../2_contrib_user/3-favorite-recipe.spec.ts | 217 - .../1-create-recipe.spec.ts copy.bak | 69 - .../3_anon_user/1-view-recipe.spec.ts.bak | 71 - .../3_anon_user/2-star-rate-recipe.spec.ts | 239 - .../3_anon_user/2-thumb-rate-recipe.spec.ts | 231 - .../3_anon_user/3-sort-browse-recipe.spec.ts | 102 - .../4-filter-browse-recipe.spec.ts | 84 - .../1-create-approve-recipe.spec.ts | 242 - .../tests/5_api/1-nutrition-api.spec.ts | 368 - .../3-profile-pretty-urls.spec.ts.bak | 51 - .../7_accessibility/5-add-recipe-form.spec.ts | 27 - .../7_accessibility/6-recipe-ratings.spec.ts | 51 - .../browse.spec.ts} | 0 .../filters.spec.ts} | 0 .../search.spec.ts} | 0 .../single.spec.ts} | 0 .../create-recipe.spec.ts} | 95 +- .../csv-import.spec.ts} | 40 +- .../tests/browse/filter-category.spec.ts | 22 + .../search.spec.ts} | 16 +- .../playwright/tests/browse/sort-date.spec.ts | 35 + .../home-page.spec.ts} | 1 - .../xss-prevention.spec.ts} | 283 +- tests/playwright/utils/a11y-report-store.ts | 2 +- tests/playwright/utils/fixtures.ts | 33 + tests/playwright/utils/users.ts | 25 + tests/playwright/utils/wp-cli.ts | 90 + 41 files changed, 401 insertions(+), 12802 deletions(-) delete mode 100644 tests/playwright/bun.lock create mode 100644 tests/playwright/global-setup.ts delete mode 100644 tests/playwright/package-lock.json delete mode 100644 tests/playwright/package.json delete mode 100644 tests/playwright/tests/1_admin_user/1-create-recipe-front.spec.ts.bak delete mode 100644 tests/playwright/tests/1_admin_user/2-create-recipe-nutrition.spec.ts delete mode 100644 tests/playwright/tests/1_admin_user/3-related-recipes.spec.ts.bak delete mode 100644 tests/playwright/tests/2_contrib_user/1-create-recipe-front.spec.ts delete mode 100644 tests/playwright/tests/2_contrib_user/2-rate-recipe.spec.ts delete mode 100644 tests/playwright/tests/2_contrib_user/3-favorite-recipe.spec.ts delete mode 100644 tests/playwright/tests/3_anon_user/1-create-recipe.spec.ts copy.bak delete mode 100644 tests/playwright/tests/3_anon_user/1-view-recipe.spec.ts.bak delete mode 100644 tests/playwright/tests/3_anon_user/2-star-rate-recipe.spec.ts delete mode 100644 tests/playwright/tests/3_anon_user/2-thumb-rate-recipe.spec.ts delete mode 100644 tests/playwright/tests/3_anon_user/3-sort-browse-recipe.spec.ts delete mode 100644 tests/playwright/tests/3_anon_user/4-filter-browse-recipe.spec.ts delete mode 100644 tests/playwright/tests/4_admin_user/1-create-approve-recipe.spec.ts delete mode 100644 tests/playwright/tests/5_api/1-nutrition-api.spec.ts delete mode 100644 tests/playwright/tests/5_settings/3-profile-pretty-urls.spec.ts.bak delete mode 100644 tests/playwright/tests/7_accessibility/5-add-recipe-form.spec.ts delete mode 100644 tests/playwright/tests/7_accessibility/6-recipe-ratings.spec.ts rename tests/playwright/tests/{7_accessibility/1-browse-recipes.spec.ts => accessibility/browse.spec.ts} (100%) rename tests/playwright/tests/{7_accessibility/2-browse-filters.spec.ts => accessibility/filters.spec.ts} (100%) rename tests/playwright/tests/{7_accessibility/4-recipe-search.spec.ts => accessibility/search.spec.ts} (100%) rename tests/playwright/tests/{7_accessibility/3-recipe-single.spec.ts => accessibility/single.spec.ts} (100%) rename tests/playwright/tests/{1_admin_user/1-create-recipe.spec.ts => admin/create-recipe.spec.ts} (58%) rename tests/playwright/tests/{1_admin_user/4-csv-import.spec.ts => admin/csv-import.spec.ts} (77%) create mode 100644 tests/playwright/tests/browse/filter-category.spec.ts rename tests/playwright/tests/{3_anon_user/5-search-browse-recipe.spec.ts => browse/search.spec.ts} (76%) create mode 100644 tests/playwright/tests/browse/sort-date.spec.ts rename tests/playwright/tests/{0_general/1-home-page.spec.ts => home/home-page.spec.ts} (91%) rename tests/playwright/tests/{6_security/1-xss-prevention.spec.ts => security/xss-prevention.spec.ts} (84%) create mode 100644 tests/playwright/utils/fixtures.ts create mode 100644 tests/playwright/utils/users.ts create mode 100644 tests/playwright/utils/wp-cli.ts diff --git a/.gitignore b/.gitignore index c7697f8..3f4516d 100644 --- a/.gitignore +++ b/.gitignore @@ -11,12 +11,17 @@ # Dev dependencies node_modules/ vendor +.env # Tests +tests/playwright/test-results/ +tests/playwright/playwright-report/ +tests/playwright/.cache/ tests/test-results/ tests/playwright-report/ tests/blob-report/ -tests/playwright/.cache/ +playwright-report/ +test-results/ tests/.auth/ tests/phpunit/.cache diff --git a/bun.lock b/bun.lock index 833faa0..8ba2d85 100644 --- a/bun.lock +++ b/bun.lock @@ -5,11 +5,13 @@ "": { "name": "cooked", "devDependencies": { + "@axe-core/playwright": "^4.13.0", "@playwright/test": "^1.62.1", "@types/node": "^26.2.0", - "@wordpress/e2e-test-utils-playwright": "^1.52.0", - "@wordpress/env": "^11.12.0", + "@wordpress/e2e-test-utils-playwright": "^1.53.0", + "@wordpress/env": "^11.13.0", "bestzip": "^3.0.2", + "dotenv": "^17.4.2", "gulp": "^5.0.1", "gulp-clean-css": "^4.3.0", "gulp-rename": "^2.1.0", @@ -21,6 +23,8 @@ }, }, "packages": { + "@axe-core/playwright": ["@axe-core/playwright@4.13.0", "", { "dependencies": { "axe-core": "~4.13.0" }, "peerDependencies": { "playwright-core": ">= 1.0.0" } }, "sha512-6YLx+kxXu5GJceG4ozFg+33a2EMTdjYwWGloJ3sb9Kta5pp+ZNS53uxGVog5JetIY8s++P5UrtX+cri+u0VAVg=="], + "@formatjs/ecma402-abstract": ["@formatjs/ecma402-abstract@2.3.6", "", { "dependencies": { "@formatjs/fast-memoize": "2.2.7", "@formatjs/intl-localematcher": "0.6.2", "decimal.js": "^10.4.3", "tslib": "^2.8.0" } }, "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw=="], "@formatjs/fast-memoize": ["@formatjs/fast-memoize@2.2.7", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ=="], @@ -313,9 +317,9 @@ "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], - "@wordpress/e2e-test-utils-playwright": ["@wordpress/e2e-test-utils-playwright@1.52.0", "", { "dependencies": { "change-case": "^4.1.2", "get-port": "^5.1.1", "lighthouse": "^12.2.2", "mime": "^3.0.0", "web-vitals": "^4.2.1" }, "peerDependencies": { "@playwright/test": ">=1", "@types/node": "^20.17.10" } }, "sha512-LK0/WJd8iGfTo9ONaHrLSBh0IjKHeXU7urZPaNuXXB7jdt+coLTF+y0gP4c6sQ5m9OgfQqf01FPT/t2XzEbJ+w=="], + "@wordpress/e2e-test-utils-playwright": ["@wordpress/e2e-test-utils-playwright@1.53.0", "", { "dependencies": { "change-case": "^4.1.2", "get-port": "^5.1.1", "lighthouse": "^12.2.2", "mime": "^3.0.0", "web-vitals": "^4.2.1" }, "peerDependencies": { "@playwright/test": ">=1", "@types/node": "^20.17.10" } }, "sha512-46wLx4MUEoXeWq+JtmFjFsQd0+m219bA1WUDW4vpMrNzO2qU87Rm+9LWudRQ5+niJvPiYyZruNAhscl5KRxbOQ=="], - "@wordpress/env": ["@wordpress/env@11.12.0", "", { "dependencies": { "@inquirer/prompts": "^7.2.0", "@wp-playground/cli": "^3.0.48", "adm-zip": "^0.5.9", "chalk": "^4.1.1", "copy-dir": "^1.3.0", "cross-spawn": "^7.0.6", "docker-compose": "^0.24.3", "got": "^11.8.5", "js-yaml": "^3.15.0", "ora": "^4.0.2", "rimraf": "^5.0.10", "simple-git": "^3.32.3", "yargs": "^17.3.0" }, "bin": { "wp-env": "bin/wp-env" } }, "sha512-P1mYGEgnkYCbBPRaT9uAyxe3hFfsfkvxXKQ+Pfb00z/mXz5xbPC48ZdXTQmxTcpaj0nDwJfrBz0vzc074B5nrA=="], + "@wordpress/env": ["@wordpress/env@11.13.0", "", { "dependencies": { "@inquirer/prompts": "^7.2.0", "@wp-playground/cli": "^3.0.48", "adm-zip": "^0.5.9", "chalk": "^4.1.1", "copy-dir": "^1.3.0", "cross-spawn": "^7.0.6", "docker-compose": "^0.24.3", "got": "^11.8.5", "js-yaml": "^3.15.0", "ora": "^4.0.2", "rimraf": "^5.0.10", "simple-git": "^3.32.3", "yargs": "^17.3.0" }, "bin": { "wp-env": "bin/wp-env" } }, "sha512-ZBhCf1N1Qgh5U//WXsK9d5rgXtjkWMBHuKrpC3KgoiqEVwalYw+H5jfbGqBzjZtHJeM1SbkSumxRTRhApQPqjg=="], "@wp-playground/blueprints": ["@wp-playground/blueprints@3.1.33", "", { "dependencies": { "@php-wasm/logger": "3.1.33", "@php-wasm/progress": "3.1.33", "@php-wasm/stream-compression": "3.1.33", "@php-wasm/universal": "3.1.33", "@php-wasm/util": "3.1.33", "@php-wasm/web-service-worker": "3.1.33", "@wp-playground/common": "3.1.33", "@wp-playground/storage": "3.1.33", "@wp-playground/wordpress": "3.1.33", "ajv": "8.12.0" } }, "sha512-bAs0MOBgXPDYtsO2tQcXSqveEaye8xcNGOQvovvE6c5pZWD4RlsiuZ71oKGmtq7JJhgdLTNj0lkcF9f2NX6vrg=="], @@ -387,7 +391,7 @@ "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], - "axe-core": ["axe-core@4.11.4", "", {}, "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA=="], + "axe-core": ["axe-core@4.13.0", "", {}, "sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A=="], "b4a": ["b4a@1.8.1", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw=="], @@ -555,6 +559,8 @@ "dot-prop": ["dot-prop@9.0.0", "", { "dependencies": { "type-fest": "^4.18.2" } }, "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ=="], + "dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], "each-props": ["each-props@3.0.0", "", { "dependencies": { "is-plain-object": "^5.0.0", "object.defaults": "^1.1.0" } }, "sha512-IYf1hpuWrdzse/s/YJOrFmU15lyhSzxelNVAHTEG3DtP4QsLTWZUzcUL3HMXmKQxXpa4EIrBPpwRgj0aehdvAw=="], @@ -1429,6 +1435,8 @@ "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "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" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + "lighthouse/axe-core": ["axe-core@4.11.4", "", {}, "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA=="], + "log-symbols/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], diff --git a/package.json b/package.json index 66f88a2..c179ef7 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,9 @@ "lint": "./vendor/bin/phpcs --standard=phpcs.xml .", "lint-fix": "./vendor/bin/phpcbf --standard=phpcs.xml .", "test:php": "./vendor/bin/phpunit", + "test:e2e:install": "bunx playwright install chromium", + "test:e2e": "bunx playwright test --config tests/playwright/playwright.config.ts", + "test:e2e:a11y": "bunx playwright test --config tests/playwright/playwright.config.ts tests/playwright/tests/accessibility", "icons:embed": "bun scripts/embed-icons-font.js", "bundle": "bun run gulp build && bun run i18n && composer install --no-dev && bunx bestzip build/cooked.zip cooked.php LICENSE readme.txt wpml-config.xml assets/ includes/ languages/ sample-data/ templates/ vendor/" }, @@ -54,9 +57,11 @@ }, "homepage": "https://github.com/XjSv/Cooked#readme", "devDependencies": { + "@axe-core/playwright": "^4.13.0", "@playwright/test": "^1.62.1", "@types/node": "^26.2.0", - "@wordpress/e2e-test-utils-playwright": "^1.52.0", + "@wordpress/e2e-test-utils-playwright": "^1.53.0", + "dotenv": "^17.4.2", "gulp": "^5.0.1", "gulp-clean-css": "^4.3.0", "gulp-rename": "^2.1.0", @@ -64,7 +69,7 @@ "gulp-uglify": "^3.0.2", "playwright": "^1.62.1", "sass": "^1.102.0", - "@wordpress/env": "^11.12.0", + "@wordpress/env": "^11.13.0", "bestzip": "^3.0.2" } } diff --git a/tests/playwright/.env.example b/tests/playwright/.env.example index 20ed45b..ca6f0b4 100644 --- a/tests/playwright/.env.example +++ b/tests/playwright/.env.example @@ -1,4 +1,9 @@ -EDAMAM_APP_ID= -EDAMAM_APP_KEY= - -# WordPress Nutrition Integration uses `wp` CLI — run tests inside DDEV, e.g. ddev playwright test tests/5_api/ +# Copy to .env in this directory (Playwright tests). +# +# DDEV Playwright defaults to this URL when WP_BASE_URL is unset. +# Without DDEV, Playwright uses http://localhost:8888 (wp-env). +WP_BASE_URL=https://dev.mimisrecipes.ddev.site +# +# Optional admin fixture override. Default is e2e_admin, created by Playwright globalSetup. +# WP_ADMIN_USER=e2e_admin +# WP_ADMIN_PASSWORD=password diff --git a/tests/playwright/bun.lock b/tests/playwright/bun.lock deleted file mode 100644 index 00a3730..0000000 --- a/tests/playwright/bun.lock +++ /dev/null @@ -1,1158 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 0, - "workspaces": { - "": { - "name": "playwright", - "devDependencies": { - "@axe-core/playwright": "^4.11.3", - "@playwright/test": "^1.58.1", - "@types/node": "^20.17.10", - "@wordpress/e2e-test-utils": "^11.34.0", - "@wordpress/e2e-test-utils-playwright": "^1.39.0", - "dotenv": "^17.3.1", - }, - }, - }, - "packages": { - "@axe-core/playwright": ["@axe-core/playwright@4.11.3", "", { "dependencies": { "axe-core": "~4.11.4" }, "peerDependencies": { "playwright-core": ">= 1.0.0" } }, "sha512-h/kfksv4F0cVIDlKpT4700OehdRgpvuVskuQ2nb7/JmtWUXpe9ftHAPtwyXGvVSsa6SJ64A9ER7Zrzc/sIvC4w=="], - - "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], - - "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], - - "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], - - "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], - - "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], - - "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], - - "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], - - "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], - - "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], - - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - - "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], - - "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="], - - "@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], - - "@babel/plugin-syntax-async-generators": ["@babel/plugin-syntax-async-generators@7.8.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw=="], - - "@babel/plugin-syntax-bigint": ["@babel/plugin-syntax-bigint@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg=="], - - "@babel/plugin-syntax-class-properties": ["@babel/plugin-syntax-class-properties@7.12.13", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA=="], - - "@babel/plugin-syntax-class-static-block": ["@babel/plugin-syntax-class-static-block@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw=="], - - "@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw=="], - - "@babel/plugin-syntax-import-meta": ["@babel/plugin-syntax-import-meta@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g=="], - - "@babel/plugin-syntax-json-strings": ["@babel/plugin-syntax-json-strings@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA=="], - - "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="], - - "@babel/plugin-syntax-logical-assignment-operators": ["@babel/plugin-syntax-logical-assignment-operators@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig=="], - - "@babel/plugin-syntax-nullish-coalescing-operator": ["@babel/plugin-syntax-nullish-coalescing-operator@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ=="], - - "@babel/plugin-syntax-numeric-separator": ["@babel/plugin-syntax-numeric-separator@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug=="], - - "@babel/plugin-syntax-object-rest-spread": ["@babel/plugin-syntax-object-rest-spread@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA=="], - - "@babel/plugin-syntax-optional-catch-binding": ["@babel/plugin-syntax-optional-catch-binding@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q=="], - - "@babel/plugin-syntax-optional-chaining": ["@babel/plugin-syntax-optional-chaining@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg=="], - - "@babel/plugin-syntax-private-property-in-object": ["@babel/plugin-syntax-private-property-in-object@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg=="], - - "@babel/plugin-syntax-top-level-await": ["@babel/plugin-syntax-top-level-await@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw=="], - - "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="], - - "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], - - "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], - - "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], - - "@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="], - - "@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], - - "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], - - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], - - "@formatjs/ecma402-abstract": ["@formatjs/ecma402-abstract@2.3.6", "", { "dependencies": { "@formatjs/fast-memoize": "2.2.7", "@formatjs/intl-localematcher": "0.6.2", "decimal.js": "^10.4.3", "tslib": "^2.8.0" } }, "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw=="], - - "@formatjs/fast-memoize": ["@formatjs/fast-memoize@2.2.7", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ=="], - - "@formatjs/icu-messageformat-parser": ["@formatjs/icu-messageformat-parser@2.11.4", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.6", "@formatjs/icu-skeleton-parser": "1.8.16", "tslib": "^2.8.0" } }, "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw=="], - - "@formatjs/icu-skeleton-parser": ["@formatjs/icu-skeleton-parser@1.8.16", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.6", "tslib": "^2.8.0" } }, "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ=="], - - "@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.6.2", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA=="], - - "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "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" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - - "@istanbuljs/load-nyc-config": ["@istanbuljs/load-nyc-config@1.1.0", "", { "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ=="], - - "@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="], - - "@jest/console": ["@jest/console@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "jest-message-util": "30.2.0", "jest-util": "30.2.0", "slash": "^3.0.0" } }, "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ=="], - - "@jest/core": ["@jest/core@30.2.0", "", { "dependencies": { "@jest/console": "30.2.0", "@jest/pattern": "30.0.1", "@jest/reporters": "30.2.0", "@jest/test-result": "30.2.0", "@jest/transform": "30.2.0", "@jest/types": "30.2.0", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", "jest-changed-files": "30.2.0", "jest-config": "30.2.0", "jest-haste-map": "30.2.0", "jest-message-util": "30.2.0", "jest-regex-util": "30.0.1", "jest-resolve": "30.2.0", "jest-resolve-dependencies": "30.2.0", "jest-runner": "30.2.0", "jest-runtime": "30.2.0", "jest-snapshot": "30.2.0", "jest-util": "30.2.0", "jest-validate": "30.2.0", "jest-watcher": "30.2.0", "micromatch": "^4.0.8", "pretty-format": "30.2.0", "slash": "^3.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ=="], - - "@jest/diff-sequences": ["@jest/diff-sequences@30.0.1", "", {}, "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw=="], - - "@jest/environment": ["@jest/environment@30.2.0", "", { "dependencies": { "@jest/fake-timers": "30.2.0", "@jest/types": "30.2.0", "@types/node": "*", "jest-mock": "30.2.0" } }, "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g=="], - - "@jest/expect": ["@jest/expect@30.2.0", "", { "dependencies": { "expect": "30.2.0", "jest-snapshot": "30.2.0" } }, "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA=="], - - "@jest/expect-utils": ["@jest/expect-utils@30.2.0", "", { "dependencies": { "@jest/get-type": "30.1.0" } }, "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA=="], - - "@jest/fake-timers": ["@jest/fake-timers@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", "jest-message-util": "30.2.0", "jest-mock": "30.2.0", "jest-util": "30.2.0" } }, "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw=="], - - "@jest/get-type": ["@jest/get-type@30.1.0", "", {}, "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA=="], - - "@jest/globals": ["@jest/globals@30.2.0", "", { "dependencies": { "@jest/environment": "30.2.0", "@jest/expect": "30.2.0", "@jest/types": "30.2.0", "jest-mock": "30.2.0" } }, "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw=="], - - "@jest/pattern": ["@jest/pattern@30.0.1", "", { "dependencies": { "@types/node": "*", "jest-regex-util": "30.0.1" } }, "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA=="], - - "@jest/reporters": ["@jest/reporters@30.2.0", "", { "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "30.2.0", "@jest/test-result": "30.2.0", "@jest/transform": "30.2.0", "@jest/types": "30.2.0", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", "chalk": "^4.1.2", "collect-v8-coverage": "^1.0.2", "exit-x": "^0.2.2", "glob": "^10.3.10", "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", "jest-message-util": "30.2.0", "jest-util": "30.2.0", "jest-worker": "30.2.0", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ=="], - - "@jest/schemas": ["@jest/schemas@30.0.5", "", { "dependencies": { "@sinclair/typebox": "^0.34.0" } }, "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA=="], - - "@jest/snapshot-utils": ["@jest/snapshot-utils@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" } }, "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug=="], - - "@jest/source-map": ["@jest/source-map@30.0.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "callsites": "^3.1.0", "graceful-fs": "^4.2.11" } }, "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg=="], - - "@jest/test-result": ["@jest/test-result@30.2.0", "", { "dependencies": { "@jest/console": "30.2.0", "@jest/types": "30.2.0", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" } }, "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg=="], - - "@jest/test-sequencer": ["@jest/test-sequencer@30.2.0", "", { "dependencies": { "@jest/test-result": "30.2.0", "graceful-fs": "^4.2.11", "jest-haste-map": "30.2.0", "slash": "^3.0.0" } }, "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q=="], - - "@jest/transform": ["@jest/transform@30.2.0", "", { "dependencies": { "@babel/core": "^7.27.4", "@jest/types": "30.2.0", "@jridgewell/trace-mapping": "^0.3.25", "babel-plugin-istanbul": "^7.0.1", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", "jest-haste-map": "30.2.0", "jest-regex-util": "30.0.1", "jest-util": "30.2.0", "micromatch": "^4.0.8", "pirates": "^4.0.7", "slash": "^3.0.0", "write-file-atomic": "^5.0.1" } }, "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA=="], - - "@jest/types": ["@jest/types@30.2.0", "", { "dependencies": { "@jest/pattern": "30.0.1", "@jest/schemas": "30.0.5", "@types/istanbul-lib-coverage": "^2.0.6", "@types/istanbul-reports": "^3.0.4", "@types/node": "*", "@types/yargs": "^17.0.33", "chalk": "^4.1.2" } }, "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg=="], - - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], - - "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], - - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], - - "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - - "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.57.2", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A=="], - - "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@1.30.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA=="], - - "@opentelemetry/core": ["@opentelemetry/core@1.30.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ=="], - - "@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.57.2", "", { "dependencies": { "@opentelemetry/api-logs": "0.57.2", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "semver": "^7.5.2", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg=="], - - "@opentelemetry/instrumentation-amqplib": ["@opentelemetry/instrumentation-amqplib@0.46.1", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-AyXVnlCf/xV3K/rNumzKxZqsULyITJH6OVLiW6730JPRqWA7Zc9bvYoVNpN6iOpTU8CasH34SU/ksVJmObFibQ=="], - - "@opentelemetry/instrumentation-connect": ["@opentelemetry/instrumentation-connect@0.43.1", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/connect": "3.4.38" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-ht7YGWQuV5BopMcw5Q2hXn3I8eG8TH0J/kc/GMcW4CuNTgiP6wCu44BOnucJWL3CmFWaRHI//vWyAhaC8BwePw=="], - - "@opentelemetry/instrumentation-dataloader": ["@opentelemetry/instrumentation-dataloader@0.16.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.57.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-K/qU4CjnzOpNkkKO4DfCLSQshejRNAJtd4esgigo/50nxCB6XCyi1dhAblUHM9jG5dRm8eu0FB+t87nIo99LYQ=="], - - "@opentelemetry/instrumentation-express": ["@opentelemetry/instrumentation-express@0.47.1", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-QNXPTWteDclR2B4pDFpz0TNghgB33UMjUt14B+BZPmtH1MwUFAfLHBaP5If0Z5NZC+jaH8oF2glgYjrmhZWmSw=="], - - "@opentelemetry/instrumentation-fs": ["@opentelemetry/instrumentation-fs@0.19.1", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.57.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-6g0FhB3B9UobAR60BGTcXg4IHZ6aaYJzp0Ki5FhnxyAPt8Ns+9SSvgcrnsN2eGmk3RWG5vYycUGOEApycQL24A=="], - - "@opentelemetry/instrumentation-generic-pool": ["@opentelemetry/instrumentation-generic-pool@0.43.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.57.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-M6qGYsp1cURtvVLGDrPPZemMFEbuMmCXgQYTReC/IbimV5sGrLBjB+/hANUpRZjX67nGLdKSVLZuQQAiNz+sww=="], - - "@opentelemetry/instrumentation-graphql": ["@opentelemetry/instrumentation-graphql@0.47.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.57.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-EGQRWMGqwiuVma8ZLAZnExQ7sBvbOx0N/AE/nlafISPs8S+QtXX+Viy6dcQwVWwYHQPAcuY3bFt3xgoAwb4ZNQ=="], - - "@opentelemetry/instrumentation-hapi": ["@opentelemetry/instrumentation-hapi@0.45.2", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-7Ehow/7Wp3aoyCrZwQpU7a2CnoMq0XhIcioFuKjBb0PLYfBfmTsFTUyatlHu0fRxhwcRsSQRTvEhmZu8CppBpQ=="], - - "@opentelemetry/instrumentation-http": ["@opentelemetry/instrumentation-http@0.57.2", "", { "dependencies": { "@opentelemetry/core": "1.30.1", "@opentelemetry/instrumentation": "0.57.2", "@opentelemetry/semantic-conventions": "1.28.0", "forwarded-parse": "2.1.2", "semver": "^7.5.2" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-1Uz5iJ9ZAlFOiPuwYg29Bf7bJJc/GeoeJIFKJYQf67nTVKFe8RHbEtxgkOmK4UGZNHKXcpW4P8cWBYzBn1USpg=="], - - "@opentelemetry/instrumentation-ioredis": ["@opentelemetry/instrumentation-ioredis@0.47.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/redis-common": "^0.36.2", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-OtFGSN+kgk/aoKgdkKQnBsQFDiG8WdCxu+UrHr0bXScdAmtSzLSraLo7wFIb25RVHfRWvzI5kZomqJYEg/l1iA=="], - - "@opentelemetry/instrumentation-kafkajs": ["@opentelemetry/instrumentation-kafkajs@0.7.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-OtjaKs8H7oysfErajdYr1yuWSjMAectT7Dwr+axIoZqT9lmEOkD/H/3rgAs8h/NIuEi2imSXD+vL4MZtOuJfqQ=="], - - "@opentelemetry/instrumentation-knex": ["@opentelemetry/instrumentation-knex@0.44.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-U4dQxkNhvPexffjEmGwCq68FuftFK15JgUF05y/HlK3M6W/G2iEaACIfXdSnwVNe9Qh0sPfw8LbOPxrWzGWGMQ=="], - - "@opentelemetry/instrumentation-koa": ["@opentelemetry/instrumentation-koa@0.47.1", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-l/c+Z9F86cOiPJUllUCt09v+kICKvT+Vg1vOAJHtHPsJIzurGayucfCMq2acd/A/yxeNWunl9d9eqZ0G+XiI6A=="], - - "@opentelemetry/instrumentation-lru-memoizer": ["@opentelemetry/instrumentation-lru-memoizer@0.44.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.57.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-5MPkYCvG2yw7WONEjYj5lr5JFehTobW7wX+ZUFy81oF2lr9IPfZk9qO+FTaM0bGEiymwfLwKe6jE15nHn1nmHg=="], - - "@opentelemetry/instrumentation-mongodb": ["@opentelemetry/instrumentation-mongodb@0.52.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-1xmAqOtRUQGR7QfJFfGV/M2kC7wmI2WgZdpru8hJl3S0r4hW0n3OQpEHlSGXJAaNFyvT+ilnwkT+g5L4ljHR6g=="], - - "@opentelemetry/instrumentation-mongoose": ["@opentelemetry/instrumentation-mongoose@0.46.1", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-3kINtW1LUTPkiXFRSSBmva1SXzS/72we/jL22N+BnF3DFcoewkdkHPYOIdAAk9gSicJ4d5Ojtt1/HeibEc5OQg=="], - - "@opentelemetry/instrumentation-mysql": ["@opentelemetry/instrumentation-mysql@0.45.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/mysql": "2.15.26" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-TKp4hQ8iKQsY7vnp/j0yJJ4ZsP109Ht6l4RHTj0lNEG1TfgTrIH5vJMbgmoYXWzNHAqBH2e7fncN12p3BP8LFg=="], - - "@opentelemetry/instrumentation-mysql2": ["@opentelemetry/instrumentation-mysql2@0.45.2", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0", "@opentelemetry/sql-common": "^0.40.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-h6Ad60FjCYdJZ5DTz1Lk2VmQsShiViKe0G7sYikb0GHI0NVvApp2XQNRHNjEMz87roFttGPLHOYVPlfy+yVIhQ=="], - - "@opentelemetry/instrumentation-pg": ["@opentelemetry/instrumentation-pg@0.51.1", "", { "dependencies": { "@opentelemetry/core": "^1.26.0", "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0", "@opentelemetry/sql-common": "^0.40.1", "@types/pg": "8.6.1", "@types/pg-pool": "2.0.6" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-QxgjSrxyWZc7Vk+qGSfsejPVFL1AgAJdSBMYZdDUbwg730D09ub3PXScB9d04vIqPriZ+0dqzjmQx0yWKiCi2Q=="], - - "@opentelemetry/instrumentation-redis-4": ["@opentelemetry/instrumentation-redis-4@0.46.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/redis-common": "^0.36.2", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-UMqleEoabYMsWoTkqyt9WAzXwZ4BlFZHO40wr3d5ZvtjKCHlD4YXLm+6OLCeIi/HkX7EXvQaz8gtAwkwwSEvcQ=="], - - "@opentelemetry/instrumentation-tedious": ["@opentelemetry/instrumentation-tedious@0.18.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/tedious": "^4.0.14" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-5Cuy/nj0HBaH+ZJ4leuD7RjgvA844aY2WW+B5uLcWtxGjRZl3MNLuxnNg5DYWZNPO+NafSSnra0q49KWAHsKBg=="], - - "@opentelemetry/instrumentation-undici": ["@opentelemetry/instrumentation-undici@0.10.1", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.57.1" }, "peerDependencies": { "@opentelemetry/api": "^1.7.0" } }, "sha512-rkOGikPEyRpMCmNu9AQuV5dtRlDmJp2dK5sw8roVshAGoB6hH/3QjDtRhdwd75SsJwgynWUNRUYe0wAkTo16tQ=="], - - "@opentelemetry/redis-common": ["@opentelemetry/redis-common@0.36.2", "", {}, "sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g=="], - - "@opentelemetry/resources": ["@opentelemetry/resources@1.30.1", "", { "dependencies": { "@opentelemetry/core": "1.30.1", "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA=="], - - "@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@1.30.1", "", { "dependencies": { "@opentelemetry/core": "1.30.1", "@opentelemetry/resources": "1.30.1", "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg=="], - - "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], - - "@opentelemetry/sql-common": ["@opentelemetry/sql-common@0.40.1", "", { "dependencies": { "@opentelemetry/core": "^1.1.0" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0" } }, "sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg=="], - - "@paulirish/trace_engine": ["@paulirish/trace_engine@0.0.59", "", { "dependencies": { "legacy-javascript": "latest", "third-party-web": "latest" } }, "sha512-439NUzQGmH+9Y017/xCchBP9571J4bzhpcNhrxorf7r37wcyJZkgUfrUsRL3xl+JDcZ6ORhoFCzCw98c6S3YHw=="], - - "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], - - "@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="], - - "@playwright/test": ["@playwright/test@1.58.2", "", { "dependencies": { "playwright": "1.58.2" }, "bin": { "playwright": "cli.js" } }, "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA=="], - - "@prisma/instrumentation": ["@prisma/instrumentation@6.11.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0" }, "peerDependencies": { "@opentelemetry/api": "^1.8" } }, "sha512-mrZOev24EDhnefmnZX7WVVT7v+r9LttPRqf54ONvj6re4XMF7wFTpK2tLJi4XHB7fFp/6xhYbgRel8YV7gQiyA=="], - - "@puppeteer/browsers": ["@puppeteer/browsers@2.12.0", "", { "dependencies": { "debug": "^4.4.3", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.7.3", "tar-fs": "^3.1.1", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-Xuq42yxcQJ54ti8ZHNzF5snFvtpgXzNToJ1bXUGQRaiO8t+B6UM8sTUJfvV+AJnqtkJU/7hdy6nbKyA12aHtRw=="], - - "@sentry/core": ["@sentry/core@9.47.1", "", {}, "sha512-KX62+qIt4xgy8eHKHiikfhz2p5fOciXd0Cl+dNzhgPFq8klq4MGMNaf148GB3M/vBqP4nw/eFvRMAayFCgdRQw=="], - - "@sentry/node": ["@sentry/node@9.47.1", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1", "@opentelemetry/core": "^1.30.1", "@opentelemetry/instrumentation": "^0.57.2", "@opentelemetry/instrumentation-amqplib": "^0.46.1", "@opentelemetry/instrumentation-connect": "0.43.1", "@opentelemetry/instrumentation-dataloader": "0.16.1", "@opentelemetry/instrumentation-express": "0.47.1", "@opentelemetry/instrumentation-fs": "0.19.1", "@opentelemetry/instrumentation-generic-pool": "0.43.1", "@opentelemetry/instrumentation-graphql": "0.47.1", "@opentelemetry/instrumentation-hapi": "0.45.2", "@opentelemetry/instrumentation-http": "0.57.2", "@opentelemetry/instrumentation-ioredis": "0.47.1", "@opentelemetry/instrumentation-kafkajs": "0.7.1", "@opentelemetry/instrumentation-knex": "0.44.1", "@opentelemetry/instrumentation-koa": "0.47.1", "@opentelemetry/instrumentation-lru-memoizer": "0.44.1", "@opentelemetry/instrumentation-mongodb": "0.52.0", "@opentelemetry/instrumentation-mongoose": "0.46.1", "@opentelemetry/instrumentation-mysql": "0.45.1", "@opentelemetry/instrumentation-mysql2": "0.45.2", "@opentelemetry/instrumentation-pg": "0.51.1", "@opentelemetry/instrumentation-redis-4": "0.46.1", "@opentelemetry/instrumentation-tedious": "0.18.1", "@opentelemetry/instrumentation-undici": "0.10.1", "@opentelemetry/resources": "^1.30.1", "@opentelemetry/sdk-trace-base": "^1.30.1", "@opentelemetry/semantic-conventions": "^1.34.0", "@prisma/instrumentation": "6.11.1", "@sentry/core": "9.47.1", "@sentry/node-core": "9.47.1", "@sentry/opentelemetry": "9.47.1", "import-in-the-middle": "^1.14.2", "minimatch": "^9.0.0" } }, "sha512-CDbkasBz3fnWRKSFs6mmaRepM2pa+tbZkrqhPWifFfIkJDidtVW40p6OnquTvPXyPAszCnDZRnZT14xyvNmKPQ=="], - - "@sentry/node-core": ["@sentry/node-core@9.47.1", "", { "dependencies": { "@sentry/core": "9.47.1", "@sentry/opentelemetry": "9.47.1", "import-in-the-middle": "^1.14.2" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.0.0", "@opentelemetry/core": "^1.30.1 || ^2.0.0", "@opentelemetry/instrumentation": ">=0.57.1 <1", "@opentelemetry/resources": "^1.30.1 || ^2.0.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.0.0", "@opentelemetry/semantic-conventions": "^1.34.0" } }, "sha512-7TEOiCGkyShJ8CKtsri9lbgMCbB+qNts2Xq37itiMPN2m+lIukK3OX//L8DC5nfKYZlgikrefS63/vJtm669hQ=="], - - "@sentry/opentelemetry": ["@sentry/opentelemetry@9.47.1", "", { "dependencies": { "@sentry/core": "9.47.1" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.0.0", "@opentelemetry/core": "^1.30.1 || ^2.0.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.0.0", "@opentelemetry/semantic-conventions": "^1.34.0" } }, "sha512-STtFpjF7lwzeoedDJV+5XA6P89BfmFwFftmHSGSe3UTI8z8IoiR5yB6X2vCjSPvXlfeOs13qCNNCEZyznxM8Xw=="], - - "@sinclair/typebox": ["@sinclair/typebox@0.34.48", "", {}, "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA=="], - - "@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="], - - "@sinonjs/fake-timers": ["@sinonjs/fake-timers@13.0.5", "", { "dependencies": { "@sinonjs/commons": "^3.0.1" } }, "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw=="], - - "@tannin/compile": ["@tannin/compile@1.1.0", "", { "dependencies": { "@tannin/evaluate": "^1.2.0", "@tannin/postfix": "^1.1.0" } }, "sha512-n8m9eNDfoNZoxdvWiTfW/hSPhehzLJ3zW7f8E7oT6mCROoMNWCB4TYtv041+2FMAxweiE0j7i1jubQU4MEC/Gg=="], - - "@tannin/evaluate": ["@tannin/evaluate@1.2.0", "", {}, "sha512-3ioXvNowbO/wSrxsDG5DKIMxC81P0QrQTYai8zFNY+umuoHWRPbQ/TuuDEOju9E+jQDXmj6yI5GyejNuh8I+eg=="], - - "@tannin/plural-forms": ["@tannin/plural-forms@1.1.0", "", { "dependencies": { "@tannin/compile": "^1.1.0" } }, "sha512-xl9R2mDZO/qiHam1AgMnAES6IKIg7OBhcXqy6eDsRCdXuxAFPcjrej9HMjyCLE0DJ/8cHf0i5OQTstuBRhpbHw=="], - - "@tannin/postfix": ["@tannin/postfix@1.1.0", "", {}, "sha512-oocsqY7g0cR+Gur5jRQLSrX2OtpMLMse1I10JQBm8CdGMrDkh1Mg2gjsiquMHRtBs4Qwu5wgEp5GgIYHk4SNPw=="], - - "@tannin/sprintf": ["@tannin/sprintf@1.3.3", "", {}, "sha512-RwARl+hFwhzy0tg9atWcchLFvoQiOh4rrP7uG2N5E4W80BPCUX0ElcUR9St43fxB9EfjsW2df9Qp+UsTbvQDjA=="], - - "@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="], - - "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], - - "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], - - "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], - - "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], - - "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], - - "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], - - "@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="], - - "@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="], - - "@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="], - - "@types/mysql": ["@types/mysql@2.15.26", "", { "dependencies": { "@types/node": "*" } }, "sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ=="], - - "@types/node": ["@types/node@20.19.32", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-Ez8QE4DMfhjjTsES9K2dwfV258qBui7qxUsoaixZDiTzbde4U12e1pXGNu/ECsUIOi5/zoCxAQxIhQnaUQ2VvA=="], - - "@types/pg": ["@types/pg@8.6.1", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w=="], - - "@types/pg-pool": ["@types/pg-pool@2.0.6", "", { "dependencies": { "@types/pg": "*" } }, "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ=="], - - "@types/shimmer": ["@types/shimmer@1.2.0", "", {}, "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg=="], - - "@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="], - - "@types/tedious": ["@types/tedious@4.0.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw=="], - - "@types/yargs": ["@types/yargs@17.0.35", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg=="], - - "@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="], - - "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], - - "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], - - "@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.11.1", "", { "os": "android", "cpu": "arm" }, "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw=="], - - "@unrs/resolver-binding-android-arm64": ["@unrs/resolver-binding-android-arm64@1.11.1", "", { "os": "android", "cpu": "arm64" }, "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g=="], - - "@unrs/resolver-binding-darwin-arm64": ["@unrs/resolver-binding-darwin-arm64@1.11.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g=="], - - "@unrs/resolver-binding-darwin-x64": ["@unrs/resolver-binding-darwin-x64@1.11.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ=="], - - "@unrs/resolver-binding-freebsd-x64": ["@unrs/resolver-binding-freebsd-x64@1.11.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw=="], - - "@unrs/resolver-binding-linux-arm-gnueabihf": ["@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1", "", { "os": "linux", "cpu": "arm" }, "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw=="], - - "@unrs/resolver-binding-linux-arm-musleabihf": ["@unrs/resolver-binding-linux-arm-musleabihf@1.11.1", "", { "os": "linux", "cpu": "arm" }, "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw=="], - - "@unrs/resolver-binding-linux-arm64-gnu": ["@unrs/resolver-binding-linux-arm64-gnu@1.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ=="], - - "@unrs/resolver-binding-linux-arm64-musl": ["@unrs/resolver-binding-linux-arm64-musl@1.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w=="], - - "@unrs/resolver-binding-linux-ppc64-gnu": ["@unrs/resolver-binding-linux-ppc64-gnu@1.11.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA=="], - - "@unrs/resolver-binding-linux-riscv64-gnu": ["@unrs/resolver-binding-linux-riscv64-gnu@1.11.1", "", { "os": "linux", "cpu": "none" }, "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ=="], - - "@unrs/resolver-binding-linux-riscv64-musl": ["@unrs/resolver-binding-linux-riscv64-musl@1.11.1", "", { "os": "linux", "cpu": "none" }, "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew=="], - - "@unrs/resolver-binding-linux-s390x-gnu": ["@unrs/resolver-binding-linux-s390x-gnu@1.11.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg=="], - - "@unrs/resolver-binding-linux-x64-gnu": ["@unrs/resolver-binding-linux-x64-gnu@1.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w=="], - - "@unrs/resolver-binding-linux-x64-musl": ["@unrs/resolver-binding-linux-x64-musl@1.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA=="], - - "@unrs/resolver-binding-wasm32-wasi": ["@unrs/resolver-binding-wasm32-wasi@1.11.1", "", { "dependencies": { "@napi-rs/wasm-runtime": "^0.2.11" }, "cpu": "none" }, "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ=="], - - "@unrs/resolver-binding-win32-arm64-msvc": ["@unrs/resolver-binding-win32-arm64-msvc@1.11.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw=="], - - "@unrs/resolver-binding-win32-ia32-msvc": ["@unrs/resolver-binding-win32-ia32-msvc@1.11.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ=="], - - "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="], - - "@wordpress/api-fetch": ["@wordpress/api-fetch@7.39.0", "", { "dependencies": { "@wordpress/i18n": "^6.12.0", "@wordpress/url": "^4.39.0" } }, "sha512-H6ysbEgDYuIDhvRje1iTyC9r4bEOY9AT0fRmNEr6fSG/furznt4XNtoflzh4Q4DeaC2flTQ9hn46JgcmW3IXJA=="], - - "@wordpress/e2e-test-utils": ["@wordpress/e2e-test-utils@11.34.0", "", { "dependencies": { "@wordpress/api-fetch": "^7.34.0", "@wordpress/keycodes": "^4.34.0", "@wordpress/url": "^4.34.0", "change-case": "^4.1.2", "form-data": "^4.0.0", "node-fetch": "2.7.0" }, "peerDependencies": { "jest": ">=29", "puppeteer-core": ">=23" } }, "sha512-fDCxSTSzOtuDYlfme/LBFZIiRv++y35ykymkYQD5mbd6yFz9hotZfVUzqtXMY3x2kttgBwp28/U8geAt7ym8mQ=="], - - "@wordpress/e2e-test-utils-playwright": ["@wordpress/e2e-test-utils-playwright@1.39.0", "", { "dependencies": { "change-case": "^4.1.2", "get-port": "^5.1.1", "lighthouse": "^12.2.2", "mime": "^3.0.0", "web-vitals": "^4.2.1" }, "peerDependencies": { "@playwright/test": ">=1", "@types/node": "^20.17.10" } }, "sha512-ok008Rd8URqNQCzx/9bClC+gk7XxVV+xm5rOJjb6A4EHR8tVlynJEcE4gN0C5qCDIeOdPLbGW8ljNm2DxlelwQ=="], - - "@wordpress/hooks": ["@wordpress/hooks@4.39.0", "", {}, "sha512-FTKdGF5jHHmC8GSO6/ATQqh1IFQeDwapRtlp7t4VaTGwZtX+uzawgq/7QDIhFi3cfg9hNsFF0CSFp/Ul3nEeUA=="], - - "@wordpress/i18n": ["@wordpress/i18n@6.12.0", "", { "dependencies": { "@tannin/sprintf": "^1.3.2", "@wordpress/hooks": "^4.39.0", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "tannin": "^1.2.0" }, "bin": { "pot-to-php": "tools/pot-to-php.js" } }, "sha512-KMleg8p/HtnoX1d/WoRDI51VTZsA4RGNUvBYn+Cc3avaeeNKROb91+viMcOc8NHuLplEzl7zH9/mrOSs9aY3rg=="], - - "@wordpress/keycodes": ["@wordpress/keycodes@4.39.0", "", { "dependencies": { "@wordpress/i18n": "^6.12.0" } }, "sha512-RN7Py7vvvmBOGuRM8X4hHOvXXG57jWDW9pIXUnVGc33EvTrwtnr16f8Xt4nKWs8L/UNUdrrAtA2HAeqRkdxr+A=="], - - "@wordpress/url": ["@wordpress/url@4.39.0", "", { "dependencies": { "remove-accents": "^0.5.0" } }, "sha512-pWOtqLcApB1EZnD2am/vMHxkCLgHzpysUypP8N8jz+USdLyOKy7vaY3v94+HAL1M9HBoT9VpllWEg+LxsO/eqQ=="], - - "acorn": ["acorn@8.15.0", "", { "bin": "bin/acorn" }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], - - "acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="], - - "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], - - "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], - - "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], - - "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], - - "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], - - "ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="], - - "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], - - "atomically": ["atomically@2.1.0", "", { "dependencies": { "stubborn-fs": "^2.0.0", "when-exit": "^2.1.4" } }, "sha512-+gDffFXRW6sl/HCwbta7zK4uNqbPjv4YJEAdz7Vu+FLQHe77eZ4bvbJGi4hE0QPeJlMYMA3piXEr1UL3dAwx7Q=="], - - "axe-core": ["axe-core@4.11.4", "", {}, "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA=="], - - "b4a": ["b4a@1.7.3", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q=="], - - "babel-jest": ["babel-jest@30.2.0", "", { "dependencies": { "@jest/transform": "30.2.0", "@types/babel__core": "^7.20.5", "babel-plugin-istanbul": "^7.0.1", "babel-preset-jest": "30.2.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" }, "peerDependencies": { "@babel/core": "^7.11.0 || ^8.0.0-0" } }, "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw=="], - - "babel-plugin-istanbul": ["babel-plugin-istanbul@7.0.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-instrument": "^6.0.2", "test-exclude": "^6.0.0" } }, "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA=="], - - "babel-plugin-jest-hoist": ["babel-plugin-jest-hoist@30.2.0", "", { "dependencies": { "@types/babel__core": "^7.20.5" } }, "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA=="], - - "babel-preset-current-node-syntax": ["babel-preset-current-node-syntax@1.2.0", "", { "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg=="], - - "babel-preset-jest": ["babel-preset-jest@30.2.0", "", { "dependencies": { "babel-plugin-jest-hoist": "30.2.0", "babel-preset-current-node-syntax": "^1.2.0" }, "peerDependencies": { "@babel/core": "^7.11.0 || ^8.0.0-beta.1" } }, "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ=="], - - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="], - - "bare-fs": ["bare-fs@4.5.3", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-9+kwVx8QYvt3hPWnmb19tPnh38c6Nihz8Lx3t0g9+4GoIf3/fTgYwM4Z6NxgI+B9elLQA7mLE9PpqcWtOMRDiQ=="], - - "bare-os": ["bare-os@3.6.2", "", {}, "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A=="], - - "bare-path": ["bare-path@3.0.0", "", { "dependencies": { "bare-os": "^3.0.1" } }, "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw=="], - - "bare-stream": ["bare-stream@2.7.0", "", { "dependencies": { "streamx": "^2.21.0" }, "peerDependencies": { "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A=="], - - "bare-url": ["bare-url@2.3.2", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw=="], - - "baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": "dist/cli.js" }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="], - - "basic-ftp": ["basic-ftp@5.1.0", "", {}, "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw=="], - - "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - - "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - - "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": "cli.js" }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], - - "bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="], - - "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], - - "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], - - "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - - "camel-case": ["camel-case@4.1.2", "", { "dependencies": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" } }, "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw=="], - - "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], - - "caniuse-lite": ["caniuse-lite@1.0.30001769", "", {}, "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg=="], - - "capital-case": ["capital-case@1.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", "upper-case-first": "^2.0.2" } }, "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A=="], - - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - - "change-case": ["change-case@4.1.2", "", { "dependencies": { "camel-case": "^4.1.2", "capital-case": "^1.0.4", "constant-case": "^3.0.4", "dot-case": "^3.0.4", "header-case": "^2.0.4", "no-case": "^3.0.4", "param-case": "^3.0.4", "pascal-case": "^3.1.2", "path-case": "^3.0.4", "sentence-case": "^3.0.4", "snake-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A=="], - - "char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="], - - "chrome-launcher": ["chrome-launcher@1.2.1", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^2.0.1" }, "bin": { "print-chrome-path": "bin/print-chrome-path.cjs" } }, "sha512-qmFR5PLMzHyuNJHwOloHPAHhbaNglkfeV/xDtt5b7xiFFyU1I+AZZX0PYseMuhenJSSirgxELYIbswcoc+5H4A=="], - - "chromium-bidi": ["chromium-bidi@13.1.1", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-zB9MpoPd7VJwjowQqiW3FKOvQwffFMjQ8Iejp5ZW+sJaKLRhZX1sTxzl3Zt22TDB4zP0OOqs8lRoY7eAW5geyQ=="], - - "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], - - "cjs-module-lexer": ["cjs-module-lexer@2.2.0", "", {}, "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ=="], - - "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], - - "co": ["co@4.6.0", "", {}, "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ=="], - - "collect-v8-coverage": ["collect-v8-coverage@1.0.3", "", {}, "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw=="], - - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - - "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], - - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - - "configstore": ["configstore@7.1.0", "", { "dependencies": { "atomically": "^2.0.3", "dot-prop": "^9.0.0", "graceful-fs": "^4.2.11", "xdg-basedir": "^5.1.0" } }, "sha512-N4oog6YJWbR9kGyXvS7jEykLDXIE2C0ILYqNBZBp9iwiJpoCBWYsuAdW6PPFn6w06jjnC+3JstVvWHO4cZqvRg=="], - - "constant-case": ["constant-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", "upper-case": "^2.0.2" } }, "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ=="], - - "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - - "csp_evaluator": ["csp_evaluator@1.1.5", "", {}, "sha512-EL/iN9etCTzw/fBnp0/uj0f5BOOGvZut2mzsiiBZ/FdT6gFQCKRO/tmcKOxn5drWZ2Ndm/xBb1SI4zwWbGtmIw=="], - - "data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="], - - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], - - "dedent": ["dedent@1.7.1", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg=="], - - "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], - - "define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="], - - "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="], - - "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], - - "detect-newline": ["detect-newline@3.1.0", "", {}, "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA=="], - - "devtools-protocol": ["devtools-protocol@0.0.1566079", "", {}, "sha512-MJfAEA1UfVhSs7fbSQOG4czavUp1ajfg6prlAN0+cmfa2zNjaIbvq8VneP7do1WAQQIvgNJWSMeP6UyI90gIlQ=="], - - "dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="], - - "dot-prop": ["dot-prop@9.0.0", "", { "dependencies": { "type-fest": "^4.18.2" } }, "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ=="], - - "dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="], - - "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - - "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], - - "electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="], - - "emittery": ["emittery@0.13.1", "", {}, "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ=="], - - "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="], - - "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], - - "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="], - - "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], - - "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], - - "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - - "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], - - "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], - - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - - "escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "escodegen": "bin/escodegen.js", "esgenerate": "bin/esgenerate.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="], - - "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], - - "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - - "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - - "events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="], - - "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], - - "exit-x": ["exit-x@0.2.2", "", {}, "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ=="], - - "expect": ["expect@30.2.0", "", { "dependencies": { "@jest/expect-utils": "30.2.0", "@jest/get-type": "30.1.0", "jest-matcher-utils": "30.2.0", "jest-message-util": "30.2.0", "jest-mock": "30.2.0", "jest-util": "30.2.0" } }, "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw=="], - - "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": "cli.js" }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], - - "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], - - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], - - "fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="], - - "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], - - "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], - - "find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], - - "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - - "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], - - "forwarded-parse": ["forwarded-parse@2.1.2", "", {}, "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw=="], - - "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], - - "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], - - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - - "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], - - "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], - - "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], - - "get-package-type": ["get-package-type@0.1.0", "", {}, "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="], - - "get-port": ["get-port@5.1.1", "", {}, "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ=="], - - "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - - "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], - - "get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="], - - "gettext-parser": ["gettext-parser@1.4.0", "", { "dependencies": { "encoding": "^0.1.12", "safe-buffer": "^5.1.1" } }, "sha512-sedZYLHlHeBop/gZ1jdg59hlUEcpcZJofLq2JFwJT1zTqAU3l2wFv6IsuwFHGqbiT9DWzMUW4/em2+hspnmMMA=="], - - "glob": ["glob@10.5.0", "", { "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": "dist/esm/bin.mjs" }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], - - "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - - "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], - - "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - - "header-case": ["header-case@2.0.4", "", { "dependencies": { "capital-case": "^1.0.4", "tslib": "^2.0.3" } }, "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q=="], - - "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], - - "http-link-header": ["http-link-header@1.1.3", "", {}, "sha512-3cZ0SRL8fb9MUlU3mKM61FcQvPfXx2dBrZW3Vbg5CXa8jFlK8OaEpePenLe1oEXQduhz8b0QjsqfS59QP4AJDQ=="], - - "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], - - "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], - - "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], - - "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], - - "image-ssim": ["image-ssim@0.2.0", "", {}, "sha512-W7+sO6/yhxy83L0G7xR8YAc5Z5QFtYEXXRV6EaE8tuYBZJnA3gVgp3q7X7muhLZVodeb9UfvjSbwt9VJwjIYAg=="], - - "import-in-the-middle": ["import-in-the-middle@1.15.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^1.2.2", "module-details-from-path": "^1.0.3" } }, "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA=="], - - "import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="], - - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - - "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], - - "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], - - "intl-messageformat": ["intl-messageformat@10.7.18", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.6", "@formatjs/fast-memoize": "2.2.7", "@formatjs/icu-messageformat-parser": "2.11.4", "tslib": "^2.8.0" } }, "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g=="], - - "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], - - "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], - - "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], - - "is-docker": ["is-docker@2.2.1", "", { "bin": "cli.js" }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], - - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - - "is-generator-fn": ["is-generator-fn@2.1.0", "", {}, "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ=="], - - "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], - - "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], - - "is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], - - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], - - "istanbul-lib-instrument": ["istanbul-lib-instrument@6.0.3", "", { "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" } }, "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q=="], - - "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], - - "istanbul-lib-source-maps": ["istanbul-lib-source-maps@5.0.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0" } }, "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A=="], - - "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="], - - "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - - "jest": ["jest@30.2.0", "", { "dependencies": { "@jest/core": "30.2.0", "@jest/types": "30.2.0", "import-local": "^3.2.0", "jest-cli": "30.2.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": "bin/jest.js" }, "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A=="], - - "jest-changed-files": ["jest-changed-files@30.2.0", "", { "dependencies": { "execa": "^5.1.1", "jest-util": "30.2.0", "p-limit": "^3.1.0" } }, "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ=="], - - "jest-circus": ["jest-circus@30.2.0", "", { "dependencies": { "@jest/environment": "30.2.0", "@jest/expect": "30.2.0", "@jest/test-result": "30.2.0", "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", "jest-each": "30.2.0", "jest-matcher-utils": "30.2.0", "jest-message-util": "30.2.0", "jest-runtime": "30.2.0", "jest-snapshot": "30.2.0", "jest-util": "30.2.0", "p-limit": "^3.1.0", "pretty-format": "30.2.0", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" } }, "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg=="], - - "jest-cli": ["jest-cli@30.2.0", "", { "dependencies": { "@jest/core": "30.2.0", "@jest/test-result": "30.2.0", "@jest/types": "30.2.0", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", "jest-config": "30.2.0", "jest-util": "30.2.0", "jest-validate": "30.2.0", "yargs": "^17.7.2" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA=="], - - "jest-config": ["jest-config@30.2.0", "", { "dependencies": { "@babel/core": "^7.27.4", "@jest/get-type": "30.1.0", "@jest/pattern": "30.0.1", "@jest/test-sequencer": "30.2.0", "@jest/types": "30.2.0", "babel-jest": "30.2.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", "glob": "^10.3.10", "graceful-fs": "^4.2.11", "jest-circus": "30.2.0", "jest-docblock": "30.2.0", "jest-environment-node": "30.2.0", "jest-regex-util": "30.0.1", "jest-resolve": "30.2.0", "jest-runner": "30.2.0", "jest-util": "30.2.0", "jest-validate": "30.2.0", "micromatch": "^4.0.8", "parse-json": "^5.2.0", "pretty-format": "30.2.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "peerDependencies": { "@types/node": "*", "esbuild-register": ">=3.4.0", "ts-node": ">=9.0.0" }, "optionalPeers": ["esbuild-register", "ts-node"] }, "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA=="], - - "jest-diff": ["jest-diff@30.2.0", "", { "dependencies": { "@jest/diff-sequences": "30.0.1", "@jest/get-type": "30.1.0", "chalk": "^4.1.2", "pretty-format": "30.2.0" } }, "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A=="], - - "jest-docblock": ["jest-docblock@30.2.0", "", { "dependencies": { "detect-newline": "^3.1.0" } }, "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA=="], - - "jest-each": ["jest-each@30.2.0", "", { "dependencies": { "@jest/get-type": "30.1.0", "@jest/types": "30.2.0", "chalk": "^4.1.2", "jest-util": "30.2.0", "pretty-format": "30.2.0" } }, "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ=="], - - "jest-environment-node": ["jest-environment-node@30.2.0", "", { "dependencies": { "@jest/environment": "30.2.0", "@jest/fake-timers": "30.2.0", "@jest/types": "30.2.0", "@types/node": "*", "jest-mock": "30.2.0", "jest-util": "30.2.0", "jest-validate": "30.2.0" } }, "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA=="], - - "jest-haste-map": ["jest-haste-map@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", "graceful-fs": "^4.2.11", "jest-regex-util": "30.0.1", "jest-util": "30.2.0", "jest-worker": "30.2.0", "micromatch": "^4.0.8", "walker": "^1.0.8" }, "optionalDependencies": { "fsevents": "^2.3.3" } }, "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw=="], - - "jest-leak-detector": ["jest-leak-detector@30.2.0", "", { "dependencies": { "@jest/get-type": "30.1.0", "pretty-format": "30.2.0" } }, "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ=="], - - "jest-matcher-utils": ["jest-matcher-utils@30.2.0", "", { "dependencies": { "@jest/get-type": "30.1.0", "chalk": "^4.1.2", "jest-diff": "30.2.0", "pretty-format": "30.2.0" } }, "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg=="], - - "jest-message-util": ["jest-message-util@30.2.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@jest/types": "30.2.0", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "micromatch": "^4.0.8", "pretty-format": "30.2.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" } }, "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw=="], - - "jest-mock": ["jest-mock@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "jest-util": "30.2.0" } }, "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw=="], - - "jest-pnp-resolver": ["jest-pnp-resolver@1.2.3", "", { "peerDependencies": { "jest-resolve": "*" } }, "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w=="], - - "jest-regex-util": ["jest-regex-util@30.0.1", "", {}, "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA=="], - - "jest-resolve": ["jest-resolve@30.2.0", "", { "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "jest-haste-map": "30.2.0", "jest-pnp-resolver": "^1.2.3", "jest-util": "30.2.0", "jest-validate": "30.2.0", "slash": "^3.0.0", "unrs-resolver": "^1.7.11" } }, "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A=="], - - "jest-resolve-dependencies": ["jest-resolve-dependencies@30.2.0", "", { "dependencies": { "jest-regex-util": "30.0.1", "jest-snapshot": "30.2.0" } }, "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w=="], - - "jest-runner": ["jest-runner@30.2.0", "", { "dependencies": { "@jest/console": "30.2.0", "@jest/environment": "30.2.0", "@jest/test-result": "30.2.0", "@jest/transform": "30.2.0", "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", "jest-docblock": "30.2.0", "jest-environment-node": "30.2.0", "jest-haste-map": "30.2.0", "jest-leak-detector": "30.2.0", "jest-message-util": "30.2.0", "jest-resolve": "30.2.0", "jest-runtime": "30.2.0", "jest-util": "30.2.0", "jest-watcher": "30.2.0", "jest-worker": "30.2.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" } }, "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ=="], - - "jest-runtime": ["jest-runtime@30.2.0", "", { "dependencies": { "@jest/environment": "30.2.0", "@jest/fake-timers": "30.2.0", "@jest/globals": "30.2.0", "@jest/source-map": "30.0.1", "@jest/test-result": "30.2.0", "@jest/transform": "30.2.0", "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "cjs-module-lexer": "^2.1.0", "collect-v8-coverage": "^1.0.2", "glob": "^10.3.10", "graceful-fs": "^4.2.11", "jest-haste-map": "30.2.0", "jest-message-util": "30.2.0", "jest-mock": "30.2.0", "jest-regex-util": "30.0.1", "jest-resolve": "30.2.0", "jest-snapshot": "30.2.0", "jest-util": "30.2.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" } }, "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg=="], - - "jest-snapshot": ["jest-snapshot@30.2.0", "", { "dependencies": { "@babel/core": "^7.27.4", "@babel/generator": "^7.27.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", "@jest/expect-utils": "30.2.0", "@jest/get-type": "30.1.0", "@jest/snapshot-utils": "30.2.0", "@jest/transform": "30.2.0", "@jest/types": "30.2.0", "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", "expect": "30.2.0", "graceful-fs": "^4.2.11", "jest-diff": "30.2.0", "jest-matcher-utils": "30.2.0", "jest-message-util": "30.2.0", "jest-util": "30.2.0", "pretty-format": "30.2.0", "semver": "^7.7.2", "synckit": "^0.11.8" } }, "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA=="], - - "jest-util": ["jest-util@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.2" } }, "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA=="], - - "jest-validate": ["jest-validate@30.2.0", "", { "dependencies": { "@jest/get-type": "30.1.0", "@jest/types": "30.2.0", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", "pretty-format": "30.2.0" } }, "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw=="], - - "jest-watcher": ["jest-watcher@30.2.0", "", { "dependencies": { "@jest/test-result": "30.2.0", "@jest/types": "30.2.0", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", "jest-util": "30.2.0", "string-length": "^4.0.2" } }, "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg=="], - - "jest-worker": ["jest-worker@30.2.0", "", { "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", "jest-util": "30.2.0", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" } }, "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g=="], - - "jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="], - - "js-library-detector": ["js-library-detector@6.7.0", "", {}, "sha512-c80Qupofp43y4cJ7+8TTDN/AsDwLi5oOm/plBrWI+iQt485vKXCco+yVmOwEgdo9VOdsYTuV0UlTeetVPTriXA=="], - - "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - - "js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": "bin/js-yaml.js" }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], - - "jsesc": ["jsesc@3.1.0", "", { "bin": "bin/jsesc" }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], - - "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], - - "json5": ["json5@2.2.3", "", { "bin": "lib/cli.js" }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], - - "legacy-javascript": ["legacy-javascript@0.0.1", "", {}, "sha512-lPyntS4/aS7jpuvOlitZDFifBCb4W8L/3QU0PLbUTUj+zYah8rfVjYic88yG7ZKTxhS5h9iz7duT8oUXKszLhg=="], - - "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], - - "lighthouse": ["lighthouse@12.8.2", "", { "dependencies": { "@paulirish/trace_engine": "0.0.59", "@sentry/node": "^9.28.1", "axe-core": "^4.10.3", "chrome-launcher": "^1.2.0", "configstore": "^7.0.0", "csp_evaluator": "1.1.5", "devtools-protocol": "0.0.1507524", "enquirer": "^2.3.6", "http-link-header": "^1.1.1", "intl-messageformat": "^10.5.3", "jpeg-js": "^0.4.4", "js-library-detector": "^6.7.0", "lighthouse-logger": "^2.0.2", "lighthouse-stack-packs": "1.12.2", "lodash-es": "^4.17.21", "lookup-closest-locale": "6.2.0", "metaviewport-parser": "0.3.0", "open": "^8.4.0", "parse-cache-control": "1.0.1", "puppeteer-core": "^24.17.1", "robots-parser": "^3.0.1", "speedline-core": "^1.4.3", "third-party-web": "^0.27.0", "tldts-icann": "^7.0.12", "ws": "^7.0.0", "yargs": "^17.3.1", "yargs-parser": "^21.0.0" }, "bin": { "chrome-debug": "core/scripts/manual-chrome-launcher.js", "lighthouse": "cli/index.js", "smokehouse": "cli/test/smokehouse/frontends/smokehouse-bin.js" } }, "sha512-+5SKYzVaTFj22MgoYDPNrP9tlD2/Ay7j3SxPSFD9FpPyVxGr4UtOQGKyrdZ7wCmcnBaFk0mCkPfARU3CsE0nvA=="], - - "lighthouse-logger": ["lighthouse-logger@2.0.2", "", { "dependencies": { "debug": "^4.4.1", "marky": "^1.2.2" } }, "sha512-vWl2+u5jgOQuZR55Z1WM0XDdrJT6mzMP8zHUct7xTlWhuQs+eV0g+QL0RQdFjT54zVmbhLCP8vIVpy1wGn/gCg=="], - - "lighthouse-stack-packs": ["lighthouse-stack-packs@1.12.2", "", {}, "sha512-Ug8feS/A+92TMTCK6yHYLwaFMuelK/hAKRMdldYkMNwv+d9PtWxjXEg6rwKtsUXTADajhdrhXyuNCJ5/sfmPFw=="], - - "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], - - "locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], - - "lodash-es": ["lodash-es@4.17.23", "", {}, "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg=="], - - "lookup-closest-locale": ["lookup-closest-locale@6.2.0", "", {}, "sha512-/c2kL+Vnp1jnV6K6RpDTHK3dgg0Tu2VVp+elEiJpjfS1UyY7AjOYHohRug6wT0OpoX2qFgNORndE9RqesfVxWQ=="], - - "lower-case": ["lower-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg=="], - - "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], - - "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], - - "makeerror": ["makeerror@1.0.12", "", { "dependencies": { "tmpl": "1.0.5" } }, "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg=="], - - "marky": ["marky@1.3.0", "", {}, "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ=="], - - "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - - "memize": ["memize@2.1.1", "", {}, "sha512-8Nl+i9S5D6KXnruM03Jgjb+LwSupvR13WBr4hJegaaEyobvowCVupi79y2WSiWvO1mzBWxPwEYE5feCe8vyA5w=="], - - "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], - - "metaviewport-parser": ["metaviewport-parser@0.3.0", "", {}, "sha512-EoYJ8xfjQ6kpe9VbVHvZTZHiOl4HL1Z18CrZ+qahvLXT7ZO4YTC2JMyt5FaUp9JJp6J4Ybb/z7IsCXZt86/QkQ=="], - - "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - - "mime": ["mime@3.0.0", "", { "bin": "cli.js" }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], - - "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - - "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - - "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], - - "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], - - "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="], - - "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "napi-postinstall": ["napi-postinstall@0.3.4", "", { "bin": "lib/cli.js" }, "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ=="], - - "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - - "netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="], - - "no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="], - - "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" } }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], - - "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="], - - "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], - - "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], - - "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], - - "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], - - "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], - - "open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], - - "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], - - "p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], - - "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], - - "pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="], - - "pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="], - - "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], - - "param-case": ["param-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A=="], - - "parse-cache-control": ["parse-cache-control@1.0.1", "", {}, "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg=="], - - "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], - - "pascal-case": ["pascal-case@3.1.2", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g=="], - - "path-case": ["path-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg=="], - - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], - - "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], - - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], - - "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - - "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], - - "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], - - "pg-protocol": ["pg-protocol@1.11.0", "", {}, "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g=="], - - "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], - - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - - "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], - - "pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="], - - "playwright": ["playwright@1.58.2", "", { "dependencies": { "playwright-core": "1.58.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": "cli.js" }, "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A=="], - - "playwright-core": ["playwright-core@1.58.2", "", { "bin": "cli.js" }, "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg=="], - - "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], - - "postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="], - - "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], - - "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], - - "pretty-format": ["pretty-format@30.2.0", "", { "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" } }, "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA=="], - - "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], - - "proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="], - - "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], - - "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], - - "puppeteer-core": ["puppeteer-core@24.37.2", "", { "dependencies": { "@puppeteer/browsers": "2.12.0", "chromium-bidi": "13.1.1", "debug": "^4.4.3", "devtools-protocol": "0.0.1566079", "typed-query-selector": "^2.12.0", "webdriver-bidi-protocol": "0.4.0", "ws": "^8.19.0" } }, "sha512-nN8qwE3TGF2vA/+xemPxbesntTuqD9vCGOiZL2uh8HES3pPzLX20MyQjB42dH2rhQ3W3TljZ4ZaKZ0yX/abQuw=="], - - "pure-rand": ["pure-rand@7.0.1", "", {}, "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ=="], - - "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], - - "remove-accents": ["remove-accents@0.5.0", "", {}, "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A=="], - - "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], - - "require-in-the-middle": ["require-in-the-middle@7.5.2", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3", "resolve": "^1.22.8" } }, "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ=="], - - "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], - - "resolve-cwd": ["resolve-cwd@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg=="], - - "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], - - "robots-parser": ["robots-parser@3.0.1", "", {}, "sha512-s+pyvQeIKIZ0dx5iJiQk1tPLJAWln39+MI5jtM8wnyws+G5azk+dMnMX0qfbqNetKKNgcWWOdi0sfm+FbQbgdQ=="], - - "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], - - "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], - - "semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - - "sentence-case": ["sentence-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", "upper-case-first": "^2.0.2" } }, "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg=="], - - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "shimmer": ["shimmer@1.2.1", "", {}, "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw=="], - - "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - - "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], - - "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], - - "snake-case": ["snake-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg=="], - - "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="], - - "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], - - "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - - "source-map-support": ["source-map-support@0.5.13", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w=="], - - "speedline-core": ["speedline-core@1.4.3", "", { "dependencies": { "@types/node": "*", "image-ssim": "^0.2.0", "jpeg-js": "^0.4.1" } }, "sha512-DI7/OuAUD+GMpR6dmu8lliO2Wg5zfeh+/xsdyJZCzd8o5JgFUjCeLsBDuZjIQJdwXS3J0L/uZYrELKYqx+PXog=="], - - "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], - - "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], - - "streamx": ["streamx@2.23.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg=="], - - "string-length": ["string-length@4.0.2", "", { "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" } }, "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ=="], - - "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-bom": ["strip-bom@4.0.0", "", {}, "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w=="], - - "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], - - "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - - "stubborn-fs": ["stubborn-fs@2.0.0", "", { "dependencies": { "stubborn-utils": "^1.0.1" } }, "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA=="], - - "stubborn-utils": ["stubborn-utils@1.0.2", "", {}, "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg=="], - - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - - "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], - - "synckit": ["synckit@0.11.12", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ=="], - - "tannin": ["tannin@1.2.0", "", { "dependencies": { "@tannin/plural-forms": "^1.1.0" } }, "sha512-U7GgX/RcSeUETbV7gYgoz8PD7Ni4y95pgIP/Z6ayI3CfhSujwKEBlGFTCRN+Aqnuyf4AN2yHL+L8x+TCGjb9uA=="], - - "tar-fs": ["tar-fs@3.1.1", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg=="], - - "tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="], - - "test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="], - - "text-decoder": ["text-decoder@1.2.3", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA=="], - - "third-party-web": ["third-party-web@0.27.0", "", {}, "sha512-h0JYX+dO2Zr3abCQpS6/uFjujaOjA1DyDzGQ41+oFn9VW/ARiq9g5ln7qEP9+BTzDpOMyIfsfj4OvfgXAsMUSA=="], - - "tldts-core": ["tldts-core@7.0.22", "", {}, "sha512-KgbTDC5wzlL6j/x6np6wCnDSMUq4kucHNm00KXPbfNzmllCmtmvtykJHfmgdHntwIeupW04y8s1N/43S1PkQDw=="], - - "tldts-icann": ["tldts-icann@7.0.22", "", { "dependencies": { "tldts-core": "^7.0.22" } }, "sha512-Wb5HEhrSy+zJtdJ6gop7ZNQ/Iacz/0c8t+6Kp1QoT84VRfc0TfPJLrb8f6YuRvCUOVjU889KJlPcG+5glVX8GQ=="], - - "tmpl": ["tmpl@1.0.5", "", {}, "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="], - - "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - - "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], - - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "type-detect": ["type-detect@4.0.8", "", {}, "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="], - - "type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], - - "typed-query-selector": ["typed-query-selector@2.12.0", "", {}, "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg=="], - - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - - "unrs-resolver": ["unrs-resolver@1.11.1", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.11.1", "@unrs/resolver-binding-android-arm64": "1.11.1", "@unrs/resolver-binding-darwin-arm64": "1.11.1", "@unrs/resolver-binding-darwin-x64": "1.11.1", "@unrs/resolver-binding-freebsd-x64": "1.11.1", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-musl": "1.11.1", "@unrs/resolver-binding-wasm32-wasi": "1.11.1", "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="], - - "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": "cli.js" }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], - - "upper-case": ["upper-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg=="], - - "upper-case-first": ["upper-case-first@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg=="], - - "v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="], - - "walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="], - - "web-vitals": ["web-vitals@4.2.4", "", {}, "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="], - - "webdriver-bidi-protocol": ["webdriver-bidi-protocol@0.4.0", "", {}, "sha512-U9VIlNRrq94d1xxR9JrCEAx5Gv/2W7ERSv8oWRoNe/QYbfccS0V3h/H6qeNeCRJxXGMhhnkqvwNrvPAYeuP9VA=="], - - "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], - - "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], - - "when-exit": ["when-exit@2.1.5", "", {}, "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg=="], - - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - - "write-file-atomic": ["write-file-atomic@5.0.1", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" } }, "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw=="], - - "ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], - - "xdg-basedir": ["xdg-basedir@5.1.0", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="], - - "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], - - "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], - - "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - - "yargs": ["yargs@17.7.2", "", { "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" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], - - "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], - - "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], - - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - - "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@babel/core/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - - "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], - - "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - - "@istanbuljs/load-nyc-config/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], - - "@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="], - - "@opentelemetry/instrumentation/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - - "@opentelemetry/instrumentation-http/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="], - - "@opentelemetry/instrumentation-http/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - - "@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="], - - "@opentelemetry/sdk-trace-base/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="], - - "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - - "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "dot-prop/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], - - "execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], - - "execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - - "import-in-the-middle/cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="], - - "istanbul-lib-instrument/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - - "jest-haste-map/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - - "jest-snapshot/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - - "jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], - - "lighthouse/axe-core": ["axe-core@4.11.1", "", {}, "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A=="], - - "lighthouse/devtools-protocol": ["devtools-protocol@0.0.1507524", "", {}, "sha512-OjaNE7qpk6GRTXtqQjAE5bGx6+c4F1zZH0YXtpZQLM92HNXx4zMAaqlKhP4T52DosG6hDW8gPMNhGOF8xbwk/w=="], - - "lighthouse/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], - - "make-dir/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - - "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - - "p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], - - "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - - "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], - - "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], - - "string-length/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "test-exclude/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - - "test-exclude/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - - "wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - - "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - - "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "string-length/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "test-exclude/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - - "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - } -} diff --git a/tests/playwright/global-setup.ts b/tests/playwright/global-setup.ts new file mode 100644 index 0000000..01ba4e6 --- /dev/null +++ b/tests/playwright/global-setup.ts @@ -0,0 +1,11 @@ +import path from 'path'; +import dotenv from 'dotenv'; +import { ADMIN_USER, CONTRIB_USER } from './utils/users'; +import { ensureUser } from './utils/wp-cli'; + +dotenv.config({ path: path.resolve(__dirname, '.env'), quiet: true }); + +export default function globalSetup(): void { + ensureUser(ADMIN_USER); + ensureUser(CONTRIB_USER); +} diff --git a/tests/playwright/package-lock.json b/tests/playwright/package-lock.json deleted file mode 100644 index 0c3320e..0000000 --- a/tests/playwright/package-lock.json +++ /dev/null @@ -1,7810 +0,0 @@ -{ - "name": "playwright", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "playwright", - "version": "1.0.0", - "license": "ISC", - "devDependencies": { - "@axe-core/playwright": "^4.11.3", - "@playwright/test": "^1.58.1", - "@types/node": "^20.17.10", - "@wordpress/e2e-test-utils": "^11.34.0", - "@wordpress/e2e-test-utils-playwright": "^1.39.0", - "dotenv": "^17.3.1" - } - }, - "node_modules/@axe-core/playwright": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.13.0.tgz", - "integrity": "sha512-6YLx+kxXu5GJceG4ozFg+33a2EMTdjYwWGloJ3sb9Kta5pp+ZNS53uxGVog5JetIY8s++P5UrtX+cri+u0VAVg==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "axe-core": "~4.13.0" - }, - "peerDependencies": { - "playwright-core": ">= 1.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@emnapi/core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", - "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@formatjs/ecma402-abstract": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz", - "integrity": "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@formatjs/fast-memoize": "2.2.7", - "@formatjs/intl-localematcher": "0.6.2", - "decimal.js": "^10.4.3", - "tslib": "^2.8.0" - } - }, - "node_modules/@formatjs/fast-memoize": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz", - "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz", - "integrity": "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@formatjs/ecma402-abstract": "2.3.6", - "@formatjs/icu-skeleton-parser": "1.8.16", - "tslib": "^2.8.0" - } - }, - "node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.8.16", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz", - "integrity": "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@formatjs/ecma402-abstract": "2.3.6", - "tslib": "^2.8.0" - } - }, - "node_modules/@formatjs/intl-localematcher": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.2.tgz", - "integrity": "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "peer": 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/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", - "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/core": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", - "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/console": "30.2.0", - "@jest/pattern": "30.0.1", - "@jest/reporters": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-changed-files": "30.2.0", - "jest-config": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-resolve-dependencies": "30.2.0", - "jest-runner": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "jest-watcher": "30.2.0", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/diff-sequences": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", - "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/environment": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", - "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-mock": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "expect": "30.2.0", - "jest-snapshot": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", - "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/get-type": "30.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", - "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "30.2.0", - "@sinonjs/fake-timers": "^13.0.0", - "@types/node": "*", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", - "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/types": "30.2.0", - "jest-mock": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/pattern": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*", - "jest-regex-util": "30.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", - "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@jridgewell/trace-mapping": "^0.3.25", - "@types/node": "*", - "chalk": "^4.1.2", - "collect-v8-coverage": "^1.0.2", - "exit-x": "^0.2.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^5.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", - "slash": "^3.0.0", - "string-length": "^4.0.2", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/snapshot-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", - "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "natural-compare": "^1.4.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", - "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "callsites": "^3.1.0", - "graceful-fs": "^4.2.11" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", - "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/console": "30.2.0", - "@jest/types": "30.2.0", - "@types/istanbul-lib-coverage": "^2.0.6", - "collect-v8-coverage": "^1.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", - "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/test-result": "30.2.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", - "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/types": "30.2.0", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.1", - "chalk": "^4.1.2", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "micromatch": "^4.0.8", - "pirates": "^4.0.7", - "slash": "^3.0.0", - "write-file-atomic": "^5.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/types": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", - "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" - } - }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@opentelemetry/api-logs": { - "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz", - "integrity": "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api": "^1.3.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/context-async-hooks": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz", - "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/core": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.28.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/core/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/instrumentation": { - "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz", - "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.57.2", - "@types/shimmer": "^1.2.0", - "import-in-the-middle": "^1.8.1", - "require-in-the-middle": "^7.1.1", - "semver": "^7.5.2", - "shimmer": "^1.2.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-amqplib": { - "version": "0.46.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.46.1.tgz", - "integrity": "sha512-AyXVnlCf/xV3K/rNumzKxZqsULyITJH6OVLiW6730JPRqWA7Zc9bvYoVNpN6iOpTU8CasH34SU/ksVJmObFibQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.57.1", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-connect": { - "version": "0.43.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.43.1.tgz", - "integrity": "sha512-ht7YGWQuV5BopMcw5Q2hXn3I8eG8TH0J/kc/GMcW4CuNTgiP6wCu44BOnucJWL3CmFWaRHI//vWyAhaC8BwePw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.57.1", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/connect": "3.4.38" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-dataloader": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.16.1.tgz", - "integrity": "sha512-K/qU4CjnzOpNkkKO4DfCLSQshejRNAJtd4esgigo/50nxCB6XCyi1dhAblUHM9jG5dRm8eu0FB+t87nIo99LYQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.57.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-express": { - "version": "0.47.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.47.1.tgz", - "integrity": "sha512-QNXPTWteDclR2B4pDFpz0TNghgB33UMjUt14B+BZPmtH1MwUFAfLHBaP5If0Z5NZC+jaH8oF2glgYjrmhZWmSw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.57.1", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-fs": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.19.1.tgz", - "integrity": "sha512-6g0FhB3B9UobAR60BGTcXg4IHZ6aaYJzp0Ki5FhnxyAPt8Ns+9SSvgcrnsN2eGmk3RWG5vYycUGOEApycQL24A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.57.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-generic-pool": { - "version": "0.43.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.43.1.tgz", - "integrity": "sha512-M6qGYsp1cURtvVLGDrPPZemMFEbuMmCXgQYTReC/IbimV5sGrLBjB+/hANUpRZjX67nGLdKSVLZuQQAiNz+sww==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.57.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-graphql": { - "version": "0.47.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.47.1.tgz", - "integrity": "sha512-EGQRWMGqwiuVma8ZLAZnExQ7sBvbOx0N/AE/nlafISPs8S+QtXX+Viy6dcQwVWwYHQPAcuY3bFt3xgoAwb4ZNQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.57.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-hapi": { - "version": "0.45.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.45.2.tgz", - "integrity": "sha512-7Ehow/7Wp3aoyCrZwQpU7a2CnoMq0XhIcioFuKjBb0PLYfBfmTsFTUyatlHu0fRxhwcRsSQRTvEhmZu8CppBpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.57.1", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-http": { - "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.57.2.tgz", - "integrity": "sha512-1Uz5iJ9ZAlFOiPuwYg29Bf7bJJc/GeoeJIFKJYQf67nTVKFe8RHbEtxgkOmK4UGZNHKXcpW4P8cWBYzBn1USpg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.30.1", - "@opentelemetry/instrumentation": "0.57.2", - "@opentelemetry/semantic-conventions": "1.28.0", - "forwarded-parse": "2.1.2", - "semver": "^7.5.2" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/instrumentation-http/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@opentelemetry/instrumentation-ioredis": { - "version": "0.47.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.47.1.tgz", - "integrity": "sha512-OtFGSN+kgk/aoKgdkKQnBsQFDiG8WdCxu+UrHr0bXScdAmtSzLSraLo7wFIb25RVHfRWvzI5kZomqJYEg/l1iA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.57.1", - "@opentelemetry/redis-common": "^0.36.2", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-kafkajs": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.7.1.tgz", - "integrity": "sha512-OtjaKs8H7oysfErajdYr1yuWSjMAectT7Dwr+axIoZqT9lmEOkD/H/3rgAs8h/NIuEi2imSXD+vL4MZtOuJfqQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.57.1", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-knex": { - "version": "0.44.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.44.1.tgz", - "integrity": "sha512-U4dQxkNhvPexffjEmGwCq68FuftFK15JgUF05y/HlK3M6W/G2iEaACIfXdSnwVNe9Qh0sPfw8LbOPxrWzGWGMQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.57.1", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-koa": { - "version": "0.47.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.47.1.tgz", - "integrity": "sha512-l/c+Z9F86cOiPJUllUCt09v+kICKvT+Vg1vOAJHtHPsJIzurGayucfCMq2acd/A/yxeNWunl9d9eqZ0G+XiI6A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.57.1", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-lru-memoizer": { - "version": "0.44.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.44.1.tgz", - "integrity": "sha512-5MPkYCvG2yw7WONEjYj5lr5JFehTobW7wX+ZUFy81oF2lr9IPfZk9qO+FTaM0bGEiymwfLwKe6jE15nHn1nmHg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.57.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-mongodb": { - "version": "0.52.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.52.0.tgz", - "integrity": "sha512-1xmAqOtRUQGR7QfJFfGV/M2kC7wmI2WgZdpru8hJl3S0r4hW0n3OQpEHlSGXJAaNFyvT+ilnwkT+g5L4ljHR6g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.57.1", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-mongoose": { - "version": "0.46.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.46.1.tgz", - "integrity": "sha512-3kINtW1LUTPkiXFRSSBmva1SXzS/72we/jL22N+BnF3DFcoewkdkHPYOIdAAk9gSicJ4d5Ojtt1/HeibEc5OQg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.57.1", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-mysql": { - "version": "0.45.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.45.1.tgz", - "integrity": "sha512-TKp4hQ8iKQsY7vnp/j0yJJ4ZsP109Ht6l4RHTj0lNEG1TfgTrIH5vJMbgmoYXWzNHAqBH2e7fncN12p3BP8LFg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.57.1", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/mysql": "2.15.26" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-mysql2": { - "version": "0.45.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.45.2.tgz", - "integrity": "sha512-h6Ad60FjCYdJZ5DTz1Lk2VmQsShiViKe0G7sYikb0GHI0NVvApp2XQNRHNjEMz87roFttGPLHOYVPlfy+yVIhQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.57.1", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@opentelemetry/sql-common": "^0.40.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-pg": { - "version": "0.51.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.51.1.tgz", - "integrity": "sha512-QxgjSrxyWZc7Vk+qGSfsejPVFL1AgAJdSBMYZdDUbwg730D09ub3PXScB9d04vIqPriZ+0dqzjmQx0yWKiCi2Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^1.26.0", - "@opentelemetry/instrumentation": "^0.57.1", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@opentelemetry/sql-common": "^0.40.1", - "@types/pg": "8.6.1", - "@types/pg-pool": "2.0.6" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-redis-4": { - "version": "0.46.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.46.1.tgz", - "integrity": "sha512-UMqleEoabYMsWoTkqyt9WAzXwZ4BlFZHO40wr3d5ZvtjKCHlD4YXLm+6OLCeIi/HkX7EXvQaz8gtAwkwwSEvcQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.57.1", - "@opentelemetry/redis-common": "^0.36.2", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-tedious": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.18.1.tgz", - "integrity": "sha512-5Cuy/nj0HBaH+ZJ4leuD7RjgvA844aY2WW+B5uLcWtxGjRZl3MNLuxnNg5DYWZNPO+NafSSnra0q49KWAHsKBg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.57.1", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/tedious": "^4.0.14" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-undici": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.10.1.tgz", - "integrity": "sha512-rkOGikPEyRpMCmNu9AQuV5dtRlDmJp2dK5sw8roVshAGoB6hH/3QjDtRhdwd75SsJwgynWUNRUYe0wAkTo16tQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.57.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.7.0" - } - }, - "node_modules/@opentelemetry/instrumentation/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@opentelemetry/redis-common": { - "version": "0.36.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz", - "integrity": "sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/resources": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.30.1", - "@opentelemetry/semantic-conventions": "1.28.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", - "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.30.1", - "@opentelemetry/resources": "1.30.1", - "@opentelemetry/semantic-conventions": "1.28.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.39.0.tgz", - "integrity": "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/sql-common": { - "version": "0.40.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.40.1.tgz", - "integrity": "sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^1.1.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0" - } - }, - "node_modules/@paulirish/trace_engine": { - "version": "0.0.59", - "resolved": "https://registry.npmjs.org/@paulirish/trace_engine/-/trace_engine-0.0.59.tgz", - "integrity": "sha512-439NUzQGmH+9Y017/xCchBP9571J4bzhpcNhrxorf7r37wcyJZkgUfrUsRL3xl+JDcZ6ORhoFCzCw98c6S3YHw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "legacy-javascript": "latest", - "third-party-web": "latest" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@playwright/test": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", - "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright": "1.58.2" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@prisma/instrumentation": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-6.11.1.tgz", - "integrity": "sha512-mrZOev24EDhnefmnZX7WVVT7v+r9LttPRqf54ONvj6re4XMF7wFTpK2tLJi4XHB7fFp/6xhYbgRel8YV7gQiyA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.8" - } - }, - "node_modules/@puppeteer/browsers": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.12.0.tgz", - "integrity": "sha512-Xuq42yxcQJ54ti8ZHNzF5snFvtpgXzNToJ1bXUGQRaiO8t+B6UM8sTUJfvV+AJnqtkJU/7hdy6nbKyA12aHtRw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "debug": "^4.4.3", - "extract-zip": "^2.0.1", - "progress": "^2.0.3", - "proxy-agent": "^6.5.0", - "semver": "^7.7.3", - "tar-fs": "^3.1.1", - "yargs": "^17.7.2" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@puppeteer/browsers/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/core": { - "version": "9.47.1", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.47.1.tgz", - "integrity": "sha512-KX62+qIt4xgy8eHKHiikfhz2p5fOciXd0Cl+dNzhgPFq8klq4MGMNaf148GB3M/vBqP4nw/eFvRMAayFCgdRQw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@sentry/node": { - "version": "9.47.1", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-9.47.1.tgz", - "integrity": "sha512-CDbkasBz3fnWRKSFs6mmaRepM2pa+tbZkrqhPWifFfIkJDidtVW40p6OnquTvPXyPAszCnDZRnZT14xyvNmKPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^1.30.1", - "@opentelemetry/core": "^1.30.1", - "@opentelemetry/instrumentation": "^0.57.2", - "@opentelemetry/instrumentation-amqplib": "^0.46.1", - "@opentelemetry/instrumentation-connect": "0.43.1", - "@opentelemetry/instrumentation-dataloader": "0.16.1", - "@opentelemetry/instrumentation-express": "0.47.1", - "@opentelemetry/instrumentation-fs": "0.19.1", - "@opentelemetry/instrumentation-generic-pool": "0.43.1", - "@opentelemetry/instrumentation-graphql": "0.47.1", - "@opentelemetry/instrumentation-hapi": "0.45.2", - "@opentelemetry/instrumentation-http": "0.57.2", - "@opentelemetry/instrumentation-ioredis": "0.47.1", - "@opentelemetry/instrumentation-kafkajs": "0.7.1", - "@opentelemetry/instrumentation-knex": "0.44.1", - "@opentelemetry/instrumentation-koa": "0.47.1", - "@opentelemetry/instrumentation-lru-memoizer": "0.44.1", - "@opentelemetry/instrumentation-mongodb": "0.52.0", - "@opentelemetry/instrumentation-mongoose": "0.46.1", - "@opentelemetry/instrumentation-mysql": "0.45.1", - "@opentelemetry/instrumentation-mysql2": "0.45.2", - "@opentelemetry/instrumentation-pg": "0.51.1", - "@opentelemetry/instrumentation-redis-4": "0.46.1", - "@opentelemetry/instrumentation-tedious": "0.18.1", - "@opentelemetry/instrumentation-undici": "0.10.1", - "@opentelemetry/resources": "^1.30.1", - "@opentelemetry/sdk-trace-base": "^1.30.1", - "@opentelemetry/semantic-conventions": "^1.34.0", - "@prisma/instrumentation": "6.11.1", - "@sentry/core": "9.47.1", - "@sentry/node-core": "9.47.1", - "@sentry/opentelemetry": "9.47.1", - "import-in-the-middle": "^1.14.2", - "minimatch": "^9.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@sentry/node-core": { - "version": "9.47.1", - "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-9.47.1.tgz", - "integrity": "sha512-7TEOiCGkyShJ8CKtsri9lbgMCbB+qNts2Xq37itiMPN2m+lIukK3OX//L8DC5nfKYZlgikrefS63/vJtm669hQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sentry/core": "9.47.1", - "@sentry/opentelemetry": "9.47.1", - "import-in-the-middle": "^1.14.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.0.0", - "@opentelemetry/core": "^1.30.1 || ^2.0.0", - "@opentelemetry/instrumentation": ">=0.57.1 <1", - "@opentelemetry/resources": "^1.30.1 || ^2.0.0", - "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.0.0", - "@opentelemetry/semantic-conventions": "^1.34.0" - } - }, - "node_modules/@sentry/opentelemetry": { - "version": "9.47.1", - "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-9.47.1.tgz", - "integrity": "sha512-STtFpjF7lwzeoedDJV+5XA6P89BfmFwFftmHSGSe3UTI8z8IoiR5yB6X2vCjSPvXlfeOs13qCNNCEZyznxM8Xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sentry/core": "9.47.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.0.0", - "@opentelemetry/core": "^1.30.1 || ^2.0.0", - "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.0.0", - "@opentelemetry/semantic-conventions": "^1.34.0" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, - "node_modules/@tannin/compile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@tannin/compile/-/compile-1.1.0.tgz", - "integrity": "sha512-n8m9eNDfoNZoxdvWiTfW/hSPhehzLJ3zW7f8E7oT6mCROoMNWCB4TYtv041+2FMAxweiE0j7i1jubQU4MEC/Gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tannin/evaluate": "^1.2.0", - "@tannin/postfix": "^1.1.0" - } - }, - "node_modules/@tannin/evaluate": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@tannin/evaluate/-/evaluate-1.2.0.tgz", - "integrity": "sha512-3ioXvNowbO/wSrxsDG5DKIMxC81P0QrQTYai8zFNY+umuoHWRPbQ/TuuDEOju9E+jQDXmj6yI5GyejNuh8I+eg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tannin/plural-forms": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@tannin/plural-forms/-/plural-forms-1.1.0.tgz", - "integrity": "sha512-xl9R2mDZO/qiHam1AgMnAES6IKIg7OBhcXqy6eDsRCdXuxAFPcjrej9HMjyCLE0DJ/8cHf0i5OQTstuBRhpbHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tannin/compile": "^1.1.0" - } - }, - "node_modules/@tannin/postfix": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@tannin/postfix/-/postfix-1.1.0.tgz", - "integrity": "sha512-oocsqY7g0cR+Gur5jRQLSrX2OtpMLMse1I10JQBm8CdGMrDkh1Mg2gjsiquMHRtBs4Qwu5wgEp5GgIYHk4SNPw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tannin/sprintf": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@tannin/sprintf/-/sprintf-1.3.3.tgz", - "integrity": "sha512-RwARl+hFwhzy0tg9atWcchLFvoQiOh4rrP7uG2N5E4W80BPCUX0ElcUR9St43fxB9EfjsW2df9Qp+UsTbvQDjA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/mysql": { - "version": "2.15.26", - "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.26.tgz", - "integrity": "sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "20.19.32", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.32.tgz", - "integrity": "sha512-Ez8QE4DMfhjjTsES9K2dwfV258qBui7qxUsoaixZDiTzbde4U12e1pXGNu/ECsUIOi5/zoCxAQxIhQnaUQ2VvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/pg": { - "version": "8.6.1", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", - "integrity": "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" - } - }, - "node_modules/@types/pg-pool": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.6.tgz", - "integrity": "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/pg": "*" - } - }, - "node_modules/@types/shimmer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", - "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/tedious": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", - "integrity": "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@wordpress/api-fetch": { - "version": "7.39.0", - "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-7.39.0.tgz", - "integrity": "sha512-H6ysbEgDYuIDhvRje1iTyC9r4bEOY9AT0fRmNEr6fSG/furznt4XNtoflzh4Q4DeaC2flTQ9hn46JgcmW3IXJA==", - "dev": true, - "license": "GPL-2.0-or-later", - "dependencies": { - "@wordpress/i18n": "^6.12.0", - "@wordpress/url": "^4.39.0" - }, - "engines": { - "node": ">=18.12.0", - "npm": ">=8.19.2" - } - }, - "node_modules/@wordpress/e2e-test-utils": { - "version": "11.34.0", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-11.34.0.tgz", - "integrity": "sha512-fDCxSTSzOtuDYlfme/LBFZIiRv++y35ykymkYQD5mbd6yFz9hotZfVUzqtXMY3x2kttgBwp28/U8geAt7ym8mQ==", - "deprecated": "This package has been deprecated in favor of @wordpress/e2e-test-utils-playwright", - "dev": true, - "license": "GPL-2.0-or-later", - "dependencies": { - "@wordpress/api-fetch": "^7.34.0", - "@wordpress/keycodes": "^4.34.0", - "@wordpress/url": "^4.34.0", - "change-case": "^4.1.2", - "form-data": "^4.0.0", - "node-fetch": "2.7.0" - }, - "engines": { - "node": ">=18.12.0", - "npm": ">=8.19.2" - }, - "peerDependencies": { - "jest": ">=29", - "puppeteer-core": ">=23" - } - }, - "node_modules/@wordpress/e2e-test-utils-playwright": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-1.39.0.tgz", - "integrity": "sha512-ok008Rd8URqNQCzx/9bClC+gk7XxVV+xm5rOJjb6A4EHR8tVlynJEcE4gN0C5qCDIeOdPLbGW8ljNm2DxlelwQ==", - "dev": true, - "license": "GPL-2.0-or-later", - "dependencies": { - "change-case": "^4.1.2", - "get-port": "^5.1.1", - "lighthouse": "^12.2.2", - "mime": "^3.0.0", - "web-vitals": "^4.2.1" - }, - "engines": { - "node": ">=18.12.0", - "npm": ">=8.19.2" - }, - "peerDependencies": { - "@playwright/test": ">=1", - "@types/node": "^20.17.10" - } - }, - "node_modules/@wordpress/hooks": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-4.39.0.tgz", - "integrity": "sha512-FTKdGF5jHHmC8GSO6/ATQqh1IFQeDwapRtlp7t4VaTGwZtX+uzawgq/7QDIhFi3cfg9hNsFF0CSFp/Ul3nEeUA==", - "dev": true, - "license": "GPL-2.0-or-later", - "engines": { - "node": ">=18.12.0", - "npm": ">=8.19.2" - } - }, - "node_modules/@wordpress/i18n": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.12.0.tgz", - "integrity": "sha512-KMleg8p/HtnoX1d/WoRDI51VTZsA4RGNUvBYn+Cc3avaeeNKROb91+viMcOc8NHuLplEzl7zH9/mrOSs9aY3rg==", - "dev": true, - "license": "GPL-2.0-or-later", - "dependencies": { - "@tannin/sprintf": "^1.3.2", - "@wordpress/hooks": "^4.39.0", - "gettext-parser": "^1.3.1", - "memize": "^2.1.0", - "tannin": "^1.2.0" - }, - "bin": { - "pot-to-php": "tools/pot-to-php.js" - }, - "engines": { - "node": ">=18.12.0", - "npm": ">=8.19.2" - } - }, - "node_modules/@wordpress/keycodes": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-4.39.0.tgz", - "integrity": "sha512-RN7Py7vvvmBOGuRM8X4hHOvXXG57jWDW9pIXUnVGc33EvTrwtnr16f8Xt4nKWs8L/UNUdrrAtA2HAeqRkdxr+A==", - "dev": true, - "license": "GPL-2.0-or-later", - "dependencies": { - "@wordpress/i18n": "^6.12.0" - }, - "engines": { - "node": ">=18.12.0", - "npm": ">=8.19.2" - } - }, - "node_modules/@wordpress/url": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-4.39.0.tgz", - "integrity": "sha512-pWOtqLcApB1EZnD2am/vMHxkCLgHzpysUypP8N8jz+USdLyOKy7vaY3v94+HAL1M9HBoT9VpllWEg+LxsO/eqQ==", - "dev": true, - "license": "GPL-2.0-or-later", - "dependencies": { - "remove-accents": "^0.5.0" - }, - "engines": { - "node": ">=18.12.0", - "npm": ">=8.19.2" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/atomically": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.0.tgz", - "integrity": "sha512-+gDffFXRW6sl/HCwbta7zK4uNqbPjv4YJEAdz7Vu+FLQHe77eZ4bvbJGi4hE0QPeJlMYMA3piXEr1UL3dAwx7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "stubborn-fs": "^2.0.0", - "when-exit": "^2.1.4" - } - }, - "node_modules/axe-core": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.13.0.tgz", - "integrity": "sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==", - "dev": true, - "license": "MPL-2.0", - "engines": { - "node": ">=4" - } - }, - "node_modules/b4a": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", - "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", - "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "react-native-b4a": "*" - }, - "peerDependenciesMeta": { - "react-native-b4a": { - "optional": true - } - } - }, - "node_modules/babel-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", - "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/transform": "30.2.0", - "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.2.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", - "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "workspaces": [ - "test/babel-8" - ], - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", - "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/babel__core": "^7.20.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/babel-preset-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", - "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "babel-plugin-jest-hoist": "30.2.0", - "babel-preset-current-node-syntax": "^1.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-beta.1" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/bare-events": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", - "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", - "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "bare-abort-controller": "*" - }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - } - } - }, - "node_modules/bare-fs": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.3.tgz", - "integrity": "sha512-9+kwVx8QYvt3hPWnmb19tPnh38c6Nihz8Lx3t0g9+4GoIf3/fTgYwM4Z6NxgI+B9elLQA7mLE9PpqcWtOMRDiQ==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4", - "bare-url": "^2.2.2", - "fast-fifo": "^1.3.2" - }, - "engines": { - "bare": ">=1.16.0" - }, - "peerDependencies": { - "bare-buffer": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } - } - }, - "node_modules/bare-os": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", - "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "engines": { - "bare": ">=1.14.0" - } - }, - "node_modules/bare-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", - "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-os": "^3.0.1" - } - }, - "node_modules/bare-stream": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", - "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "streamx": "^2.21.0" - }, - "peerDependencies": { - "bare-buffer": "*", - "bare-events": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - }, - "bare-events": { - "optional": true - } - } - }, - "node_modules/bare-url": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", - "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-path": "^3.0.0" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/basic-ftp": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", - "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "node-int64": "^0.4.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, - "license": "MIT", - "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, - "license": "MIT", - "peer": true - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001769", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", - "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0", - "peer": true - }, - "node_modules/capital-case": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", - "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3", - "upper-case-first": "^2.0.2" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "peer": 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/change-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", - "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "camel-case": "^4.1.2", - "capital-case": "^1.0.4", - "constant-case": "^3.0.4", - "dot-case": "^3.0.4", - "header-case": "^2.0.4", - "no-case": "^3.0.4", - "param-case": "^3.0.4", - "pascal-case": "^3.1.2", - "path-case": "^3.0.4", - "sentence-case": "^3.0.4", - "snake-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/chrome-launcher": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.2.1.tgz", - "integrity": "sha512-qmFR5PLMzHyuNJHwOloHPAHhbaNglkfeV/xDtt5b7xiFFyU1I+AZZX0PYseMuhenJSSirgxELYIbswcoc+5H4A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.cjs" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/chromium-bidi": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-13.1.1.tgz", - "integrity": "sha512-zB9MpoPd7VJwjowQqiW3FKOvQwffFMjQ8Iejp5ZW+sJaKLRhZX1sTxzl3Zt22TDB4zP0OOqs8lRoY7eAW5geyQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "mitt": "^3.0.1", - "zod": "^3.24.1" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "node_modules/ci-info": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", - "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", - "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", - "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/configstore": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-7.1.0.tgz", - "integrity": "sha512-N4oog6YJWbR9kGyXvS7jEykLDXIE2C0ILYqNBZBp9iwiJpoCBWYsuAdW6PPFn6w06jjnC+3JstVvWHO4cZqvRg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "atomically": "^2.0.3", - "dot-prop": "^9.0.0", - "graceful-fs": "^4.2.11", - "xdg-basedir": "^5.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/constant-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", - "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3", - "upper-case": "^2.0.2" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT", - "peer": 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, - "license": "MIT", - "peer": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csp_evaluator": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/csp_evaluator/-/csp_evaluator-1.1.5.tgz", - "integrity": "sha512-EL/iN9etCTzw/fBnp0/uj0f5BOOGvZut2mzsiiBZ/FdT6gFQCKRO/tmcKOxn5drWZ2Ndm/xBb1SI4zwWbGtmIw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "dev": true, - "license": "MIT" - }, - "node_modules/dedent": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", - "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", - "dev": true, - "license": "MIT", - "peer": true, - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/devtools-protocol": { - "version": "0.0.1507524", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1507524.tgz", - "integrity": "sha512-OjaNE7qpk6GRTXtqQjAE5bGx6+c4F1zZH0YXtpZQLM92HNXx4zMAaqlKhP4T52DosG6hDW8gPMNhGOF8xbwk/w==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/dot-prop": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", - "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^4.18.2" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dot-prop/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dotenv": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", - "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/electron-to-chromium": { - "version": "1.5.286", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/enquirer/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/enquirer/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/events-universal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", - "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bare-events": "^2.7.0" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/exit-x": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", - "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/expect-utils": "30.2.0", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extract-zip/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", - "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.4", - "mime-types": "^2.1.35" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/forwarded-parse": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", - "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-port": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", - "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-uri": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/gettext-parser": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/gettext-parser/-/gettext-parser-1.4.0.tgz", - "integrity": "sha512-sedZYLHlHeBop/gZ1jdg59hlUEcpcZJofLq2JFwJT1zTqAU3l2wFv6IsuwFHGqbiT9DWzMUW4/em2+hspnmMMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "encoding": "^0.1.12", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "peer": 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/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/header-case": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", - "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "capital-case": "^1.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/http-link-header": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/http-link-header/-/http-link-header-1.1.3.tgz", - "integrity": "sha512-3cZ0SRL8fb9MUlU3mKM61FcQvPfXx2dBrZW3Vbg5CXa8jFlK8OaEpePenLe1oEXQduhz8b0QjsqfS59QP4AJDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/image-ssim": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/image-ssim/-/image-ssim-0.2.0.tgz", - "integrity": "sha512-W7+sO6/yhxy83L0G7xR8YAc5Z5QFtYEXXRV6EaE8tuYBZJnA3gVgp3q7X7muhLZVodeb9UfvjSbwt9VJwjIYAg==", - "dev": true, - "license": "MIT" - }, - "node_modules/import-in-the-middle": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.15.0.tgz", - "integrity": "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "acorn": "^8.14.0", - "acorn-import-attributes": "^1.9.5", - "cjs-module-lexer": "^1.2.2", - "module-details-from-path": "^1.0.3" - } - }, - "node_modules/import-in-the-middle/node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/intl-messageformat": { - "version": "10.7.18", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.18.tgz", - "integrity": "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@formatjs/ecma402-abstract": "2.3.6", - "@formatjs/fast-memoize": "2.2.7", - "@formatjs/icu-messageformat-parser": "2.11.4", - "tslib": "^2.8.0" - } - }, - "node_modules/ip-address": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.5.0.tgz", - "integrity": "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "peer": true, - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", - "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/core": "30.2.0", - "@jest/types": "30.2.0", - "import-local": "^3.2.0", - "jest-cli": "30.2.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", - "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "execa": "^5.1.1", - "jest-util": "30.2.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-circus": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", - "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "co": "^4.6.0", - "dedent": "^1.6.0", - "is-generator-fn": "^2.1.0", - "jest-each": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "p-limit": "^3.1.0", - "pretty-format": "30.2.0", - "pure-rand": "^7.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-cli": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", - "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/core": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "exit-x": "^0.2.2", - "import-local": "^3.2.0", - "jest-config": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "yargs": "^17.7.2" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", - "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/get-type": "30.1.0", - "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.2.0", - "@jest/types": "30.2.0", - "babel-jest": "30.2.0", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "deepmerge": "^4.3.1", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "jest-circus": "30.2.0", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-runner": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "micromatch": "^4.0.8", - "parse-json": "^5.2.0", - "pretty-format": "30.2.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "esbuild-register": ">=3.4.0", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "esbuild-register": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", - "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/diff-sequences": "30.0.1", - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", - "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "detect-newline": "^3.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-each": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", - "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "jest-util": "30.2.0", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", - "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-mock": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", - "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "anymatch": "^3.1.3", - "fb-watchman": "^2.0.2", - "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", - "micromatch": "^4.0.8", - "walker": "^1.0.8" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.3" - } - }, - "node_modules/jest-leak-detector": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", - "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/get-type": "30.1.0", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", - "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "jest-diff": "30.2.0", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", - "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.2.0", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-mock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", - "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-util": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", - "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "slash": "^3.0.0", - "unrs-resolver": "^1.7.11" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", - "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "jest-regex-util": "30.0.1", - "jest-snapshot": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-runner": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", - "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/console": "30.2.0", - "@jest/environment": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-leak-detector": "30.2.0", - "jest-message-util": "30.2.0", - "jest-resolve": "30.2.0", - "jest-runtime": "30.2.0", - "jest-util": "30.2.0", - "jest-watcher": "30.2.0", - "jest-worker": "30.2.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", - "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/globals": "30.2.0", - "@jest/source-map": "30.0.1", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "cjs-module-lexer": "^2.1.0", - "collect-v8-coverage": "^1.0.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", - "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/core": "^7.27.4", - "@babel/generator": "^7.27.5", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1", - "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.2.0", - "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "babel-preset-current-node-syntax": "^1.2.0", - "chalk": "^4.1.2", - "expect": "30.2.0", - "graceful-fs": "^4.2.11", - "jest-diff": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "pretty-format": "30.2.0", - "semver": "^7.7.2", - "synckit": "^0.11.8" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", - "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/jest-validate": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", - "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", - "camelcase": "^6.3.0", - "chalk": "^4.1.2", - "leven": "^3.1.0", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", - "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "jest-util": "30.2.0", - "string-length": "^4.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-worker": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", - "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*", - "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.2.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.1.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jpeg-js": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", - "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/js-library-detector": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/js-library-detector/-/js-library-detector-6.7.0.tgz", - "integrity": "sha512-c80Qupofp43y4cJ7+8TTDN/AsDwLi5oOm/plBrWI+iQt485vKXCco+yVmOwEgdo9VOdsYTuV0UlTeetVPTriXA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/legacy-javascript": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/legacy-javascript/-/legacy-javascript-0.0.1.tgz", - "integrity": "sha512-lPyntS4/aS7jpuvOlitZDFifBCb4W8L/3QU0PLbUTUj+zYah8rfVjYic88yG7ZKTxhS5h9iz7duT8oUXKszLhg==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/lighthouse": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/lighthouse/-/lighthouse-12.8.2.tgz", - "integrity": "sha512-+5SKYzVaTFj22MgoYDPNrP9tlD2/Ay7j3SxPSFD9FpPyVxGr4UtOQGKyrdZ7wCmcnBaFk0mCkPfARU3CsE0nvA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@paulirish/trace_engine": "0.0.59", - "@sentry/node": "^9.28.1", - "axe-core": "^4.10.3", - "chrome-launcher": "^1.2.0", - "configstore": "^7.0.0", - "csp_evaluator": "1.1.5", - "devtools-protocol": "0.0.1507524", - "enquirer": "^2.3.6", - "http-link-header": "^1.1.1", - "intl-messageformat": "^10.5.3", - "jpeg-js": "^0.4.4", - "js-library-detector": "^6.7.0", - "lighthouse-logger": "^2.0.2", - "lighthouse-stack-packs": "1.12.2", - "lodash-es": "^4.17.21", - "lookup-closest-locale": "6.2.0", - "metaviewport-parser": "0.3.0", - "open": "^8.4.0", - "parse-cache-control": "1.0.1", - "puppeteer-core": "^24.17.1", - "robots-parser": "^3.0.1", - "speedline-core": "^1.4.3", - "third-party-web": "^0.27.0", - "tldts-icann": "^7.0.12", - "ws": "^7.0.0", - "yargs": "^17.3.1", - "yargs-parser": "^21.0.0" - }, - "bin": { - "chrome-debug": "core/scripts/manual-chrome-launcher.js", - "lighthouse": "cli/index.js", - "smokehouse": "cli/test/smokehouse/frontends/smokehouse-bin.js" - }, - "engines": { - "node": ">=18.16" - } - }, - "node_modules/lighthouse-logger": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.2.tgz", - "integrity": "sha512-vWl2+u5jgOQuZR55Z1WM0XDdrJT6mzMP8zHUct7xTlWhuQs+eV0g+QL0RQdFjT54zVmbhLCP8vIVpy1wGn/gCg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "debug": "^4.4.1", - "marky": "^1.2.2" - } - }, - "node_modules/lighthouse-stack-packs": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/lighthouse-stack-packs/-/lighthouse-stack-packs-1.12.2.tgz", - "integrity": "sha512-Ug8feS/A+92TMTCK6yHYLwaFMuelK/hAKRMdldYkMNwv+d9PtWxjXEg6rwKtsUXTADajhdrhXyuNCJ5/sfmPFw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash-es": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", - "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", - "dev": true, - "license": "MIT" - }, - "node_modules/lookup-closest-locale": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/lookup-closest-locale/-/lookup-closest-locale-6.2.0.tgz", - "integrity": "sha512-/c2kL+Vnp1jnV6K6RpDTHK3dgg0Tu2VVp+elEiJpjfS1UyY7AjOYHohRug6wT0OpoX2qFgNORndE9RqesfVxWQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/marky": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", - "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/memize": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/memize/-/memize-2.1.1.tgz", - "integrity": "sha512-8Nl+i9S5D6KXnruM03Jgjb+LwSupvR13WBr4hJegaaEyobvowCVupi79y2WSiWvO1mzBWxPwEYE5feCe8vyA5w==", - "dev": true, - "license": "MIT" - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/metaviewport-parser": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/metaviewport-parser/-/metaviewport-parser-0.3.0.tgz", - "integrity": "sha512-EoYJ8xfjQ6kpe9VbVHvZTZHiOl4HL1Z18CrZ+qahvLXT7ZO4YTC2JMyt5FaUp9JJp6J4Ybb/z7IsCXZt86/QkQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "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, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "peer": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true, - "license": "MIT" - }, - "node_modules/module-details-from-path": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", - "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", - "dev": true, - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/napi-postinstall": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", - "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "napi-postinstall": "lib/cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/napi-postinstall" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "dev": true, - "license": "MIT", - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0", - "peer": true - }, - "node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/parse-cache-control": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", - "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==", - "dev": true - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/path-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", - "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "peer": 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/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, - "license": "MIT" - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.11.0.tgz", - "integrity": "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==", - "dev": true, - "license": "MIT" - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/playwright": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", - "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.58.2" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", - "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", - "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/proxy-agent": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", - "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/puppeteer-core": { - "version": "24.37.2", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.37.2.tgz", - "integrity": "sha512-nN8qwE3TGF2vA/+xemPxbesntTuqD9vCGOiZL2uh8HES3pPzLX20MyQjB42dH2rhQ3W3TljZ4ZaKZ0yX/abQuw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "2.12.0", - "chromium-bidi": "13.1.1", - "debug": "^4.4.3", - "devtools-protocol": "0.0.1566079", - "typed-query-selector": "^2.12.0", - "webdriver-bidi-protocol": "0.4.0", - "ws": "^8.19.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1566079", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1566079.tgz", - "integrity": "sha512-MJfAEA1UfVhSs7fbSQOG4czavUp1ajfg6prlAN0+cmfa2zNjaIbvq8VneP7do1WAQQIvgNJWSMeP6UyI90gIlQ==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/puppeteer-core/node_modules/ws": { - "version": "8.21.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", - "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/pure-rand": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", - "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT", - "peer": true - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/remove-accents": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", - "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==", - "dev": true, - "license": "MIT" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-in-the-middle": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", - "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "module-details-from-path": "^1.0.3", - "resolve": "^1.22.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/robots-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/robots-parser/-/robots-parser-3.0.1.tgz", - "integrity": "sha512-s+pyvQeIKIZ0dx5iJiQk1tPLJAWln39+MI5jtM8wnyws+G5azk+dMnMX0qfbqNetKKNgcWWOdi0sfm+FbQbgdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/sentence-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", - "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3", - "upper-case-first": "^2.0.2" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "peer": 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, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shimmer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", - "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "peer": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/snake-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", - "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", - "dev": true, - "license": "MIT", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/speedline-core": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/speedline-core/-/speedline-core-1.4.3.tgz", - "integrity": "sha512-DI7/OuAUD+GMpR6dmu8lliO2Wg5zfeh+/xsdyJZCzd8o5JgFUjCeLsBDuZjIQJdwXS3J0L/uZYrELKYqx+PXog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "image-ssim": "^0.2.0", - "jpeg-js": "^0.4.1" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/streamx": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", - "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "events-universal": "^1.0.0", - "fast-fifo": "^1.3.2", - "text-decoder": "^1.1.0" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-length/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-length/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "peer": 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, - "license": "MIT", - "peer": 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/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stubborn-fs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz", - "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "stubborn-utils": "^1.0.1" - } - }, - "node_modules/stubborn-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz", - "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==", - "dev": true, - "license": "MIT" - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/synckit": { - "version": "0.11.12", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", - "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@pkgr/core": "^0.2.9" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, - "node_modules/tannin": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tannin/-/tannin-1.2.0.tgz", - "integrity": "sha512-U7GgX/RcSeUETbV7gYgoz8PD7Ni4y95pgIP/Z6ayI3CfhSujwKEBlGFTCRN+Aqnuyf4AN2yHL+L8x+TCGjb9uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tannin/plural-forms": "^1.1.0" - } - }, - "node_modules/tar-fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", - "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" - } - }, - "node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/text-decoder": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", - "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "b4a": "^1.6.4" - } - }, - "node_modules/third-party-web": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/third-party-web/-/third-party-web-0.27.0.tgz", - "integrity": "sha512-h0JYX+dO2Zr3abCQpS6/uFjujaOjA1DyDzGQ41+oFn9VW/ARiq9g5ln7qEP9+BTzDpOMyIfsfj4OvfgXAsMUSA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tldts-core": { - "version": "7.0.22", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.22.tgz", - "integrity": "sha512-KgbTDC5wzlL6j/x6np6wCnDSMUq4kucHNm00KXPbfNzmllCmtmvtykJHfmgdHntwIeupW04y8s1N/43S1PkQDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tldts-icann": { - "version": "7.0.22", - "resolved": "https://registry.npmjs.org/tldts-icann/-/tldts-icann-7.0.22.tgz", - "integrity": "sha512-Wb5HEhrSy+zJtdJ6gop7ZNQ/Iacz/0c8t+6Kp1QoT84VRfc0TfPJLrb8f6YuRvCUOVjU889KJlPcG+5glVX8GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tldts-core": "^7.0.22" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-query-selector": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", - "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", - "dev": true, - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/unrs-resolver": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", - "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "dependencies": { - "napi-postinstall": "^0.3.0" - }, - "funding": { - "url": "https://opencollective.com/unrs-resolver" - }, - "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.11.1", - "@unrs/resolver-binding-android-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-x64": "1.11.1", - "@unrs/resolver-binding-freebsd-x64": "1.11.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-musl": "1.11.1", - "@unrs/resolver-binding-wasm32-wasi": "1.11.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/upper-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", - "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/upper-case-first": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", - "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/web-vitals": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", - "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/webdriver-bidi-protocol": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.0.tgz", - "integrity": "sha512-U9VIlNRrq94d1xxR9JrCEAx5Gv/2W7ERSv8oWRoNe/QYbfccS0V3h/H6qeNeCRJxXGMhhnkqvwNrvPAYeuP9VA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/when-exit": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz", - "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==", - "dev": true, - "license": "MIT" - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "peer": 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/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "peer": 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/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT", - "peer": 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, - "license": "MIT", - "peer": 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-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/ws": { - "version": "7.5.13", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", - "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xdg-basedir": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", - "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/tests/playwright/package.json b/tests/playwright/package.json deleted file mode 100644 index e8dfe98..0000000 --- a/tests/playwright/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "playwright", - "version": "1.0.0", - "main": "index.js", - "scripts": { - "test": "playwright test", - "test:a11y": "playwright test tests/7_accessibility" - }, - "keywords": [], - "author": "", - "license": "ISC", - "description": "", - "devDependencies": { - "@axe-core/playwright": "^4.11.3", - "@playwright/test": "^1.58.1", - "@types/node": "^20.17.10", - "@wordpress/e2e-test-utils": "^11.34.0", - "@wordpress/e2e-test-utils-playwright": "^1.39.0", - "dotenv": "^17.3.1" - } -} diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index 305a384..9a8e7c9 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -1,94 +1,55 @@ -import 'dotenv/config'; +import path from 'path'; +import dotenv from 'dotenv'; import { defineConfig, devices } from '@playwright/test'; -/** - * See https://playwright.dev/docs/test-configuration. - */ +dotenv.config({ path: path.resolve(__dirname, '.env'), quiet: true }); + +function getBaseURL(): string { + if (process.env.WP_BASE_URL) { + return process.env.WP_BASE_URL; + } + if (process.env.DDEV_HOSTNAME || process.env.IS_DDEV_PROJECT) { + return 'https://dev.mimisrecipes.ddev.site'; + } + return 'http://localhost:8888'; +} + export default defineConfig({ testDir: './tests', - /* Run tests in files in parallel */ + outputDir: path.join(__dirname, 'test-results'), + globalSetup: require.resolve('./global-setup.ts'), fullyParallel: false, - /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: true, - /* Retry on CI only */ - retries: 0, - /* Opt out of parallel tests on CI. */ + retries: process.env.CI ? 1 : 0, workers: 1, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: [ ['list'], - ['html', { open: 'never' }], + ['html', { open: 'never', outputFolder: path.join(__dirname, 'playwright-report') }], ['./reporters/a11y-reporter.ts'], ], - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: process.env.WP_BASE_URL || 'https://dev.mimisrecipes.ddev.site', - baseURL: 'https://dev.mimisrecipes.ddev.site', - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', - // Populates context with given storage state. - //storageState: 'state.json', + baseURL: getBaseURL(), ignoreHTTPSErrors: true, - - /* Timeout settings */ + trace: 'on-first-retry', actionTimeout: 30000, navigationTimeout: 30000, }, - - /* Global test timeout */ timeout: 60000, - - /* Configure projects for major browsers */ + webServer: { + command: 'echo "Using existing WordPress server"', + reuseExistingServer: true, + ignoreHTTPSErrors: true, + timeout: 120000, + }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'], + launchOptions: { + args: ['--disable-dev-shm-usage'], + }, }, }, - - // { - // name: 'firefox', - // use: { ...devices['Desktop Firefox'] }, - // }, - - // { - // name: 'webkit', - // use: { ...devices['Desktop Safari'] }, - // }, - - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, - // }, ], - - /* Run your local dev server before starting the tests */ - webServer: { - // command: 'npm run start', - command: 'echo "Using DDEV server"', - // url: 'http://127.0.0.1:9323', - // url: 'https://dev.mimisrecipes.ddev.site', - reuseExistingServer: true, - ignoreHTTPSErrors: true, - // Add timeout to give server more time to respond - timeout: 120000, - }, }); diff --git a/tests/playwright/tests/1_admin_user/1-create-recipe-front.spec.ts.bak b/tests/playwright/tests/1_admin_user/1-create-recipe-front.spec.ts.bak deleted file mode 100644 index 3f5915e..0000000 --- a/tests/playwright/tests/1_admin_user/1-create-recipe-front.spec.ts.bak +++ /dev/null @@ -1,269 +0,0 @@ -import { test as base, expect, Page } from '@playwright/test'; -import { ensureValidAuth, getAuthPath } from '../../utils/auth'; -import { readFileSync } from 'fs'; - -var title: string; -var postId: number; - -declare global { - interface Window { - tinyMCE: { - get: (id: string) => { - setContent: (content: string) => void; - }; - editors: { [key: string]: any }; - }; - } -} - -const dragAndDropFile = async ( - page: Page, - selector: string, - filePath: string, - fileName: string, - fileType = '' -) => { - const buffer = readFileSync(filePath).toString('base64'); - - const dataTransfer = await page.evaluateHandle( - async ({ bufferData, localFileName, localFileType }) => { - const dt = new DataTransfer(); - - const blobData = await fetch(bufferData).then((res) => res.blob()); - - const file = new File([blobData], localFileName, { type: localFileType }); - dt.items.add(file); - return dt; - }, - { - bufferData: `data:application/octet-stream;base64,${buffer}`, - localFileName: fileName, - localFileType: fileType, - } - ); - - await page.dispatchEvent(selector, 'drop', { dataTransfer }); -}; - -// Create a fixture for authentication -const test = base.extend({ - adminContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('mtresova') - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'mtresova', 'password'); - await use(context); - }, - contribContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('testUserContributor') - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'testUserContributor', 'password'); - await use(context); - } -}); - -test.describe('Create a new complete recipe front-end (contrib user)', () => { - test('Create a new recipe', async ({ contribContext }) => { - const contribPage = await contribContext.newPage(); - await contribPage.goto('/profile/add/', { waitUntil: 'networkidle' }); - title = 'Test Recipe Playwright: ' + Date.now(); - - // Set recipe title - await contribPage.fill('input[name="_recipe_settings[post_title]"]', title); - - // Set difficulty level - await contribPage.selectOption('select[name="_recipe_settings[difficulty_level]"]', '1'); // Beginner - - // Set times and servings - await contribPage.fill('input[name="_recipe_settings[prep_time]"]', '15'); - await contribPage.fill('input[name="_recipe_settings[cook_time]"]', '30'); - - // Click on the Nutrition tab to make those fields visible - await contribPage.click('.cooked-add-nutrition-button', { force: true }); - await contribPage.fill('input[name="_recipe_settings[nutrition][servings]"]', '4'); - - // Set recipe taxonomies - await contribPage.selectOption('select[name="_recipe_settings[category]"]', 'bread'); - await contribPage.selectOption('select[name="_recipe_settings[cooking_method]"]', 'baking'); - await contribPage.selectOption('select[name="_recipe_settings[cuisine]"]', 'albanian'); - await contribPage.selectOption('select[name="_recipe_settings[diet]"]', 'vegetarian'); - - await dragAndDropFile(contribPage, "#featured_image", "tests/_files/icon_pro.png", "icon_pro.png", "image/png"); - - // Handle all WYSIWYG editors - await contribPage.evaluate(() => { - // Excerpt editor - const excerptEditor = window.tinyMCE.get('excerpt'); - if (excerptEditor) { - excerptEditor.setContent('This is a brief description of the recipe.'); - } - - // Notes editor - const notesEditor = window.tinyMCE.get('notes'); - if (notesEditor) { - notesEditor.setContent('Important notes about this recipe.'); - } - - // Add ingredients - const addIngredientButton = document.querySelector('.cooked-add-ingredient-button'); - if (addIngredientButton) { - (addIngredientButton as HTMLElement).click(); - } - - // Find the first ingredient fields using regex - const ingredientBlocks = document.querySelectorAll('.cooked-ingredient-block'); - if (ingredientBlocks.length > 0) { - const firstIngredient = ingredientBlocks[0]; - const amountInput = firstIngredient.querySelector('input[data-ingredient-part="amount"]') as HTMLInputElement; - const measurementSelect = firstIngredient.querySelector('select[data-ingredient-part="measurement"]') as HTMLSelectElement; - const itemInput = firstIngredient.querySelector('input[data-ingredient-part="name"]') as HTMLInputElement; - - if (amountInput) amountInput.value = '2'; - if (measurementSelect) { - // Set the measurement to "cups" - measurementSelect.value = 'cup'; - } - if (itemInput) itemInput.value = 'flour'; - } - - // Add a direction step - const addDirectionButton = document.querySelector('.cooked-add-direction-button'); - if (addDirectionButton) { - (addDirectionButton as HTMLElement).click(); - } - - // Wait for TinyMCE to initialize the new editor - return new Promise((resolve) => { - const checkEditor = setInterval(() => { - const directionEditors = Object.keys(window.tinyMCE.editors).filter(id => /^direction-\d+-content$/.test(id)); - - if (directionEditors.length > 0) { - const directionEditor = window.tinyMCE.get(directionEditors[0]); - if (directionEditor) { - directionEditor.setContent('First step of the recipe.'); - clearInterval(checkEditor); - resolve(true); - } - } - }, 500); // Check every 500ms - - // Set a timeout to prevent infinite checking - setTimeout(() => { - clearInterval(checkEditor); - resolve(false); - }, 10000); // Maximum 10 second timeout - }); - - }); - - // Click the publish button first - await contribPage.getByRole('button', { name: 'Submit Recipe', exact: true }).click(); - - // Wait for both URL change and success message - await Promise.all([ - // Wait for URL to change to post edit page - contribPage.waitForURL('/profile/'), - // Wait for success message - contribPage.waitForSelector('.cooked-success-banner', { timeout: 10000 }) - ]); - - // Check for success message content - await expect(contribPage.locator('.cooked-success-banner')).toContainText('You have successfully submitted a new recipe. It is now pending approval.'); - - // After successful submission, get the recipe ID from the edit button URL - const editButton = contribPage.locator('.cooked-edit-button').first(); - const href = await editButton.getAttribute('href'); - postId = parseInt(href?.match(/edit-recipe\/(\d+)/)?.[1] || ''); - - expect(postId).toBeTruthy(); - }); - - test('View the recipe (frontend)', async ({ contribContext }) => { - const contribPage = await contribContext.newPage(); - await contribPage.goto('/?post_type=cp_recipe&p=' + postId, { waitUntil: 'networkidle' }); - - // Check for recipe title in h1.entry-title - await expect(contribPage.locator('h1.entry-title')).toHaveText(title); - - // Check for pending message - await expect(contribPage.getByText('This recipe is pending review. No one else can see it yet.')).toBeVisible(); - }); - - test('Edit the recipe (frontend)', async ({ contribContext }) => { - const contribPage = await contribContext.newPage(); - // Get the frontend URL using WP-CLI - if (!postId) { - throw new Error('Post ID is not set'); - } - - await contribPage.goto('/profile/edit-recipe/' + postId + '/', { waitUntil: 'networkidle' }); - - await expect(contribPage.getByText(title)).toBeDefined(); - - // Change the title - title = 'Test Recipe Playwright - Edited - ' + Date.now(); - await contribPage.fill('input[name="_recipe_settings[post_title]"]', title); - - // Click the publish button first - await contribPage.getByRole('button', { name: 'Update Recipe', exact: true }).click(); - - // Wait for both URL change and success message - await Promise.all([ - // Wait for URL to change to post edit page - contribPage.waitForURL('/profile/'), - // Wait for success message - contribPage.waitForSelector('.cooked-success-banner', { timeout: 10000 }) - ]); - - // Check for success message content - await expect(contribPage.locator('.cooked-success-banner')).toContainText('You have successfully edited the recipe. It is now pending approval.'); - - await contribPage.close(); - }); -}); - -// Delete the recipe -test.describe('Delete the recipe (contrib user)', () => { - test('Delete the recipe (contrib user)', async ({ contribContext }) => { - const contribPage = await contribContext.newPage(); - await contribPage.goto('/profile/', { waitUntil: 'networkidle' }); - - const deleteButton = contribPage.locator('.cooked-delete-button').first(); - await deleteButton.click(); - - // Wait for confirmation dialog to appear - await expect(contribPage.locator('.cooked-confirm-block .cooked-delete-final').first()).toBeVisible({ timeout: 10000 }); - - // First, handle the confirm dialog that will appear - contribPage.on('dialog', async dialog => { - // Automatically accept the confirmation - await dialog.accept(); - }); - - // OR alternatively, use dispatchEvent to trigger a native click - await contribPage.evaluate(() => { - const button = document.querySelector('.cooked-delete-button.cooked-delete-final') as HTMLElement; - if (button) { - button.dispatchEvent(new MouseEvent('click', { - bubbles: true, - cancelable: true, - view: window - })); - } - }); - - // Wait for the recipe to be deleted - await contribPage.waitForTimeout(1000); - - // Verify the recipe title no longer exists in the list - await expect(contribPage.getByText(title)).not.toBeVisible(); - - // Verify by trying to access the recipe URL directly - const response = await contribPage.goto('/?post_type=cp_recipe&p=' + postId, { waitUntil: 'networkidle' }); - - expect(response?.status()).toBe(404); - }); -}); diff --git a/tests/playwright/tests/1_admin_user/2-create-recipe-nutrition.spec.ts b/tests/playwright/tests/1_admin_user/2-create-recipe-nutrition.spec.ts deleted file mode 100644 index 034da8b..0000000 --- a/tests/playwright/tests/1_admin_user/2-create-recipe-nutrition.spec.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { test as base, expect } from '@playwright/test'; -import { ensureValidAuth, getAuthPath } from '../../utils/auth'; -import { execSync } from 'child_process'; - -var postId: number; -var title: string; - -declare global { - interface Window { - tinyMCE: { - get: (id: string) => { - setContent: (content: string) => void; - }; - editors: { [key: string]: any }; - }; - } -} - -// Create a fixture for authentication -const test = base.extend({ - adminContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('mtresova') - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'mtresova', 'password'); - await use(context); - await page.close(); - await context.close(); - } -}); - -test.describe('Create a new complete recipe (admin)', () => { - test('Create a new recipe (admin)', async ({ adminContext }) => { - const adminPage = await adminContext.newPage(); - await adminPage.goto('/wp-admin/post-new.php?post_type=cp_recipe', { waitUntil: 'networkidle' }); - title = 'Test Recipe Playwright: ' + Date.now(); - - // Set recipe title - await adminPage.getByLabel('Recipe title ...').fill(title); - - // Set difficulty level - await adminPage.selectOption('select[name="_recipe_settings[difficulty_level]"]', '1'); // Beginner - - // Set times and servings - await adminPage.fill('input[name="_recipe_settings[prep_time]"]', '15'); - await adminPage.fill('input[name="_recipe_settings[cook_time]"]', '30'); - - // Click on the Nutrition tab to make those fields visible - await adminPage.click('#cooked-recipe-tab-nutrition', { force: true }); - await adminPage.fill('input[name="_recipe_settings[nutrition][servings]"]', '4'); - - // Set recipe taxonomies - await adminPage.check('input[name="tax_input[cp_recipe_category][]"][value="298"]'); - await adminPage.check('input[name="tax_input[cp_recipe_cooking_method][]"][value="288"]'); - await adminPage.check('input[name="tax_input[cp_recipe_cuisine][]"][value="279"]'); - await adminPage.check('input[name="tax_input[cp_recipe_diet][]"][value="268"]'); - - // Set recipe tags (can be multiple) - await adminPage.locator('input[name="newtag[cp_recipe_tags]"]').fill('butter, egg, flour'); - await adminPage.locator('.button.tagadd').first().click(); - - await adminPage.locator('#set-post-thumbnail').click(); - - // Wait for the media modal to appear - await adminPage.waitForSelector('.media-modal-content'); - - // If you want to upload a new image, use this: - // await adminPage.setInputFiles('input[type="file"]', 'path/to/your/image.jpg'); - // await adminPage.getByRole('button', { name: 'Upload' }).click(); - - // Or to select an existing image from the media library: - await adminPage.getByRole('tab', { name: 'Media Library' }).click(); - await adminPage.waitForSelector('.attachment-preview'); - - // Select the first image in the media library - await adminPage.locator('.attachment-preview').first().click(); - - // Click the "Set Featured Image" button in the modal - await adminPage.getByRole('button', { name: 'Set Featured Image' }).click(); - - // Wait for the featured image to be set - await adminPage.waitForSelector('#remove-post-thumbnail'); - - // Handle all WYSIWYG editors - await adminPage.evaluate(() => { - // Excerpt editor - const excerptEditor = window.tinyMCE.get('_recipe_settings_excerpt'); - if (excerptEditor) { - excerptEditor.setContent('This is a brief description of the recipe.'); - } - - // Notes editor - const notesEditor = window.tinyMCE.get('_recipe_settings_notes'); - if (notesEditor) { - notesEditor.setContent('Important notes about this recipe.'); - } - - // Add ingredients - // const addIngredientButton = document.querySelector('.cooked-add-ingredient-button'); - // if (addIngredientButton) { - // (addIngredientButton as HTMLElement).click(); - // } - - // Find the first ingredient fields using regex - const ingredientBlocks = document.querySelectorAll('.cooked-ingredient-block'); - if (ingredientBlocks.length > 0) { - const firstIngredient = ingredientBlocks[0]; - const amountInput = firstIngredient.querySelector('input[data-ingredient-part="amount"]') as HTMLInputElement; - const measurementSelect = firstIngredient.querySelector('select[data-ingredient-part="measurement"]') as HTMLSelectElement; - const itemInput = firstIngredient.querySelector('input[data-ingredient-part="name"]') as HTMLInputElement; - - if (amountInput) amountInput.value = '2'; - if (measurementSelect) { - // Set the measurement to "cups" - measurementSelect.value = 'cup'; - } - if (itemInput) itemInput.value = 'flour'; - } - - // Add a direction step - // const addDirectionButton = document.querySelector('.cooked-add-direction-button'); - // if (addDirectionButton) { - // (addDirectionButton as HTMLElement).click(); - // } - - // Find the first direction editor using regex - const directionEditors = Object.keys(window.tinyMCE.editors).filter(id => /^direction-\d+-content$/.test(id)); - if (directionEditors.length > 0) { - const directionEditor = window.tinyMCE.get(directionEditors[0]); - if (directionEditor) { - directionEditor.setContent('First step of the recipe.'); - } - } - }); - - // Click the publish button first - await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - - // Wait for both URL change and success message - await Promise.all([ - // Wait for URL to change to post edit page - adminPage.waitForURL(/post\.php\?post=\d+&action=edit/), - // Wait for success message - adminPage.waitForSelector('.notice-success', { timeout: 10000 }) - ]); - - // Check for success message content - await expect(adminPage.locator('.notice-success')).toContainText('Post published.'); - - // Get the post ID from the URL - const url = adminPage.url(); - const postIdMatch = url.match(/post=(\d+)/); - postId = postIdMatch ? parseInt(postIdMatch[1]) : 0; - - expect(postId).toBeTruthy(); - }); - - test('View the recipe (frontend)', async ({ adminContext }) => { - // Get the frontend URL using WP-CLI - if (!postId) { - throw new Error('Post ID is not set'); - } - const adminPage = await adminContext.newPage(); - await adminPage.goto('/?post_type=cp_recipe&p=' + postId, { waitUntil: 'networkidle' }); - await expect(adminPage.getByText(title)).toBeDefined(); - }); - - test('Edit the recipe (admin)', async ({ adminContext }) => { - // Get the frontend URL using WP-CLI - if (!postId) { - throw new Error('Post ID is not set'); - } - const adminPage = await adminContext.newPage(); - await adminPage.goto(`/wp-admin/post.php?post=${postId}&action=edit`, { waitUntil: 'networkidle' }); - await expect(adminPage.getByText(title)).toBeDefined(); - - // Change the title - await adminPage.getByLabel('Recipe title ...').fill('Test Recipe Playwright - Edited - ' + Date.now()); - - // Click the publish button first - await adminPage.getByRole('button', { name: 'Update', exact: true }).click(); - - // Wait for both URL change and success message - await Promise.all([ - // Wait for URL to change to post edit page - adminPage.waitForURL(/post\.php\?post=\d+&action=edit/), - // Wait for success message - adminPage.waitForSelector('.notice.notice-success.updated', { timeout: 10000 }) - ]); - - // Check for success message content - await expect(adminPage.locator('.notice.notice-success.updated')).toContainText('Post updated.'); - }); -}); - -// Cleanup task - Delete the recipe -test.afterAll(async () => { - if (postId) { - try { - console.log(`Cleaning up: Deleting recipe ${postId}`); - execSync(`wp post delete ${postId} --force`); - } catch (error) { - console.error(`Failed to delete recipe ${postId}:`, error); - } - } else { - console.log('No post ID to delete'); - } -}); diff --git a/tests/playwright/tests/1_admin_user/3-related-recipes.spec.ts.bak b/tests/playwright/tests/1_admin_user/3-related-recipes.spec.ts.bak deleted file mode 100644 index 8e29f56..0000000 --- a/tests/playwright/tests/1_admin_user/3-related-recipes.spec.ts.bak +++ /dev/null @@ -1,711 +0,0 @@ -/** - * Related Recipes Shortcode Tests - * - * Tests the [cooked-related-recipes] shortcode functionality: - * - Creates test recipes with specific relationships (shared categories, tags, ingredients) - * - Verifies related recipes are displayed correctly - * - Tests shortcode parameters (limit, columns, hide_image, match_categories, title) - * - Tests admin calculation tool in Settings > Tools - * - * Test recipes created: - * - Source recipe: Italian category, pasta/dinner tags, flour/eggs/tomato/garlic ingredients - * - Related Recipe 1: Same category, shared ingredients (flour, eggs) - * - Related Recipe 2: Shared tags (pasta, dinner), shared ingredients (tomato, garlic) - * - Unrelated Recipe: Different category, different tags, different ingredients - */ - -import { test as base, expect } from '@playwright/test'; -import { ensureValidAuth, getAuthPath } from '../../utils/auth'; -import { execSync } from 'child_process'; - -// Recipe IDs for cleanup -var sourceRecipeId: number; -var relatedRecipe1Id: number; -var relatedRecipe2Id: number; -var unrelatedRecipeId: number; - -// Taxonomies created during tests (for cleanup) -var createdCategorySlugs: string[] = []; -var createdTagNames: string[] = []; - -declare global { - interface Window { - tinyMCE: { - get: (id: string) => { - setContent: (content: string) => void; - }; - editors: { [key: string]: any }; - }; - } -} - -// Create a fixture for authentication -const test = base.extend({ - adminContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('mtresova') - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'mtresova', 'password'); - await use(context); - await page.close(); - await context.close(); - } -}); - -/** - * Helper function to get or create a category and return its ID - * Tracks created categories for cleanup - */ -function getOrCreateCategory(slug: string, name: string): number { - try { - // Check if category already exists - const result = execSync(`wp term get cp_recipe_category ${slug} --field=term_id --format=json`, { encoding: 'utf-8' }); - return parseInt(result.trim()); - } catch { - // Category doesn't exist, create it - try { - const result = execSync(`wp term create cp_recipe_category "${name}" --slug="${slug}" --format=json`, { encoding: 'utf-8' }); - const term = JSON.parse(result); - // Track that we created this category - if (!createdCategorySlugs.includes(slug)) { - createdCategorySlugs.push(slug); - } - return term.term_id; - } catch { - // Fallback: try to find any existing category - const result = execSync(`wp term list cp_recipe_category --format=json --fields=term_id,slug`, { encoding: 'utf-8' }); - const terms = JSON.parse(result); - if (terms.length > 0) { - return terms[0].term_id; - } - return 0; - } - } -} - -/** - * Helper function to track tag names used in recipes - */ -function trackTags(tagNames: string[]): void { - tagNames.forEach(tag => { - if (tag && !createdTagNames.includes(tag)) { - createdTagNames.push(tag); - } - }); -} - -/** - * Helper function to create a recipe with specific attributes - */ -async function createRecipe( - adminPage: any, - title: string, - categorySlug: string = '', - tagNames: string[] = [], - ingredients: string[] = [], - difficulty: string = '1' -): Promise { - await adminPage.goto('/wp-admin/post-new.php?post_type=cp_recipe', { waitUntil: 'networkidle' }); - - await adminPage.getByLabel('Recipe title ...').fill(title); - await adminPage.selectOption('select[name="_recipe_settings[difficulty_level]"]', difficulty); - await adminPage.fill('input[name="_recipe_settings[prep_time]"]', '15'); - await adminPage.fill('input[name="_recipe_settings[cook_time]"]', '30'); - - // Set nutrition servings - await adminPage.click('#cooked-recipe-tab-nutrition', { force: true }); - await adminPage.fill('input[name="_recipe_settings[nutrition][servings]"]', '4'); - - // Set category if provided - if (categorySlug) { - const catId = getOrCreateCategory(categorySlug, categorySlug.charAt(0).toUpperCase() + categorySlug.slice(1)); - if (catId > 0) { - // Wait for category checkboxes to be available - await adminPage.waitForSelector(`input[name="tax_input[cp_recipe_category][]"]`, { timeout: 5000 }).catch(() => {}); - const checkbox = adminPage.locator(`input[name="tax_input[cp_recipe_category][]"][value="${catId}"]`); - if (await checkbox.count() > 0) { - await checkbox.check(); - } - } - } - - // Set tags - if (tagNames.length > 0) { - trackTags(tagNames); - await adminPage.locator('input[name="newtag[cp_recipe_tags]"]').fill(tagNames.join(', ')); - } - - // Set ingredients - add them one by one - if (ingredients.length > 0) { - await adminPage.evaluate((ingredientList: string[]) => { - // Get existing ingredient blocks - let ingredientBlocks = Array.from(document.querySelectorAll('.cooked-ingredient-block')); - - // Add more blocks if needed - const addButton = document.querySelector('.cooked-add-ingredient-button') as HTMLElement; - while (ingredientBlocks.length < ingredientList.length && addButton) { - addButton.click(); - ingredientBlocks = Array.from(document.querySelectorAll('.cooked-ingredient-block')); - } - - // Set ingredient names - ingredientList.forEach((ingredientName, index) => { - if (index < ingredientBlocks.length) { - const block = ingredientBlocks[index]; - const itemInput = block.querySelector('input[data-ingredient-part="name"]') as HTMLInputElement; - if (itemInput) { - itemInput.value = ingredientName; - // Trigger change event - itemInput.dispatchEvent(new Event('input', { bubbles: true })); - itemInput.dispatchEvent(new Event('change', { bubbles: true })); - } - } - }); - }, ingredients); - - // Wait for ingredients to be set - await adminPage.waitForTimeout(800); - } - - // Publish - await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - - await Promise.all([ - adminPage.waitForURL(/post\.php\?post=\d+&action=edit/), - adminPage.waitForSelector('.notice-success', { timeout: 10000 }) - ]); - - const url = adminPage.url(); - const postIdMatch = url.match(/post=(\d+)/); - return postIdMatch ? parseInt(postIdMatch[1]) : 0; -} - -test.describe('Related Recipes Shortcode Tests', () => { - test.beforeAll(async ({ adminContext }) => { - const adminPage = await adminContext.newPage(); - - // Create source recipe: Italian Pasta with specific category, tags, and ingredients - sourceRecipeId = await createRecipe( - adminPage, - `Source Recipe - Related Test ${Date.now()}`, - 'italian', // Category slug (will be created if doesn't exist) - ['pasta', 'italian', 'dinner'], - ['flour', 'eggs', 'tomato', 'garlic'], - '2' // Intermediate difficulty - ); - - // Create related recipe 1: Shares category and some ingredients - relatedRecipe1Id = await createRecipe( - adminPage, - `Related Recipe 1 - Shared Category ${Date.now()}`, - 'italian', // Same category - ['italian', 'main-course'], - ['flour', 'eggs', 'cheese'], // Shares flour and eggs - '1' - ); - - // Create related recipe 2: Shares tags and ingredients - relatedRecipe2Id = await createRecipe( - adminPage, - `Related Recipe 2 - Shared Tags ${Date.now()}`, - '', // Different category (none) - ['pasta', 'dinner'], // Shares tags - ['tomato', 'garlic', 'onion'], // Shares tomato and garlic - '2' // Same difficulty - ); - - // Create unrelated recipe: No shared attributes - unrelatedRecipeId = await createRecipe( - adminPage, - `Unrelated Recipe - No Matches ${Date.now()}`, - 'dessert', // Different category - ['dessert', 'sweet'], - ['sugar', 'butter', 'vanilla'], - '1' - ); - - await adminPage.close(); - - // Pre-calculate related recipes cache via admin tool - const settingsPage = await adminContext.newPage(); - await settingsPage.goto('/wp-admin/admin.php?page=cooked_settings#tools', { waitUntil: 'networkidle' }); - - // Click Calculate Related Recipes button - const calculateButton = settingsPage.locator('#cooked-calculate-related-button'); - if (await calculateButton.isVisible()) { - await calculateButton.click(); - - // Wait for completion - await settingsPage.waitForSelector('#cooked-related-completed.cooked-active', { timeout: 120000 }); - } - - await settingsPage.close(); - }); - - test('Related recipes shortcode displays correctly with explicit ID', async ({ page }) => { - test.use({ storageState: { cookies: [], origins: [] } }); - - if (!sourceRecipeId) { - test.skip(); - return; - } - - // Create a test page with the shortcode - const adminPage = await page.context().newPage(); - await adminPage.goto('/wp-admin/post-new.php?post_type=page', { waitUntil: 'networkidle' }); - - const pageTitle = `Test Related Recipes Display ${Date.now()}`; - await adminPage.getByLabel('Add title').fill(pageTitle); - await adminPage.click('#content-html'); // Switch to text editor - await adminPage.locator('#content').fill(`[cooked-related-recipes id="${sourceRecipeId}"]`); - - await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - await adminPage.waitForURL(/post\.php\?post=\d+&action=edit/); - - const pageUrl = adminPage.url(); - const pageIdMatch = pageUrl.match(/post=(\d+)/); - const testPageId = pageIdMatch ? parseInt(pageIdMatch[1]) : 0; - await adminPage.close(); - - // View the page on frontend - await page.goto(`/?p=${testPageId}`, { waitUntil: 'networkidle' }); - - // Check if related recipes section exists - const relatedSection = page.locator('.cooked-related-recipes-grid, .cooked-related-recipes-title'); - await expect(relatedSection.first()).toBeVisible({ timeout: 10000 }); - - // Cleanup - if (testPageId) { - execSync(`wp post delete ${testPageId} --force`, { stdio: 'ignore' }); - } - }); - - test('Related recipes respects title parameter', async ({ page }) => { - test.use({ storageState: { cookies: [], origins: [] } }); - - if (!sourceRecipeId) { - test.skip(); - return; - } - - const customTitle = `My Custom Related Recipes ${Date.now()}`; - - // Create a test page with custom title - const adminPage = await page.context().newPage(); - await adminPage.goto('/wp-admin/post-new.php?post_type=page', { waitUntil: 'networkidle' }); - - const pageTitle = `Test Related Recipes Title ${Date.now()}`; - await adminPage.getByLabel('Add title').fill(pageTitle); - await adminPage.click('#content-html'); - await adminPage.locator('#content').fill(`[cooked-related-recipes id="${sourceRecipeId}" title="${customTitle}"]`); - - await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - await adminPage.waitForURL(/post\.php\?post=\d+&action=edit/); - - const pageUrl = adminPage.url(); - const pageIdMatch = pageUrl.match(/post=(\d+)/); - const testPageId = pageIdMatch ? parseInt(pageIdMatch[1]) : 0; - await adminPage.close(); - - await page.goto(`/?p=${testPageId}`, { waitUntil: 'networkidle' }); - - // Check that custom title is displayed - const titleElement = page.locator('.cooked-related-recipes-title'); - await expect(titleElement).toBeVisible(); - await expect(titleElement).toHaveText(customTitle); - - // Cleanup - if (testPageId) { - execSync(`wp post delete ${testPageId} --force`, { stdio: 'ignore' }); - } - }); - - test('Related recipes shows recipes with shared categories', async ({ page }) => { - test.use({ storageState: { cookies: [], origins: [] } }); - - if (!sourceRecipeId || !relatedRecipe1Id) { - test.skip(); - return; - } - - // Create a test page with the shortcode - const adminPage = await page.context().newPage(); - await adminPage.goto('/wp-admin/post-new.php?post_type=page', { waitUntil: 'networkidle' }); - - const pageTitle = `Test Related Categories ${Date.now()}`; - await adminPage.getByLabel('Add title').fill(pageTitle); - await adminPage.click('#content-html'); - await adminPage.locator('#content').fill(`[cooked-related-recipes id="${sourceRecipeId}"]`); - - await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - await adminPage.waitForURL(/post\.php\?post=\d+&action=edit/); - - const pageUrl = adminPage.url(); - const pageIdMatch = pageUrl.match(/post=(\d+)/); - const testPageId = pageIdMatch ? parseInt(pageIdMatch[1]) : 0; - await adminPage.close(); - - await page.goto(`/?p=${testPageId}`, { waitUntil: 'networkidle' }); - - // Wait for related recipes to load - await page.waitForSelector('.cooked-related-recipes-grid', { timeout: 10000 }); - - // Check that related recipe 1 (shares category) appears - const relatedRecipe1 = page.locator(`#cooked-recipe-${relatedRecipe1Id}`); - await expect(relatedRecipe1).toBeVisible({ timeout: 10000 }); - - // Cleanup - if (testPageId) { - execSync(`wp post delete ${testPageId} --force`, { stdio: 'ignore' }); - } - }); - - test('Related recipes shows recipes with shared tags', async ({ page }) => { - test.use({ storageState: { cookies: [], origins: [] } }); - - if (!sourceRecipeId || !relatedRecipe2Id) { - test.skip(); - return; - } - - // Create a test page with the shortcode - const adminPage = await page.context().newPage(); - await adminPage.goto('/wp-admin/post-new.php?post_type=page', { waitUntil: 'networkidle' }); - - const pageTitle = `Test Related Tags ${Date.now()}`; - await adminPage.getByLabel('Add title').fill(pageTitle); - await adminPage.click('#content-html'); - await adminPage.locator('#content').fill(`[cooked-related-recipes id="${sourceRecipeId}"]`); - - await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - await adminPage.waitForURL(/post\.php\?post=\d+&action=edit/); - - const pageUrl = adminPage.url(); - const pageIdMatch = pageUrl.match(/post=(\d+)/); - const testPageId = pageIdMatch ? parseInt(pageIdMatch[1]) : 0; - await adminPage.close(); - - await page.goto(`/?p=${testPageId}`, { waitUntil: 'networkidle' }); - - // Wait for related recipes to load - await page.waitForSelector('.cooked-related-recipes-grid', { timeout: 10000 }); - - // Check that related recipe 2 (shares tags) appears - const relatedRecipe2 = page.locator(`#cooked-recipe-${relatedRecipe2Id}`); - await expect(relatedRecipe2).toBeVisible({ timeout: 10000 }); - - // Cleanup - if (testPageId) { - execSync(`wp post delete ${testPageId} --force`, { stdio: 'ignore' }); - } - }); - - test('Related recipes excludes unrelated recipes', async ({ page }) => { - test.use({ storageState: { cookies: [], origins: [] } }); - - if (!sourceRecipeId || !unrelatedRecipeId) { - test.skip(); - return; - } - - // Create a test page with the shortcode - const adminPage = await page.context().newPage(); - await adminPage.goto('/wp-admin/post-new.php?post_type=page', { waitUntil: 'networkidle' }); - - const pageTitle = `Test Related Exclude ${Date.now()}`; - await adminPage.getByLabel('Add title').fill(pageTitle); - await adminPage.click('#content-html'); - await adminPage.locator('#content').fill(`[cooked-related-recipes id="${sourceRecipeId}"]`); - - await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - await adminPage.waitForURL(/post\.php\?post=\d+&action=edit/); - - const pageUrl = adminPage.url(); - const pageIdMatch = pageUrl.match(/post=(\d+)/); - const testPageId = pageIdMatch ? parseInt(pageIdMatch[1]) : 0; - await adminPage.close(); - - await page.goto(`/?p=${testPageId}`, { waitUntil: 'networkidle' }); - - // Wait for related recipes section to load - await page.waitForSelector('.cooked-related-recipes-grid, .cooked-related-recipes-empty', { timeout: 10000 }); - - // Check that unrelated recipe does NOT appear - const unrelatedRecipe = page.locator(`#cooked-recipe-${unrelatedRecipeId}`); - await expect(unrelatedRecipe).not.toBeVisible(); - - // Cleanup - if (testPageId) { - execSync(`wp post delete ${testPageId} --force`, { stdio: 'ignore' }); - } - }); - - test('Related recipes respects limit parameter', async ({ page }) => { - test.use({ storageState: { cookies: [], origins: [] } }); - - if (!sourceRecipeId) { - test.skip(); - return; - } - - // Create a page with the shortcode using limit=1 - const adminPage = await page.context().newPage(); - await adminPage.goto('/wp-admin/post-new.php?post_type=page', { waitUntil: 'networkidle' }); - - const pageTitle = `Test Related Recipes Limit ${Date.now()}`; - await adminPage.getByLabel('Add title').fill(pageTitle); - - // Add shortcode with limit - await adminPage.click('#content-html'); // Switch to text editor - await adminPage.locator('#content').fill(`[cooked-related-recipes id="${sourceRecipeId}" limit="1"]`); - - // Publish page - await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - await adminPage.waitForURL(/post\.php\?post=\d+&action=edit/); - - const pageUrl = adminPage.url(); - const pageIdMatch = pageUrl.match(/post=(\d+)/); - const testPageId = pageIdMatch ? parseInt(pageIdMatch[1]) : 0; - - await adminPage.close(); - - // View the page on frontend - await page.goto(`/?p=${testPageId}`, { waitUntil: 'networkidle' }); - - // Check that only 1 recipe is displayed - const relatedRecipes = page.locator('.cooked-related-recipes-grid .cooked-recipe'); - const count = await relatedRecipes.count(); - expect(count).toBeLessThanOrEqual(1); - - // Cleanup test page - if (testPageId) { - execSync(`wp post delete ${testPageId} --force`, { stdio: 'ignore' }); - } - }); - - test('Related recipes respects columns parameter', async ({ page }) => { - test.use({ storageState: { cookies: [], origins: [] } }); - - if (!sourceRecipeId) { - test.skip(); - return; - } - - // Create a page with the shortcode using columns=2 - const adminPage = await page.context().newPage(); - await adminPage.goto('/wp-admin/post-new.php?post_type=page', { waitUntil: 'networkidle' }); - - const pageTitle = `Test Related Recipes Columns ${Date.now()}`; - await adminPage.getByLabel('Add title').fill(pageTitle); - - await adminPage.click('#content-html'); - await adminPage.locator('#content').fill(`[cooked-related-recipes id="${sourceRecipeId}" columns="2"]`); - - await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - await adminPage.waitForURL(/post\.php\?post=\d+&action=edit/); - - const pageUrl = adminPage.url(); - const pageIdMatch = pageUrl.match(/post=(\d+)/); - const testPageId = pageIdMatch ? parseInt(pageIdMatch[1]) : 0; - - await adminPage.close(); - - // View the page - await page.goto(`/?p=${testPageId}`, { waitUntil: 'networkidle' }); - - // Check that grid has 2 columns class - const grid = page.locator('.cooked-related-recipes-grid.cooked-columns-2'); - await expect(grid).toBeVisible(); - - // Cleanup - if (testPageId) { - execSync(`wp post delete ${testPageId} --force`, { stdio: 'ignore' }); - } - }); - - test('Related recipes respects hide_image parameter', async ({ page }) => { - test.use({ storageState: { cookies: [], origins: [] } }); - - if (!sourceRecipeId) { - test.skip(); - return; - } - - const adminPage = await page.context().newPage(); - await adminPage.goto('/wp-admin/post-new.php?post_type=page', { waitUntil: 'networkidle' }); - - const pageTitle = `Test Related Recipes Hide Image ${Date.now()}`; - await adminPage.getByLabel('Add title').fill(pageTitle); - - await adminPage.click('#content-html'); - await adminPage.locator('#content').fill(`[cooked-related-recipes id="${sourceRecipeId}" hide_image="true"]`); - - await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - await adminPage.waitForURL(/post\.php\?post=\d+&action=edit/); - - const pageUrl = adminPage.url(); - const pageIdMatch = pageUrl.match(/post=(\d+)/); - const testPageId = pageIdMatch ? parseInt(pageIdMatch[1]) : 0; - - await adminPage.close(); - - await page.goto(`/?p=${testPageId}`, { waitUntil: 'networkidle' }); - - // Check that recipe images are not visible - const recipeImages = page.locator('.cooked-related-recipes-grid .cooked-recipe-image'); - const count = await recipeImages.count(); - expect(count).toBe(0); - - // Cleanup - if (testPageId) { - execSync(`wp post delete ${testPageId} --force`, { stdio: 'ignore' }); - } - }); - - test('Admin: Calculate Related Recipes tool completes successfully', async ({ adminContext }) => { - const adminPage = await adminContext.newPage(); - await adminPage.goto('/wp-admin/admin.php?page=cooked_settings#tools', { waitUntil: 'networkidle' }); - - // Check that the Calculate Related Recipes button exists - const calculateButton = adminPage.locator('#cooked-calculate-related-button'); - await expect(calculateButton).toBeVisible(); - - // Check if "Last calculated" message exists (from previous runs) - const lastDone = adminPage.locator('#cooked-related-last-done'); - const hasLastRun = await lastDone.isVisible().catch(() => false); - - // Click the button - await calculateButton.click(); - - // Wait for progress bar to appear - await adminPage.waitForSelector('#cooked-related-progress.cooked-active', { timeout: 5000 }); - - // Wait for completion (with a reasonable timeout for large sites) - await adminPage.waitForSelector('#cooked-related-completed.cooked-active', { timeout: 120000 }); - - // Verify completion message is shown - const completedMessage = adminPage.locator('#cooked-related-completed.cooked-active'); - await expect(completedMessage).toBeVisible(); - await expect(completedMessage).toContainText('Done.'); - - // Verify "Last calculated" message is updated/shown - const lastDoneAfter = adminPage.locator('#cooked-related-last-done'); - await expect(lastDoneAfter).toBeVisible(); - await expect(lastDoneAfter).toContainText('Last:'); - - await adminPage.close(); - }); - - test('Related recipes with match_categories=false excludes category matches', async ({ page }) => { - test.use({ storageState: { cookies: [], origins: [] } }); - - if (!sourceRecipeId || !relatedRecipe1Id) { - test.skip(); - return; - } - - const adminPage = await page.context().newPage(); - await adminPage.goto('/wp-admin/post-new.php?post_type=page', { waitUntil: 'networkidle' }); - - const pageTitle = `Test Related Recipes No Categories ${Date.now()}`; - await adminPage.getByLabel('Add title').fill(pageTitle); - - await adminPage.click('#content-html'); - await adminPage.locator('#content').fill(`[cooked-related-recipes id="${sourceRecipeId}" match_categories="false"]`); - - await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - await adminPage.waitForURL(/post\.php\?post=\d+&action=edit/); - - const pageUrl = adminPage.url(); - const pageIdMatch = pageUrl.match(/post=(\d+)/); - const testPageId = pageIdMatch ? parseInt(pageIdMatch[1]) : 0; - - await adminPage.close(); - - await page.goto(`/?p=${testPageId}`, { waitUntil: 'networkidle' }); - - // Recipe 1 should not appear (it only matched by category) - const relatedRecipe1 = page.locator(`#cooked-recipe-${relatedRecipe1Id}`); - await expect(relatedRecipe1).not.toBeVisible(); - - // Recipe 2 should still appear (matches by tags/ingredients) - const relatedRecipe2 = page.locator(`#cooked-recipe-${relatedRecipe2Id}`); - await expect(relatedRecipe2).toBeVisible(); - - // Cleanup - if (testPageId) { - execSync(`wp post delete ${testPageId} --force`, { stdio: 'ignore' }); - } - }); -}); - -// Cleanup - Delete all test recipes and created taxonomies -test.afterAll(async () => { - // Delete test recipes - const recipeIds = [sourceRecipeId, relatedRecipe1Id, relatedRecipe2Id, unrelatedRecipeId].filter(id => id); - - for (const recipeId of recipeIds) { - try { - console.log(`Cleaning up: Deleting recipe ${recipeId}`); - execSync(`wp post delete ${recipeId} --force`, { stdio: 'ignore' }); - } catch (error) { - console.error(`Failed to delete recipe ${recipeId}:`, error); - } - } - - // Delete created categories (only if they have no posts associated) - for (const categorySlug of createdCategorySlugs) { - try { - // Get category count before deleting - const result = execSync(`wp term get cp_recipe_category ${categorySlug} --format=json --fields=term_id,count`, { encoding: 'utf-8' }).trim(); - if (result) { - const category = JSON.parse(result); - // Only delete if count is 0 (no posts using it) after our recipes are deleted - if (category.count === 0 || category.count === undefined) { - console.log(`Cleaning up: Deleting category ${categorySlug} (ID: ${category.term_id})`); - execSync(`wp term delete cp_recipe_category ${categorySlug} --force`, { stdio: 'ignore' }); - } else { - console.log(`Skipping category ${categorySlug} - still in use by ${category.count} post(s)`); - } - } - } catch (error) { - // Category might not exist or already deleted, ignore - console.log(`Category ${categorySlug} not found or already deleted`); - } - } - - // Delete created tags (only if they have no posts associated) - for (const tagName of createdTagNames) { - try { - // Get tag by name - const result = execSync(`wp term list cp_recipe_tags --format=json --fields=term_id,name,count --name="${tagName}"`, { encoding: 'utf-8' }).trim(); - if (result) { - const tags = JSON.parse(result); - for (const tag of tags) { - if (tag.name === tagName) { - // Only delete if count is 0 (no posts using it) or if it was likely created by us - // After deleting our test recipes, count should be 0 - if (tag.count === 0 || tag.count === undefined) { - console.log(`Cleaning up: Deleting tag ${tagName} (ID: ${tag.term_id})`); - execSync(`wp term delete cp_recipe_tags ${tag.term_id} --force`, { stdio: 'ignore' }); - } else { - console.log(`Skipping tag ${tagName} - still in use by ${tag.count} post(s)`); - } - break; - } - } - } - } catch (error) { - // Tag might not exist or already deleted, ignore - console.log(`Tag ${tagName} not found or already deleted`); - } - } - - // Clear the related recipes calculation cache option (optional, but good for clean state) - try { - execSync(`wp option delete cooked_related_calculation_last`, { stdio: 'ignore' }); - } catch (error) { - // Option might not exist, ignore - } -}); diff --git a/tests/playwright/tests/2_contrib_user/1-create-recipe-front.spec.ts b/tests/playwright/tests/2_contrib_user/1-create-recipe-front.spec.ts deleted file mode 100644 index 4b86ee0..0000000 --- a/tests/playwright/tests/2_contrib_user/1-create-recipe-front.spec.ts +++ /dev/null @@ -1,241 +0,0 @@ -import { test as base, Dialog, expect, Page } from '@playwright/test'; -import { ensureValidAuth, getAuthPath } from '../../utils/auth'; -import { readFileSync } from 'fs'; - -var title: string; -var postId: number; - -const dragAndDropFile = async ( - page: Page, - selector: string, - filePath: string, - fileName: string, - fileType = '' -) => { - const buffer = readFileSync(filePath).toString('base64'); - - const dataTransfer = await page.evaluateHandle( - async ({ bufferData, localFileName, localFileType }) => { - const dt = new DataTransfer(); - - const blobData = await fetch(bufferData).then((res) => res.blob()); - - const file = new File([blobData], localFileName, { type: localFileType }); - dt.items.add(file); - return dt; - }, - { - bufferData: `data:application/octet-stream;base64,${buffer}`, - localFileName: fileName, - localFileType: fileType, - } - ); - - await page.dispatchEvent(selector, 'drop', { dataTransfer }); -}; - -// Create a fixture for authentication -const test = base.extend({ - contribContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('testUserContributor') - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'testUserContributor', 'password'); - await use(context); - await page.close(); - await context.close(); - } -}); - -test.describe('Create a new complete recipe front-end (contrib user)', () => { - test('Create a new recipe (contrib user)', async ({ contribContext }) => { - const contribPage = await contribContext.newPage(); - await contribPage.goto('/profile/add/', { waitUntil: 'networkidle' }); - title = 'Test Recipe Playwright: ' + Date.now(); - - // Set recipe title - await contribPage.fill('input[name="_recipe_settings[post_title]"]', title); - - // Set difficulty level - await contribPage.selectOption('select[name="_recipe_settings[difficulty_level]"]', '1'); // Beginner - - // Set times and servings - await contribPage.fill('input[name="_recipe_settings[prep_time]"]', '15'); - await contribPage.fill('input[name="_recipe_settings[cook_time]"]', '30'); - - // Click on the Nutrition tab to make those fields visible - await contribPage.click('.cooked-add-nutrition-button', { force: true }); - await contribPage.fill('input[name="_recipe_settings[nutrition][servings]"]', '4'); - - // Set recipe taxonomies - await contribPage.selectOption('select[name="_recipe_settings[category]"]', 'bread'); - await contribPage.selectOption('select[name="_recipe_settings[cooking_method]"]', 'baking'); - await contribPage.selectOption('select[name="_recipe_settings[cuisine]"]', 'albanian'); - await contribPage.selectOption('select[name="_recipe_settings[diet]"]', 'vegetarian'); - - await dragAndDropFile(contribPage, "#featured_image", "tests/_files/icon_pro.png", "icon_pro.png", "image/png"); - - await contribPage.fill('textarea[name="_recipe_settings[excerpt]"]', 'This is a brief description of the recipe.'); - await contribPage.fill('textarea[name="_recipe_settings[notes]"]', 'Important notes about this recipe.'); - - // Handle all WYSIWYG editors - await contribPage.evaluate(() => { - // Add ingredients - const addIngredientButton = document.querySelector('.cooked-add-ingredient-button'); - if (addIngredientButton) { - (addIngredientButton as HTMLElement).click(); - } - - // Find the first ingredient fields using regex - const ingredientBlocks = document.querySelectorAll('.cooked-ingredient-block'); - if (ingredientBlocks.length > 0) { - const firstIngredient = ingredientBlocks[0]; - const amountInput = firstIngredient.querySelector('input[data-ingredient-part="amount"]') as HTMLInputElement; - const measurementSelect = firstIngredient.querySelector('select[data-ingredient-part="measurement"]') as HTMLSelectElement; - const itemInput = firstIngredient.querySelector('input[data-ingredient-part="name"]') as HTMLInputElement; - - if (amountInput) amountInput.value = '2'; - if (measurementSelect) { - // Set the measurement to "cups" - measurementSelect.value = 'cup'; - } - if (itemInput) itemInput.value = 'flour'; - } - - // Add a direction step - const addDirectionButton = document.querySelector('.cooked-add-direction-button'); - if (addDirectionButton) { - (addDirectionButton as HTMLElement).click(); - } - - // Find and fill the direction textarea - return new Promise((resolve) => { - const checkTextarea = setInterval(() => { - const directionTextareas = document.querySelectorAll('textarea[data-direction-part="content"]'); - - if (directionTextareas.length > 0) { - const firstDirectionTextarea = directionTextareas[0] as HTMLTextAreaElement; - firstDirectionTextarea.value = 'First step of the recipe.'; - clearInterval(checkTextarea); - resolve(true); - } - }, 500); - - // Set a timeout to prevent infinite checking - setTimeout(() => { - clearInterval(checkTextarea); - resolve(false); - }, 10000); // Maximum 10 second timeout - }); - - }); - - // Click the publish button first - await contribPage.getByRole('button', { name: 'Submit Recipe', exact: true }).click(); - - // Wait for both URL change and success message - await Promise.all([ - // Wait for URL to change to post edit page - contribPage.waitForURL('/profile'), - // Wait for success message - contribPage.waitForSelector('.cooked-success-banner', { timeout: 10000 }) - ]); - - // Check for success message content - await expect(contribPage.locator('.cooked-success-banner')).toContainText('You have successfully submitted a new recipe. It is now pending approval.'); - - // After successful submission, get the recipe ID from the edit button URL - const editButton = contribPage.locator('.cooked-edit-button').first(); - const href = await editButton.getAttribute('href'); - postId = parseInt(href?.match(/edit-recipe\/(\d+)/)?.[1] || ''); - - expect(postId).toBeTruthy(); - }); - - test('View the recipe (frontend)', async ({ contribContext }) => { - const contribPage = await contribContext.newPage(); - await contribPage.goto('/?post_type=cp_recipe&p=' + postId, { waitUntil: 'networkidle' }); - - // Check for recipe title in h1.entry-title - await expect(contribPage.locator('h1.entry-title')).toHaveText(title); - - // Check for pending message - await expect(contribPage.getByText('This recipe is pending review. No one else can see it yet.')).toBeVisible(); - }); - - test('Edit the recipe (frontend)', async ({ contribContext }) => { - const contribPage = await contribContext.newPage(); - // Get the frontend URL using WP-CLI - if (!postId) { - throw new Error('Post ID is not set'); - } - - await contribPage.goto('/profile/edit-recipe/' + postId + '/', { waitUntil: 'networkidle' }); - - await expect(contribPage.getByText(title)).toBeDefined(); - - // Change the title - title = 'Test Recipe Playwright - Edited - ' + Date.now(); - await contribPage.fill('input[name="_recipe_settings[post_title]"]', title); - - // Click the publish button first - await contribPage.getByRole('button', { name: 'Update Recipe', exact: true }).click(); - - // Wait for both URL change and success message - await Promise.all([ - // Wait for URL to change to post edit page - contribPage.waitForURL('/profile'), - // Wait for success message - contribPage.waitForSelector('.cooked-success-banner', { timeout: 10000 }) - ]); - - // Check for success message content - await expect(contribPage.locator('.cooked-success-banner')).toContainText('You have successfully edited the recipe. It is now pending approval.'); - - await contribPage.close(); - }); -}); - -// Delete the recipe -test.describe('Delete the recipe (contrib user)', () => { - test('Delete the recipe (contrib user)', async ({ contribContext }) => { - const contribPage = await contribContext.newPage(); - await contribPage.goto('/profile', { waitUntil: 'networkidle' }); - - const deleteButton = contribPage.locator('.cooked-delete-button').first(); - await deleteButton.click(); - - // Wait for confirmation dialog to appear - await expect(contribPage.locator('.cooked-confirm-block .cooked-delete-final').first()).toBeVisible({ timeout: 10000 }); - - // First, handle the confirm dialog that will appear - contribPage.on('dialog', async (dialog: Dialog) => { - // Automatically accept the confirmation - await dialog.accept(); - }); - - // OR alternatively, use dispatchEvent to trigger a native click - await contribPage.evaluate(() => { - const button = document.querySelector('.cooked-delete-button.cooked-delete-final') as HTMLElement; - if (button) { - button.dispatchEvent(new MouseEvent('click', { - bubbles: true, - cancelable: true, - view: window - })); - } - }); - - // Wait for the recipe to be deleted - await contribPage.waitForTimeout(1000); - - // Verify the recipe title no longer exists in the list - await expect(contribPage.getByText(title)).not.toBeVisible(); - - // Verify by trying to access the recipe URL directly - const response = await contribPage.goto('/?post_type=cp_recipe&p=' + postId, { waitUntil: 'networkidle' }); - - expect(response?.status()).toBe(404); - }); -}); diff --git a/tests/playwright/tests/2_contrib_user/2-rate-recipe.spec.ts b/tests/playwright/tests/2_contrib_user/2-rate-recipe.spec.ts deleted file mode 100644 index 75b9a90..0000000 --- a/tests/playwright/tests/2_contrib_user/2-rate-recipe.spec.ts +++ /dev/null @@ -1,235 +0,0 @@ -import { test as base, expect } from '@playwright/test'; -import { ensureValidAuth, getAuthPath } from '../../utils/auth'; -import { execSync } from 'child_process'; - -var postId: number; -var title: string; - -declare global { - interface Window { - tinyMCE: { - get: (id: string) => { - setContent: (content: string) => void; - }; - editors: { [key: string]: any }; - }; - } -} - -// Create a fixture for authentication -const test = base.extend({ - adminContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('mtresova') - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'mtresova', 'password'); - await use(context); - await page.close(); - await context.close(); - }, - contribContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('testUserContributor') - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'testUserContributor', 'password'); - await use(context); - await page.close(); - await context.close(); - } -}); - -test.describe('Admin Star Ratings Settings (admin user)', () => { - test('Enable Star Ratings (admin user)', async ({ adminContext }) => { - const adminPage = await adminContext.newPage(); - await adminPage.goto('/wp-admin/admin.php?page=cooked_settings#engagement', { waitUntil: 'networkidle' }); - - const select = adminPage.locator('select[name="cooked_settings[rating_type]"]'); - const currentValue = await select.evaluate((el) => (el as HTMLSelectElement).value); - if (currentValue !== 'stars') { - await select.selectOption('stars'); - } - - await adminPage.getByRole('button', { name: 'Update Settings' }).click(); - await expect(adminPage.getByText('Cooked settings has been updated!')).toBeDefined(); - }); -}); - -test.describe('Create a new complete recipe (admin)', () => { - test('Create a new recipe', async ({ adminContext }) => { - const adminPage = await adminContext.newPage(); - await adminPage.goto('/wp-admin/post-new.php?post_type=cp_recipe', { waitUntil: 'networkidle' }); - title = 'Test Recipe Playwright: ' + Date.now(); - - // Set recipe title - await adminPage.getByLabel('Recipe title ...').fill(title); - - // Set difficulty level - await adminPage.selectOption('select[name="_recipe_settings[difficulty_level]"]', '1'); // Beginner - - // Set times and servings - await adminPage.fill('input[name="_recipe_settings[prep_time]"]', '15'); - await adminPage.fill('input[name="_recipe_settings[cook_time]"]', '30'); - // await adminPage.fill('input[name="_recipe_settings[total_time]"]', '45'); - - // Click on the Nutrition tab to make those fields visible - await adminPage.click('#cooked-recipe-tab-nutrition', { force: true }); - await adminPage.fill('input[name="_recipe_settings[nutrition][servings]"]', '4'); - - // Set recipe taxonomies (using checkboxes) - await adminPage.check('input[name="tax_input[cp_recipe_category][]"][value="298"]'); - await adminPage.check('input[name="tax_input[cp_recipe_cooking_method][]"][value="288"]'); - await adminPage.check('input[name="tax_input[cp_recipe_cuisine][]"][value="279"]'); - await adminPage.check('input[name="tax_input[cp_recipe_diet][]"][value="268"]'); - - // Set recipe tags (can be multiple) - await adminPage.locator('input[name="newtag[cp_recipe_tags]"]').fill('butter, egg, flour'); - await adminPage.locator('.button.tagadd').first().click(); - - // Click the Set featured image link - await adminPage.locator('#set-post-thumbnail').click(); - - // Wait for the media modal to appear - await adminPage.waitForSelector('.media-modal-content'); - - // If you want to upload a new image, use this: - // await adminPage.setInputFiles('input[type="file"]', 'path/to/your/image.jpg'); - // await adminPage.getByRole('button', { name: 'Upload' }).click(); - - // Or to select an existing image from the media library: - await adminPage.getByRole('tab', { name: 'Media Library' }).click(); - await adminPage.waitForSelector('.attachment-preview'); - - // Select the first image in the media library - await adminPage.locator('.attachment-preview').first().click(); - - // Click the "Set Featured Image" button in the modal - await adminPage.getByRole('button', { name: 'Set Featured Image' }).click(); - - // Wait for the featured image to be set - await adminPage.waitForSelector('#remove-post-thumbnail'); - - // Handle all WYSIWYG editors - await adminPage.evaluate(() => { - // Excerpt editor - const excerptEditor = window.tinyMCE.get('_recipe_settings_excerpt'); - if (excerptEditor) { - excerptEditor.setContent('This is a brief description of the recipe.'); - } - - // Notes editor - const notesEditor = window.tinyMCE.get('_recipe_settings_notes'); - if (notesEditor) { - notesEditor.setContent('Important notes about this recipe.'); - } - - // Add ingredients - // const addIngredientButton = document.querySelector('.cooked-add-ingredient-button'); - // if (addIngredientButton) { - // (addIngredientButton as HTMLElement).click(); - // } - - // Find the first ingredient fields using regex - const ingredientBlocks = document.querySelectorAll('.cooked-ingredient-block'); - if (ingredientBlocks.length > 0) { - const firstIngredient = ingredientBlocks[0]; - const amountInput = firstIngredient.querySelector('input[data-ingredient-part="amount"]') as HTMLInputElement; - const measurementSelect = firstIngredient.querySelector('select[data-ingredient-part="measurement"]') as HTMLSelectElement; - const itemInput = firstIngredient.querySelector('input[data-ingredient-part="name"]') as HTMLInputElement; - - if (amountInput) amountInput.value = '2'; - if (measurementSelect) { - // Set the measurement to "cups" - measurementSelect.value = 'cup'; - } - if (itemInput) itemInput.value = 'flour'; - } - - // Add a direction step - // const addDirectionButton = document.querySelector('.cooked-add-direction-button'); - // if (addDirectionButton) { - // (addDirectionButton as HTMLElement).click(); - // } - - // Find the first direction editor using regex - const directionEditors = Object.keys(window.tinyMCE.editors).filter(id => /^direction-\d+-content$/.test(id)); - if (directionEditors.length > 0) { - const directionEditor = window.tinyMCE.get(directionEditors[0]); - if (directionEditor) { - directionEditor.setContent('First step of the recipe.'); - } - } - }); - - // Click the publish button first - await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - - // Wait for both URL change and success message - await Promise.all([ - // Wait for URL to change to post edit page - adminPage.waitForURL(/post\.php\?post=\d+&action=edit/), - // Wait for success message - adminPage.waitForSelector('.notice-success', { timeout: 10000 }) - ]); - - // Check for success message content - await expect(adminPage.locator('.notice-success')).toContainText('Post published.'); - - // Get the post ID from the URL - const url = adminPage.url(); - const postIdMatch = url.match(/post=(\d+)/); - postId = postIdMatch ? parseInt(postIdMatch[1]) : 0; - - await adminPage.close(); - }); -}); - -test.describe('Rate a recipe (contrib user)', () => { - test('Rate the recipe (contrib user)', async ({ contribContext }) => { - // Contributor rates the recipe - const contribPage = await contribContext.newPage(); - - // Get the frontend URL using WP-CLI - if (!postId) { - throw new Error('Post ID is not set'); - } - - await contribPage.goto('/?post_type=cp_recipe&p=' + postId, { waitUntil: 'networkidle' }); - await expect(contribPage.getByText(title)).toBeDefined(); - - // Click on the 3-star rating button - await contribPage.locator('.cooked-rating-stars .cooked-rating-choice[data-rating-value="3"]').click(); - - // Add a small delay between ratings if needed - await contribPage.waitForTimeout(1000); // 1 second delay - - // Verify the average rating is 3.0 - await expect(contribPage.locator('.cooked-current-rating')).toHaveText('3.0'); - - // Now change to 5-star rating - await contribPage.locator('.cooked-rating-stars .cooked-rating-choice[data-rating-value="5"]').click(); - - // Add a small delay between ratings if needed - await contribPage.waitForTimeout(1000); // 1 second delay - - // Verify the average rating is now 5.0 - await expect(contribPage.locator('.cooked-current-rating')).toHaveText('5.0'); - - await contribPage.close(); - }); -}); - -// Cleanup task - Delete the recipe -test.afterAll(async () => { - if (postId) { - try { - console.log(`Cleaning up: Deleting recipe ${postId}`); - execSync(`wp post delete ${postId} --force`); - } catch (error) { - console.error(`Failed to delete recipe ${postId}:`, error); - } - } else { - console.log('No post ID to delete'); - } -}); diff --git a/tests/playwright/tests/2_contrib_user/3-favorite-recipe.spec.ts b/tests/playwright/tests/2_contrib_user/3-favorite-recipe.spec.ts deleted file mode 100644 index 689dce1..0000000 --- a/tests/playwright/tests/2_contrib_user/3-favorite-recipe.spec.ts +++ /dev/null @@ -1,217 +0,0 @@ -import { test as base, expect } from '@playwright/test'; -import { ensureValidAuth, getAuthPath } from '../../utils/auth'; -import { execSync } from 'child_process'; - -var postId: number; -var title: string; - -declare global { - interface Window { - tinyMCE: { - get: (id: string) => { - setContent: (content: string) => void; - }; - editors: { [key: string]: any }; - }; - } -} - -// Create a fixture for authentication -const test = base.extend({ - adminContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('mtresova') - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'mtresova', 'password'); - await use(context); - await page.close(); - await context.close(); - }, - contribContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('testUserContributor') - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'testUserContributor', 'password'); - await use(context); - await page.close(); - await context.close(); - } -}); - -test.describe('Create a new complete recipe (admin)', () => { - test('Create a new recipe', async ({ adminContext }) => { - const adminPage = await adminContext.newPage(); - await adminPage.goto('/wp-admin/post-new.php?post_type=cp_recipe', { waitUntil: 'networkidle' }); - title = 'Test Recipe Playwright: ' + Date.now(); - - // Set recipe title - await adminPage.getByLabel('Recipe title ...').fill(title); - - // Set difficulty level - await adminPage.selectOption('select[name="_recipe_settings[difficulty_level]"]', '1'); // Beginner - - // Set times and servings - await adminPage.fill('input[name="_recipe_settings[prep_time]"]', '15'); - await adminPage.fill('input[name="_recipe_settings[cook_time]"]', '30'); - // await page.fill('input[name="_recipe_settings[total_time]"]', '45'); - - // Click on the Nutrition tab to make those fields visible - await adminPage.click('#cooked-recipe-tab-nutrition', { force: true }); - await adminPage.fill('input[name="_recipe_settings[nutrition][servings]"]', '4'); - - // Set recipe taxonomies (using checkboxes) - await adminPage.check('input[name="tax_input[cp_recipe_category][]"][value="298"]'); - await adminPage.check('input[name="tax_input[cp_recipe_cooking_method][]"][value="288"]'); - await adminPage.check('input[name="tax_input[cp_recipe_cuisine][]"][value="279"]'); - await adminPage.check('input[name="tax_input[cp_recipe_diet][]"][value="268"]'); - - // Set recipe tags (can be multiple) - await adminPage.locator('input[name="newtag[cp_recipe_tags]"]').fill('butter, egg, flour'); - await adminPage.locator('.button.tagadd').first().click(); - - // Click the Set featured image link - await adminPage.locator('#set-post-thumbnail').click(); - - // Wait for the media modal to appear - await adminPage.waitForSelector('.media-modal-content'); - - // If you want to upload a new image, use this: - // await page.setInputFiles('input[type="file"]', 'path/to/your/image.jpg'); - // await page.getByRole('button', { name: 'Upload' }).click(); - - // Or to select an existing image from the media library: - await adminPage.getByRole('tab', { name: 'Media Library' }).click(); - await adminPage.waitForSelector('.attachment-preview'); - - // Select the first image in the media library - await adminPage.locator('.attachment-preview').first().click(); - - // Click the "Set Featured Image" button in the modal - await adminPage.getByRole('button', { name: 'Set Featured Image' }).click(); - - // Wait for the featured image to be set - await adminPage.waitForSelector('#remove-post-thumbnail'); - - // Handle all WYSIWYG editors - await adminPage.evaluate(() => { - // Excerpt editor - const excerptEditor = window.tinyMCE.get('_recipe_settings_excerpt'); - if (excerptEditor) { - excerptEditor.setContent('This is a brief description of the recipe.'); - } - - // Notes editor - const notesEditor = window.tinyMCE.get('_recipe_settings_notes'); - if (notesEditor) { - notesEditor.setContent('Important notes about this recipe.'); - } - - // Add ingredients - // const addIngredientButton = document.querySelector('.cooked-add-ingredient-button'); - // if (addIngredientButton) { - // (addIngredientButton as HTMLElement).click(); - // } - - // Find the first ingredient fields using regex - const ingredientBlocks = document.querySelectorAll('.cooked-ingredient-block'); - if (ingredientBlocks.length > 0) { - const firstIngredient = ingredientBlocks[0]; - const amountInput = firstIngredient.querySelector('input[data-ingredient-part="amount"]') as HTMLInputElement; - const measurementSelect = firstIngredient.querySelector('select[data-ingredient-part="measurement"]') as HTMLSelectElement; - const itemInput = firstIngredient.querySelector('input[data-ingredient-part="name"]') as HTMLInputElement; - - if (amountInput) amountInput.value = '2'; - if (measurementSelect) { - // Set the measurement to "cups" - measurementSelect.value = 'cup'; - } - if (itemInput) itemInput.value = 'flour'; - } - - // Add a direction step - // const addDirectionButton = document.querySelector('.cooked-add-direction-button'); - // if (addDirectionButton) { - // (addDirectionButton as HTMLElement).click(); - // } - - // Find the first direction editor using regex - const directionEditors = Object.keys(window.tinyMCE.editors).filter(id => /^direction-\d+-content$/.test(id)); - if (directionEditors.length > 0) { - const directionEditor = window.tinyMCE.get(directionEditors[0]); - if (directionEditor) { - directionEditor.setContent('First step of the recipe.'); - } - } - }); - - // Click the publish button first - await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - - // Wait for both URL change and success message - await Promise.all([ - // Wait for URL to change to post edit page - adminPage.waitForURL(/post\.php\?post=\d+&action=edit/), - // Wait for success message - adminPage.waitForSelector('.notice-success', { timeout: 10000 }) - ]); - - // Check for success message content - await expect(adminPage.locator('.notice-success')).toContainText('Post published.'); - - // Get the post ID from the URL - const url = adminPage.url(); - const postIdMatch = url.match(/post=(\d+)/); - postId = postIdMatch ? parseInt(postIdMatch[1]) : 0; - }); -}); - -test.describe('Favorite a recipe (contrib user)', () => { - test('Favorite the recipe (frontend)', async ({ contribContext }) => { - // Get the frontend URL using WP-CLI - if (!postId) { - throw new Error('Post ID is not set'); - } - - const contribPage = await contribContext.newPage(); - - await contribPage.goto('/?post_type=cp_recipe&p=' + postId, { waitUntil: 'networkidle' }); - await expect(contribPage.getByText(title)).toBeDefined(); - - // Get initial favorite count - const initialCount = await contribPage.locator('.cooked-favorite-total').textContent(); - - // Click the favorite heart - await contribPage.locator('.cooked-favorite-heart').click(); - - // Wait for the favorite action to complete and verify count increased - await expect(contribPage.locator('.cooked-favorite-total')).toHaveText(String(Number(initialCount) + 1)); - - // Verify the heart is now in "favorited" state (if there's a CSS class change) - await expect(contribPage.locator('.cooked-favorite-heart')).toHaveClass(/cooked-is-favorite/); - - // Now unfavorite - await contribPage.locator('.cooked-favorite-heart').click(); - - // Verify count decreased back to original - await expect(contribPage.locator('.cooked-favorite-total')).toHaveText(initialCount || ''); - - // Verify the heart is back to unfavorited state - await expect(contribPage.locator('.cooked-favorite-heart')).not.toHaveClass(/cooked-is-favorite/); - }); -}); - -// Cleanup task - Delete the recipe -test.afterAll(async () => { - if (postId) { - try { - console.log(`Cleaning up: Deleting recipe ${postId}`); - execSync(`wp post delete ${postId} --force`); - } catch (error) { - console.error(`Failed to delete recipe ${postId}:`, error); - } - } else { - console.log('No post ID to delete'); - } -}); diff --git a/tests/playwright/tests/3_anon_user/1-create-recipe.spec.ts copy.bak b/tests/playwright/tests/3_anon_user/1-create-recipe.spec.ts copy.bak deleted file mode 100644 index 0465fb5..0000000 --- a/tests/playwright/tests/3_anon_user/1-create-recipe.spec.ts copy.bak +++ /dev/null @@ -1,69 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { ensureValidAuth, authFile } from '../../utils/auth'; - -var postId: number; - -// test.afterEach('Close the page', async ({ page }) => { -// // logout before closing the page -// await page.close(); -// }); - -// Create a new recipe -test.describe('Create a new recipe (admin)', () => { - test.beforeEach(async ({ page }) => { - await ensureValidAuth(page, 'mtresova', 'password'); - }); - - test.use({ storageState: authFile }); - - test('Create a new recipe', async ({ page }) => { - await page.goto('/wp-admin/post-new.php?post_type=cp_recipe', { waitUntil: 'networkidle' }); - await page.getByLabel('Recipe title ...').fill('Test Recipe Playwright - ' + Date.now()); - - // Submit the form directly - // const form = page.locator('#post'); - // await form.evaluate(form => (form as HTMLFormElement).submit()); - - // Wait for 1 seconds - await page.waitForTimeout(1000); - - await page.getByRole('button', { name: 'Publish', exact: true }).click(); - - // await page.getByRole('button', { name: 'Publish', exact: true }).focus(); - // await page.keyboard.press("Enter"); - - // await page.pause(); - - // await page.locator('#publish').click(); - await page.waitForNavigation({ waitUntil: 'networkidle' }); - - // Check for success message - await expect(page.locator('.notice-success')).toBeVisible(); - await expect(page.locator('.notice-success')).toContainText('Post published.'); - - // Get the post ID - const url = page.url(); - const urlParts = url.split('/'); - postId = parseInt(urlParts[urlParts.length - 1]); - }); -}); - -// test.describe('Check that the recipe is created (guest)', () => { -// test.use({ storageState: { cookies: [], origins: [] } }); - -// test('Check that the recipe is created', async ({ page }) => { -// await page.goto('/recipes/test-recipe-playwright'); -// await expect(page.getByText('Test Recipe Playwright')).toBeDefined(); -// }); -// }); - -// Delete the recipe -// test.describe('Delete the recipe', () => { -// test.use({ storageState: authFile }); - -// test('Delete the recipe', async ({ page }) => { -// await page.goto(`/wp-admin/post.php?post=${postId}&action=trash`); -// await expect(page.locator('.notice-success')).toBeVisible(); -// await expect(page.locator('.notice-success')).toContainText('Post moved to the Trash.'); -// }); -// }); diff --git a/tests/playwright/tests/3_anon_user/1-view-recipe.spec.ts.bak b/tests/playwright/tests/3_anon_user/1-view-recipe.spec.ts.bak deleted file mode 100644 index 855f8dc..0000000 --- a/tests/playwright/tests/3_anon_user/1-view-recipe.spec.ts.bak +++ /dev/null @@ -1,71 +0,0 @@ -import { test as base, expect } from '@playwright/test'; -import { ensureValidAuth, getAuthPath } from '../../utils/auth'; -import path from 'path'; -import { execSync } from 'child_process'; - -var postId: number; - -// test.afterEach('Close the page', async ({ page }) => { -// // logout before closing the page -// await page.close(); -// }); - -// Create a new recipe -test.describe('Create a new recipe (admin)', () => { - test.beforeEach(async ({ page }) => { - await ensureValidAuth(page, 'mtresova', 'password'); - }); - - test.use({ storageState: authFile }); - - test('Create a new recipe', async ({ page }) => { - await page.goto('/wp-admin/post-new.php?post_type=cp_recipe', { waitUntil: 'networkidle' }); - await page.getByLabel('Recipe title ...').fill('Test Recipe Playwright - ' + Date.now()); - - // Submit the form directly - // const form = page.locator('#post'); - // await form.evaluate(form => (form as HTMLFormElement).submit()); - - // Wait for 1 seconds - await page.waitForTimeout(1000); - - await page.getByRole('button', { name: 'Publish', exact: true }).click(); - - // await page.getByRole('button', { name: 'Publish', exact: true }).focus(); - // await page.keyboard.press("Enter"); - - // await page.pause(); - - // await page.locator('#publish').click(); - await page.waitForNavigation({ waitUntil: 'networkidle' }); - - // Check for success message - await expect(page.locator('.notice-success')).toBeVisible(); - await expect(page.locator('.notice-success')).toContainText('Post published.'); - - // Get the post ID - const url = page.url(); - const urlParts = url.split('/'); - postId = parseInt(urlParts[urlParts.length - 1]); - }); -}); - -// test.describe('Check that the recipe is created (guest)', () => { -// test.use({ storageState: { cookies: [], origins: [] } }); - -// test('Check that the recipe is created', async ({ page }) => { -// await page.goto('/recipes/test-recipe-playwright'); -// await expect(page.getByText('Test Recipe Playwright')).toBeDefined(); -// }); -// }); - -// Delete the recipe -// test.describe('Delete the recipe', () => { -// test.use({ storageState: authFile }); - -// test('Delete the recipe', async ({ page }) => { -// await page.goto(`/wp-admin/post.php?post=${postId}&action=trash`); -// await expect(page.locator('.notice-success')).toBeVisible(); -// await expect(page.locator('.notice-success')).toContainText('Post moved to the Trash.'); -// }); -// }); diff --git a/tests/playwright/tests/3_anon_user/2-star-rate-recipe.spec.ts b/tests/playwright/tests/3_anon_user/2-star-rate-recipe.spec.ts deleted file mode 100644 index bcad1d2..0000000 --- a/tests/playwright/tests/3_anon_user/2-star-rate-recipe.spec.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { test as base, expect } from '@playwright/test'; -import { ensureValidAuth, getAuthPath } from '../../utils/auth'; -import { execSync } from 'child_process'; - -var postId: number; -var title: string; - -declare global { - interface Window { - tinyMCE: { - get: (id: string) => { - setContent: (content: string) => void; - }; - editors: { [key: string]: any }; - }; - } -} - -// Create a fixture for authentication -const test = base.extend({ - adminContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('mtresova') - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'mtresova', 'password'); - await use(context); - await page.close(); - await context.close(); - }, - contribContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('testUserContributor') - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'testUserContributor', 'password'); - await use(context); - await page.close(); - await context.close(); - } -}); - -test.describe('Admin Guest Ratings Settings', () => { - test('Enable Guest and Star Ratings', async ({ adminContext }) => { - const adminPage = await adminContext.newPage(); - await adminPage.goto('/wp-admin/admin.php?page=cooked_settings#engagement', { waitUntil: 'networkidle' }); - - await expect(adminPage.getByRole('checkbox', { name: 'cooked_settings[enable_guest_ratings][]' })).toBeDefined(); - - // Check if the checkbox is checked first before clicking it. - let switchery = adminPage.locator('input[name="cooked_settings[enable_guest_ratings][]"] ~ span.switchery'); - const checkbox = adminPage.locator('input[name="cooked_settings[enable_guest_ratings][]"]'); - if (!(await checkbox.isChecked())) { - await switchery.click(); - } - - const select = adminPage.locator('select[name="cooked_settings[rating_type]"]'); - const currentValue = await select.evaluate((el) => (el as HTMLSelectElement).value); - if (currentValue !== 'stars') { - await select.selectOption('stars'); - } - - await adminPage.getByRole('button', { name: 'Update Settings' }).click(); - await expect(adminPage.getByText('Cooked settings has been updated!')).toBeDefined(); - }); -}); - -test.describe('Create a new complete recipe (admin)', () => { - test('Create a new recipe', async ({ adminContext }) => { - const adminPage = await adminContext.newPage(); - await adminPage.goto('/wp-admin/post-new.php?post_type=cp_recipe', { waitUntil: 'networkidle' }); - title = 'Test Recipe Playwright - ' + Date.now(); - - // Set recipe title - await adminPage.getByLabel('Recipe title ...').fill(title); - - // Set difficulty level - await adminPage.selectOption('select[name="_recipe_settings[difficulty_level]"]', '1'); // Beginner - - // Set times and servings - await adminPage.fill('input[name="_recipe_settings[prep_time]"]', '15'); - await adminPage.fill('input[name="_recipe_settings[cook_time]"]', '30'); - // await adminPage.fill('input[name="_recipe_settings[total_time]"]', '45'); - - // Click on the Nutrition tab to make those fields visible - await adminPage.click('#cooked-recipe-tab-nutrition', { force: true }); - await adminPage.fill('input[name="_recipe_settings[nutrition][servings]"]', '4'); - - // Set recipe taxonomies (using checkboxes) - await adminPage.check('input[name="tax_input[cp_recipe_category][]"][value="298"]'); - await adminPage.check('input[name="tax_input[cp_recipe_cooking_method][]"][value="288"]'); - await adminPage.check('input[name="tax_input[cp_recipe_cuisine][]"][value="279"]'); - await adminPage.check('input[name="tax_input[cp_recipe_diet][]"][value="268"]'); - - // Set recipe tags (can be multiple) - await adminPage.locator('input[name="newtag[cp_recipe_tags]"]').fill('butter, egg, flour'); - await adminPage.locator('.button.tagadd').first().click(); - - // Click the Set featured image link - await adminPage.locator('#set-post-thumbnail').click(); - - // Wait for the media modal to appear - await adminPage.waitForSelector('.media-modal-content'); - - // If you want to upload a new image, use this: - // await adminPage.setInputFiles('input[type="file"]', 'path/to/your/image.jpg'); - // await adminPage.getByRole('button', { name: 'Upload' }).click(); - - // Or to select an existing image from the media library: - await adminPage.getByRole('tab', { name: 'Media Library' }).click(); - await adminPage.waitForSelector('.attachment-preview'); - - // Select the first image in the media library - await adminPage.locator('.attachment-preview').first().click(); - - // Click the "Set Featured Image" button in the modal - await adminPage.getByRole('button', { name: 'Set Featured Image' }).click(); - - // Wait for the featured image to be set - await adminPage.waitForSelector('#remove-post-thumbnail'); - - // Handle all WYSIWYG editors - await adminPage.evaluate(() => { - // Excerpt editor - const excerptEditor = window.tinyMCE.get('_recipe_settings_excerpt'); - if (excerptEditor) { - excerptEditor.setContent('This is a brief description of the recipe.'); - } - - // Notes editor - const notesEditor = window.tinyMCE.get('_recipe_settings_notes'); - if (notesEditor) { - notesEditor.setContent('Important notes about this recipe.'); - } - - // Add ingredients - // const addIngredientButton = document.querySelector('.cooked-add-ingredient-button'); - // if (addIngredientButton) { - // (addIngredientButton as HTMLElement).click(); - // } - - // Find the first ingredient fields using regex - const ingredientBlocks = document.querySelectorAll('.cooked-ingredient-block'); - if (ingredientBlocks.length > 0) { - const firstIngredient = ingredientBlocks[0]; - const amountInput = firstIngredient.querySelector('input[data-ingredient-part="amount"]') as HTMLInputElement; - const measurementSelect = firstIngredient.querySelector('select[data-ingredient-part="measurement"]') as HTMLSelectElement; - const itemInput = firstIngredient.querySelector('input[data-ingredient-part="name"]') as HTMLInputElement; - - if (amountInput) amountInput.value = '2'; - if (measurementSelect) { - // Set the measurement to "cups" - measurementSelect.value = 'cup'; - } - if (itemInput) itemInput.value = 'flour'; - } - - // Add a direction step - // const addDirectionButton = document.querySelector('.cooked-add-direction-button'); - // if (addDirectionButton) { - // (addDirectionButton as HTMLElement).click(); - // } - - // Find the first direction editor using regex - const directionEditors = Object.keys(window.tinyMCE.editors).filter(id => /^direction-\d+-content$/.test(id)); - if (directionEditors.length > 0) { - const directionEditor = window.tinyMCE.get(directionEditors[0]); - if (directionEditor) { - directionEditor.setContent('First step of the recipe.'); - } - } - }); - - // Click the publish button first - await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - - // Wait for both URL change and success message - await Promise.all([ - // Wait for URL to change to post edit page - adminPage.waitForURL(/post\.php\?post=\d+&action=edit/), - // Wait for success message - adminPage.waitForSelector('.notice-success', { timeout: 10000 }) - ]); - - // Check for success message content - await expect(adminPage.locator('.notice-success')).toContainText('Post published.'); - - // Get the post ID from the URL - const url = adminPage.url(); - const postIdMatch = url.match(/post=(\d+)/); - postId = postIdMatch ? parseInt(postIdMatch[1]) : 0; - }); -}); - -test.describe('Rate a recipe (anonymous user)', () => { - test.use({ storageState: { cookies: [], origins: [] } }); - - test('Rate the recipe (anonymous user)', async ({ page }) => { - // Get the frontend URL using WP-CLI - if (!postId) { - throw new Error('Post ID is not set'); - } - - await page.goto('/?post_type=cp_recipe&p=' + postId, { waitUntil: 'networkidle' }); - await expect(page.getByText(title)).toBeDefined(); - - // Click on the 3-star rating button - await page.locator('span.cooked-rating-stars .cooked-rating-choice[data-rating-value="3"]').click(); - - // Add a small delay between ratings if needed - await page.waitForTimeout(1000); // 1 second delay - - // Verify the average rating is 4.0 - await expect(page.locator('.cooked-current-rating')).toHaveText('3.0'); - - // Now change to 5-star rating - await page.locator('.cooked-rating-stars .cooked-rating-choice[data-rating-value="5"]').click(); - - // Add a small delay between ratings if needed - await page.waitForTimeout(1000); // 1 second delay - - // Verify the average rating is now 5.0 - await expect(page.locator('.cooked-current-rating')).toHaveText('5.0'); - }); -}); - -// Cleanup task - Delete the recipe -test.afterAll(async () => { - if (postId) { - try { - console.log(`Cleaning up: Deleting recipe ${postId}`); - execSync(`wp post delete ${postId} --force`); - } catch (error) { - console.error(`Failed to delete recipe ${postId}:`, error); - } - } else { - console.log('No post ID to delete'); - } -}); diff --git a/tests/playwright/tests/3_anon_user/2-thumb-rate-recipe.spec.ts b/tests/playwright/tests/3_anon_user/2-thumb-rate-recipe.spec.ts deleted file mode 100644 index 38a9649..0000000 --- a/tests/playwright/tests/3_anon_user/2-thumb-rate-recipe.spec.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { test as base, expect } from '@playwright/test'; -import { ensureValidAuth, getAuthPath } from '../../utils/auth'; -import { execSync } from 'child_process'; - -var postId: number; -var title: string; - -declare global { - interface Window { - tinyMCE: { - get: (id: string) => { - setContent: (content: string) => void; - }; - editors: { [key: string]: any }; - }; - } -} - -// Create a fixture for authentication -const test = base.extend({ - adminContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('mtresova') - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'mtresova', 'password'); - await use(context); - }, - contribContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('testUserContributor') - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'testUserContributor', 'password'); - await use(context); - } -}); - -test.describe('Admin Guest and Thumbs Up/Down Ratings Settings (admin user)', () => { - test('Enable Guest and Thumbs Up/Down Ratings (admin user)', async ({ adminContext }) => { - const adminPage = await adminContext.newPage(); - await adminPage.goto('/wp-admin/admin.php?page=cooked_settings#engagement', { waitUntil: 'networkidle' }); - - await expect(adminPage.getByRole('checkbox', { name: 'cooked_settings[enable_guest_ratings][]' })).toBeDefined(); - - // Check if the checkbox is checked first before clicking it. - let switchery = adminPage.locator('input[name="cooked_settings[enable_guest_ratings][]"] ~ span.switchery'); - const checkbox = adminPage.locator('input[name="cooked_settings[enable_guest_ratings][]"]'); - if (!(await checkbox.isChecked())) { - await switchery.click(); - } - - const select = adminPage.locator('select[name="cooked_settings[rating_type]"]'); - const currentValue = await select.evaluate((el) => (el as HTMLSelectElement).value); - if (currentValue !== 'thumbs') { - await select.selectOption('thumbs'); - } - - await adminPage.getByRole('button', { name: 'Update Settings' }).click(); - await expect(adminPage.getByText('Cooked settings has been updated!')).toBeDefined(); - }); -}); - -test.describe('Create a new complete recipe (admin)', () => { - test('Create a new recipe', async ({ adminContext }) => { - const adminPage = await adminContext.newPage(); - await adminPage.goto('/wp-admin/post-new.php?post_type=cp_recipe', { waitUntil: 'networkidle' }); - title = 'Test Recipe Playwright - ' + Date.now(); - - // Set recipe title - await adminPage.getByLabel('Recipe title ...').fill(title); - - // Set difficulty level - await adminPage.selectOption('select[name="_recipe_settings[difficulty_level]"]', '1'); // Beginner - - // Set times and servings - await adminPage.fill('input[name="_recipe_settings[prep_time]"]', '15'); - await adminPage.fill('input[name="_recipe_settings[cook_time]"]', '30'); - // await adminPage.fill('input[name="_recipe_settings[total_time]"]', '45'); - - // Click on the Nutrition tab to make those fields visible - await adminPage.click('#cooked-recipe-tab-nutrition', { force: true }); - await adminPage.fill('input[name="_recipe_settings[nutrition][servings]"]', '4'); - - // Set recipe taxonomies (using checkboxes) - await adminPage.check('input[name="tax_input[cp_recipe_category][]"][value="298"]'); - await adminPage.check('input[name="tax_input[cp_recipe_cooking_method][]"][value="288"]'); - await adminPage.check('input[name="tax_input[cp_recipe_cuisine][]"][value="279"]'); - await adminPage.check('input[name="tax_input[cp_recipe_diet][]"][value="268"]'); - - // Set recipe tags (can be multiple) - await adminPage.locator('input[name="newtag[cp_recipe_tags]"]').fill('butter, egg, flour'); - await adminPage.locator('.button.tagadd').first().click(); - - // Click the Set featured image link - await adminPage.locator('#set-post-thumbnail').click(); - - // Wait for the media modal to appear - await adminPage.waitForSelector('.media-modal-content'); - - // If you want to upload a new image, use this: - // await adminPage.setInputFiles('input[type="file"]', 'path/to/your/image.jpg'); - // await adminPage.getByRole('button', { name: 'Upload' }).click(); - - // Or to select an existing image from the media library: - await adminPage.getByRole('tab', { name: 'Media Library' }).click(); - await adminPage.waitForSelector('.attachment-preview'); - - // Select the first image in the media library - await adminPage.locator('.attachment-preview').first().click(); - - // Click the "Set Featured Image" button in the modal - await adminPage.getByRole('button', { name: 'Set Featured Image' }).click(); - - // Wait for the featured image to be set - await adminPage.waitForSelector('#remove-post-thumbnail'); - - // Handle all WYSIWYG editors - await adminPage.evaluate(() => { - // Excerpt editor - const excerptEditor = window.tinyMCE.get('_recipe_settings_excerpt'); - if (excerptEditor) { - excerptEditor.setContent('This is a brief description of the recipe.'); - } - - // Notes editor - const notesEditor = window.tinyMCE.get('_recipe_settings_notes'); - if (notesEditor) { - notesEditor.setContent('Important notes about this recipe.'); - } - - // Add ingredients - // const addIngredientButton = document.querySelector('.cooked-add-ingredient-button'); - // if (addIngredientButton) { - // (addIngredientButton as HTMLElement).click(); - // } - - // Find the first ingredient fields using regex - const ingredientBlocks = document.querySelectorAll('.cooked-ingredient-block'); - if (ingredientBlocks.length > 0) { - const firstIngredient = ingredientBlocks[0]; - const amountInput = firstIngredient.querySelector('input[data-ingredient-part="amount"]') as HTMLInputElement; - const measurementSelect = firstIngredient.querySelector('select[data-ingredient-part="measurement"]') as HTMLSelectElement; - const itemInput = firstIngredient.querySelector('input[data-ingredient-part="name"]') as HTMLInputElement; - - if (amountInput) amountInput.value = '2'; - if (measurementSelect) { - // Set the measurement to "cups" - measurementSelect.value = 'cup'; - } - if (itemInput) itemInput.value = 'flour'; - } - - // Add a direction step - // const addDirectionButton = document.querySelector('.cooked-add-direction-button'); - // if (addDirectionButton) { - // (addDirectionButton as HTMLElement).click(); - // } - - // Find the first direction editor using regex - const directionEditors = Object.keys(window.tinyMCE.editors).filter(id => /^direction-\d+-content$/.test(id)); - if (directionEditors.length > 0) { - const directionEditor = window.tinyMCE.get(directionEditors[0]); - if (directionEditor) { - directionEditor.setContent('First step of the recipe.'); - } - } - }); - - // Click the publish button first - await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - - // Wait for both URL change and success message - await Promise.all([ - // Wait for URL to change to post edit page - adminPage.waitForURL(/post\.php\?post=\d+&action=edit/), - // Wait for success message - adminPage.waitForSelector('.notice-success', { timeout: 10000 }) - ]); - - // Check for success message content - await expect(adminPage.locator('.notice-success')).toContainText('Post published.'); - - // Get the post ID from the URL - const url = adminPage.url(); - const postIdMatch = url.match(/post=(\d+)/); - postId = postIdMatch ? parseInt(postIdMatch[1]) : 0; - }); -}); - -test.describe('Guest Thumbs Up Rating Functionality (anonymous user)', () => { - test.use({ storageState: { cookies: [], origins: [] } }); - - test('Guest can submit thumbs up rating (anonymous user)', async ({ page }) => { - if (!postId) { - throw new Error('Post ID is not set'); - } - - await page.goto('/?post_type=cp_recipe&p=' + postId, { waitUntil: 'networkidle' }); - await expect(page.getByText(title)).toBeDefined(); - - await expect(page.locator('span.cooked-rating')).toBeDefined(); - - // Click the rating form. Get the current count value to compare after. - const currentCount = await page.locator('span.cooked-rating .cooked-rating-thumb').first().locator('span.cooked-rating-thumb-total').textContent(); - const currentCountNumber = parseInt(currentCount || '0'); - - await page.locator('span.cooked-rating .cooked-icon-thumbs-up-solid').click({ force: true }); - - // Wait for the rating count to update. - await page.waitForTimeout(1000); - - const newCount = await page.locator('span.cooked-rating .cooked-rating-thumb').first().locator('span.cooked-rating-thumb-total').textContent(); - const newCountNumber = parseInt(newCount || '0'); - expect(newCountNumber).toBe(currentCountNumber + 1); - }); -}); - -// Cleanup task - Delete the recipe -test.afterAll(async () => { - if (postId) { - try { - console.log(`Cleaning up: Deleting recipe ${postId}`); - execSync(`wp post delete ${postId} --force`); - } catch (error) { - console.error(`Failed to delete recipe ${postId}:`, error); - } - } else { - console.log('No post ID to delete'); - } -}); diff --git a/tests/playwright/tests/3_anon_user/3-sort-browse-recipe.spec.ts b/tests/playwright/tests/3_anon_user/3-sort-browse-recipe.spec.ts deleted file mode 100644 index 6e9dfb9..0000000 --- a/tests/playwright/tests/3_anon_user/3-sort-browse-recipe.spec.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test.describe('Sort Browse Recipes (anonymous user)', () => { - test.use({ storageState: { cookies: [], origins: [] } }); - - test('Sort recipes by title', async ({ page }) => { - await page.goto('/browse-recipes', { waitUntil: 'networkidle' }); - - // Check if the search section is present - await expect(page.locator('.cooked-recipe-search')).toBeDefined(); - - // Select "Oldest first" from the sort dropdown - await page.selectOption('.cooked-sortby-select', 'date_asc'); - - // Trigger form submission via JavaScript event - await page.evaluate(() => { - document.querySelector('.cooked-sortby-select').closest('form').dispatchEvent(new Event('submit')); - }); - - // Wait for navigation and URL change - await page.waitForURL('**/browse-recipes/sort/date_asc'); - - // Wait for the page to finish loading after the search - await page.waitForLoadState('networkidle'); - - // Check if the first recipe is Albanian Flatbread - const firstRecipe = page.locator('.cooked-recipe').first(); - await expect(firstRecipe.locator('.cooked-recipe-name')).toHaveText('Albanian Flatbread (Pite në Tigan)'); - await expect(firstRecipe).toHaveAttribute('id', 'cooked-recipe-1866'); - - // Select "Oldest first" from the sort dropdown - await page.selectOption('.cooked-sortby-select', 'date_desc'); - - // Trigger form submission via JavaScript event - await page.evaluate(() => { - document.querySelector('.cooked-sortby-select').closest('form').dispatchEvent(new Event('submit')); - }); - - // Wait for navigation and URL change - await page.waitForURL('**/browse-recipes/sort/date_desc'); - - // Wait for the page to finish loading after the search - await page.waitForLoadState('networkidle'); - - // Check if the first recipe is "Albanian Bread Stuffed with Cheese (Pogaça me Djathë)" - const secondRecipe = page.locator('.cooked-recipe').first(); - await expect(secondRecipe.locator('.cooked-recipe-name')).toHaveText('Albanian Bread Stuffed with Cheese (Pogaça me Djathë)'); - await expect(secondRecipe).toHaveAttribute('id', 'cooked-recipe-3587'); - }); - - test('Sort recipes by rating (desc)', async ({ page }) => { - await page.goto('/browse-recipes', { waitUntil: 'networkidle' }); - - // Check if the search section is present - await expect(page.locator('.cooked-recipe-search')).toBeDefined(); - - // Select "Rating" from the sort dropdown - await page.selectOption('.cooked-sortby-select', 'rating_desc'); - - // Trigger form submission via JavaScript event - await page.evaluate(() => { - document.querySelector('.cooked-sortby-select').closest('form').dispatchEvent(new Event('submit')); - }); - - // Wait for navigation and URL change - await page.waitForURL('**/browse-recipes/sort/rating_desc'); - - // Wait for the page to finish loading after the search - await page.waitForLoadState('networkidle'); - - // Check if the first recipe is "Albanian Bread Stuffed with Cheese (Pogaça me Djathë)" - const firstRecipe = page.locator('.cooked-recipe').first(); - await expect(firstRecipe.locator('.cooked-recipe-name')).toHaveText('Albanian Soda Bread (Albanian Kulac)'); - await expect(firstRecipe).toHaveAttribute('id', 'cooked-recipe-1865'); - }); - - test('Sort recipes by rating (asc)', async ({ page }) => { - await page.goto('/browse-recipes', { waitUntil: 'networkidle' }); - - // Check if the search section is present - await expect(page.locator('.cooked-recipe-search')).toBeDefined(); - - // Select "Rating" from the sort dropdown - await page.selectOption('.cooked-sortby-select', 'rating_asc'); - - // Trigger form submission via JavaScript event - await page.evaluate(() => { - document.querySelector('.cooked-sortby-select').closest('form').dispatchEvent(new Event('submit')); - }); - - // Wait for navigation and URL change - await page.waitForURL('**/browse-recipes/sort/rating_asc'); - - // Wait for the page to finish loading after the search - await page.waitForLoadState('networkidle'); - - // Check if the first recipe is "Albanian Bread Stuffed with Cheese (Pogaça me Djathë)" - const firstRecipe = page.locator('.cooked-recipe').first(); - await expect(firstRecipe.locator('.cooked-recipe-name')).toHaveText('Albanian Soda Bread For Stuffing (Kulaç Për Përshesh)'); - await expect(firstRecipe).toHaveAttribute('id', 'cooked-recipe-1835'); - }); -}); \ No newline at end of file diff --git a/tests/playwright/tests/3_anon_user/4-filter-browse-recipe.spec.ts b/tests/playwright/tests/3_anon_user/4-filter-browse-recipe.spec.ts deleted file mode 100644 index 35cbc7a..0000000 --- a/tests/playwright/tests/3_anon_user/4-filter-browse-recipe.spec.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test.describe('Filter Browse Recipes (anonymous user)', () => { - test.use({ storageState: { cookies: [], origins: [] } }); - - test('Filter recipes by category', async ({ page }) => { - await page.goto('/browse-recipes', { waitUntil: 'networkidle' }); - - // Check if the search section is present - await expect(page.locator('.cooked-recipe-search')).toBeDefined(); - - // Click cooked-browse-select - await page.click('.cooked-browse-select'); - - // Expect cooked-browse-select-block to be visible - await expect(page.locator('.cooked-browse-select-block')).toBeVisible(); - - // Click the "Bread" category - await page.click('.cooked-tax-column:has-text("Categories") a[href*="/recipe-category/bread"]'); - - // Wait for navigation and URL change - await page.waitForURL('**/browse-recipes/recipe-category/bread'); - - // Wait for the page to finish loading after the search - await page.waitForLoadState('networkidle'); - - // Check if the first recipe is "Albanian Bread Stuffed with Cheese (Pogaça me Djathë)" - const firstRecipe = page.locator('.cooked-recipe').first(); - await expect(firstRecipe.locator('.cooked-recipe-name')).toHaveText('Albanian Bread Stuffed with Cheese (Pogaça me Djathë)'); - await expect(firstRecipe).toHaveAttribute('id', 'cooked-recipe-3587'); - }); - - test('Filter recipes by cooking method', async ({ page }) => { - await page.goto('/browse-recipes', { waitUntil: 'networkidle' }); - - // Check if the search section is present - await expect(page.locator('.cooked-recipe-search')).toBeDefined(); - - // Click cooked-browse-select - await page.click('.cooked-browse-select'); - - // Expect cooked-browse-select-block to be visible - await expect(page.locator('.cooked-browse-select-block')).toBeVisible(); - - await page.click('.cooked-tax-column:has-text("Cooking Methods") a[href*="/cooking-method/boiling"]'); - - // Wait for navigation and URL change - await page.waitForURL('**/browse-recipes/cooking-method/boiling'); - - // Wait for the page to finish loading after the search - await page.waitForLoadState('networkidle'); - - // Check if the first recipe is "Russian Salad (Sallatë Ruse)" - const firstRecipe = page.locator('.cooked-recipe').first(); - await expect(firstRecipe.locator('.cooked-recipe-name')).toHaveText('Russian Salad (Sallatë Ruse)'); - await expect(firstRecipe).toHaveAttribute('id', 'cooked-recipe-3216'); - }); - - test('Filter recipes by cuisine', async ({ page }) => { - await page.goto('/browse-recipes', { waitUntil: 'networkidle' }); - - // Check if the search section is present - await expect(page.locator('.cooked-recipe-search')).toBeDefined(); - - // Click cooked-browse-select - await page.click('.cooked-browse-select'); - - // Expect cooked-browse-select-block to be visible - await expect(page.locator('.cooked-browse-select-block')).toBeVisible(); - - await page.click('.cooked-tax-column:has-text("Cuisines") a[href*="/cuisine/greek"]'); - - // Wait for navigation and URL change - await page.waitForURL('**/browse-recipes/cuisine/greek'); - - // Wait for the page to finish loading after the search - await page.waitForLoadState('networkidle'); - - // Check if the first recipe is "Greek Creamy Egg, Lemon & Chicken Soup (Supë Pule me Limon)" - const firstRecipe = page.locator('.cooked-recipe').first(); - await expect(firstRecipe.locator('.cooked-recipe-name')).toHaveText('Greek Creamy Egg, Lemon & Chicken Soup (Supë Pule me Limon)'); - await expect(firstRecipe).toHaveAttribute('id', 'cooked-recipe-1832'); - }); -}); \ No newline at end of file diff --git a/tests/playwright/tests/4_admin_user/1-create-approve-recipe.spec.ts b/tests/playwright/tests/4_admin_user/1-create-approve-recipe.spec.ts deleted file mode 100644 index 825a17e..0000000 --- a/tests/playwright/tests/4_admin_user/1-create-approve-recipe.spec.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { test as base, Dialog, expect, Page } from '@playwright/test'; -import { ensureValidAuth, getAuthPath } from '../../utils/auth'; -import { execSync } from 'child_process'; -import { readFileSync } from 'fs'; - -var title: string; -var postId: number; - -const dragAndDropFile = async ( - page: Page, - selector: string, - filePath: string, - fileName: string, - fileType = '' -) => { - const buffer = readFileSync(filePath).toString('base64'); - - const dataTransfer = await page.evaluateHandle( - async ({ bufferData, localFileName, localFileType }) => { - const dt = new DataTransfer(); - - const blobData = await fetch(bufferData).then((res) => res.blob()); - - const file = new File([blobData], localFileName, { type: localFileType }); - dt.items.add(file); - return dt; - }, - { - bufferData: `data:application/octet-stream;base64,${buffer}`, - localFileName: fileName, - localFileType: fileType, - } - ); - - await page.dispatchEvent(selector, 'drop', { dataTransfer }); -}; - -// Create a fixture for authentication -const test = base.extend({ - adminContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('mtresova') - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'mtresova', 'password'); - await use(context); - await page.close(); - await context.close(); - }, - contribContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('testUserContributor') - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'testUserContributor', 'password'); - await use(context); - await page.close(); - await context.close(); - } -}); - -test.describe('Create a new complete recipe front-end (contrib user)', () => { - test('Create a new recipe', async ({ contribContext }) => { - const contribPage = await contribContext.newPage(); - await contribPage.goto('/profile/add', { waitUntil: 'networkidle' }); - title = 'Test Recipe Playwright Time: ' + Date.now(); - - // Set recipe title - await contribPage.fill('input[name="_recipe_settings[post_title]"]', title); - - await dragAndDropFile(contribPage, "#featured_image", "tests/_files/icon_pro.png", "icon_pro.png", "image/png"); - - // Set difficulty level - await contribPage.selectOption('select[name="_recipe_settings[difficulty_level]"]', '1'); // Beginner - - // Set times and servings - await contribPage.fill('input[name="_recipe_settings[prep_time]"]', '15'); - await contribPage.fill('input[name="_recipe_settings[cook_time]"]', '30'); - // await contribPage.fill('input[name="_recipe_settings[total_time]"]', '45'); - - // Click on the Nutrition tab to make those fields visible - await contribPage.click('.cooked-add-nutrition-button', { force: true }); - await contribPage.fill('input[name="_recipe_settings[nutrition][servings]"]', '4'); - - // Set recipe taxonomies (using checkboxes) - await contribPage.selectOption('select[name="_recipe_settings[category]"]', 'bread'); - await contribPage.selectOption('select[name="_recipe_settings[cooking_method]"]', 'baking'); - await contribPage.selectOption('select[name="_recipe_settings[cuisine]"]', 'albanian'); - await contribPage.selectOption('select[name="_recipe_settings[diet]"]', 'vegetarian'); - - await contribPage.fill('textarea[name="_recipe_settings[excerpt]"]', 'This is a brief description of the recipe.'); - await contribPage.fill('textarea[name="_recipe_settings[notes]"]', 'Important notes about this recipe.'); - - // Handle all WYSIWYG editors - await contribPage.evaluate(() => { - // Add ingredients - const addIngredientButton = document.querySelector('.cooked-add-ingredient-button'); - if (addIngredientButton) { - (addIngredientButton as HTMLElement).click(); - } - - // Find the first ingredient fields using regex - const ingredientBlocks = document.querySelectorAll('.cooked-ingredient-block'); - if (ingredientBlocks.length > 0) { - const firstIngredient = ingredientBlocks[0]; - const amountInput = firstIngredient.querySelector('input[data-ingredient-part="amount"]') as HTMLInputElement; - const measurementSelect = firstIngredient.querySelector('select[data-ingredient-part="measurement"]') as HTMLSelectElement; - const itemInput = firstIngredient.querySelector('input[data-ingredient-part="name"]') as HTMLInputElement; - - if (amountInput) amountInput.value = '2'; - if (measurementSelect) { - // Set the measurement to "cups" - measurementSelect.value = 'cup'; - } - if (itemInput) itemInput.value = 'flour'; - } - - // Add a direction step - const addDirectionButton = document.querySelector('.cooked-add-direction-button'); - if (addDirectionButton) { - (addDirectionButton as HTMLElement).click(); - } - - // Find and fill the direction textarea - return new Promise((resolve) => { - const checkTextarea = setInterval(() => { - const directionTextareas = document.querySelectorAll('textarea[data-direction-part="content"]'); - - if (directionTextareas.length > 0) { - const firstDirectionTextarea = directionTextareas[0] as HTMLTextAreaElement; - firstDirectionTextarea.value = 'First step of the recipe.'; - clearInterval(checkTextarea); - resolve(true); - } - }, 500); - - // Set a timeout to prevent infinite checking - setTimeout(() => { - clearInterval(checkTextarea); - resolve(false); - }, 10000); // Maximum 10 second timeout - }); - - }); - - // Click the publish button first - await contribPage.getByRole('button', { name: 'Submit Recipe', exact: true }).click(); - - // Wait for both URL change and success message - await Promise.all([ - // Wait for URL to change to post edit page - contribPage.waitForURL('/profile'), - // Wait for success message - contribPage.waitForSelector('.cooked-success-banner', { timeout: 10000 }) - ]); - - // Check for success message content - await expect(contribPage.locator('.cooked-success-banner')).toContainText('You have successfully submitted a new recipe. It is now pending approval.'); - - // Pause. - // await contribPage.pause(); - - // After successful submission, get the recipe ID from the edit button URL - const editButton = contribPage.locator('.cooked-edit-button').first(); - const href = await editButton.getAttribute('href'); - postId = parseInt(href?.match(/edit-recipe\/(\d+)/)?.[1] || 0); - - expect(postId).toBeTruthy(); - }); - - test('View the recipe (frontend)', async ({ contribContext }) => { - const contribPage = await contribContext.newPage(); - await contribPage.goto('/?post_type=cp_recipe&p=' + postId, { waitUntil: 'networkidle' }); - - // Check for recipe title in h1.entry-title - await expect(contribPage.locator('h1.entry-title')).toHaveText(title); - - // Check for pending message - await expect(contribPage.getByText('This recipe is pending review. No one else can see it yet.')).toBeVisible(); - }); -}); - -test.describe('Approve the recipe (admin user)', () => { - test('Approve the recipe', async ({ adminContext }) => { - const adminPage = await adminContext.newPage(); - await adminPage.goto('/wp-admin/admin.php?page=cooked_pending', { waitUntil: 'networkidle' }); - - // First, handle the confirm dialog that will appear - adminPage.on('dialog', async (dialog: Dialog) => { - // Automatically accept the confirmation - await dialog.accept(); - }); - - // Find the recipe container by title and click its approve button - const recipeContainer = adminPage.locator('.cooked-pending-recipe', { - has: adminPage.locator('h3', { hasText: title }) - }); - await recipeContainer.locator('.button-primary').click(); - - // Wait for the recipe to be deleted - await adminPage.waitForTimeout(1000); - - // Check that the recipe title does not exist in the pending list - await expect(adminPage.getByText(title)).not.toBeVisible(); - }); - - test('Check that the recipe is visible (frontend)', async ({ adminContext }) => { - const adminPage = await adminContext.newPage(); - await adminPage.goto('/?post_type=cp_recipe&p=' + postId, { waitUntil: 'networkidle' }); - - // Check for recipe title in h1.entry-title - await expect(adminPage.locator('h1.entry-title')).toHaveText(title); - - // Check for pending message - await expect(adminPage.getByText('This recipe has been published and is viewable by everyone.')).toBeVisible(); - }); -}); - -test.describe('View as anonymous user', () => { - test.use({ storageState: { cookies: [], origins: [] } }); - - test('Check that the recipe is visible (frontend)', async ({ page }) => { - await page.goto('/?post_type=cp_recipe&p=' + postId, { waitUntil: 'networkidle' }); - - // Check for recipe title in h1.entry-title - await expect(page.locator('h1.entry-title')).toHaveText(title); - }); -}); - -// Cleanup task - Delete the recipe -test.afterAll(async () => { - if (postId) { - try { - console.log(`Cleaning up: Deleting recipe ${postId}`); - execSync(`wp post delete ${postId} --force`); - } catch (error) { - console.error(`Failed to delete recipe ${postId}:`, error); - } - } else { - console.log('No post ID to delete'); - } -}); diff --git a/tests/playwright/tests/5_api/1-nutrition-api.spec.ts b/tests/playwright/tests/5_api/1-nutrition-api.spec.ts deleted file mode 100644 index 1abcb7d..0000000 --- a/tests/playwright/tests/5_api/1-nutrition-api.spec.ts +++ /dev/null @@ -1,368 +0,0 @@ -import { test as base, expect } from '@playwright/test'; -import { ensureValidAuth, getAuthPath } from '../../utils/auth'; -import { execFileSync } from 'child_process'; - -function wp(args: string[]): string { - return execFileSync('wp', [/* '--url=dev.mimisrecipes.ddev.site', */ '--quiet', ...args], { - encoding: 'utf-8', - maxBuffer: 10 * 1024 * 1024, - }).trim(); -} - -function getCookedSettings(): Record { - try { - const raw = wp(['option', 'get', 'cooked_settings', '--format=json']); - return JSON.parse(raw) as Record; - } catch { - return {}; - } -} - -const EDAMAM_API_URL = 'https://api.edamam.com/api/nutrition-details'; -const appId = process.env.EDAMAM_APP_ID || ''; -const appKey = process.env.EDAMAM_APP_KEY || ''; -const hasCredentials = appId !== '' && appKey !== ''; - -// ─── Test Group 1: Direct Edamam API Health Check ─────────────────────────── - -base.describe('Edamam API Health Check', () => { - base.skip(!hasCredentials, 'EDAMAM_APP_ID and EDAMAM_APP_KEY must be set in .env'); - - base.use({ storageState: { cookies: [], origins: [] } }); - - base('API returns valid nutrition data for known ingredients', async ({ request }) => { - const response = await request.post( - `${EDAMAM_API_URL}?app_id=${appId}&app_key=${appKey}&beta=1&kitchen=home`, - { - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'Content-Language': 'en', - }, - data: { - title: 'Playwright Test Recipe', - ingr: ['1 cup rice', '2 tablespoons olive oil', '1 teaspoon salt'], - yield: '4', - }, - } - ); - - expect(response.status()).toBe(200); - - const data = await response.json(); - - expect(data).toHaveProperty('totalNutrients'); - expect(data).toHaveProperty('totalNutrientsKCal'); - expect(data).toHaveProperty('yield'); - expect(data).toHaveProperty('healthLabels'); - expect(data).toHaveProperty('dietLabels'); - - expect(data.totalNutrients.FAT).toBeDefined(); - expect(data.totalNutrients.FAT).toHaveProperty('quantity'); - expect(data.totalNutrients.FAT).toHaveProperty('unit'); - - expect(data.totalNutrients.PROCNT).toBeDefined(); - expect(data.totalNutrients.PROCNT).toHaveProperty('quantity'); - - expect(data.totalNutrients.CHOCDF).toBeDefined(); - expect(data.totalNutrients.CHOCDF).toHaveProperty('quantity'); - - expect(data.totalNutrientsKCal.ENERC_KCAL).toBeDefined(); - expect(data.totalNutrientsKCal.ENERC_KCAL.quantity).toBeGreaterThan(0); - }); - - base('API rejects invalid ingredients gracefully', async ({ request }) => { - const response = await request.post( - `${EDAMAM_API_URL}?app_id=${appId}&app_key=${appKey}&beta=1&kitchen=home`, - { - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'Content-Language': 'en', - }, - data: { - title: 'Bad Recipe', - ingr: ['asdfghjkl zxcvbnm'], - yield: '1', - }, - } - ); - - // Edamam typically returns 422 or 555 for unrecognizable ingredients, - // but may also return 200 with low-confidence data. - const status = response.status(); - if (status === 200) { - const data = await response.json(); - expect(data).toHaveProperty('totalNutrients'); - } else { - expect([404, 422, 555]).toContain(status); - } - }); -}); - -// ─── Test Group 2: WordPress Nutrition Integration ────────────────────────── - -declare global { - interface Window { - tinyMCE: { - get: (id: string) => { - setContent: (content: string) => void; - }; - editors: { [key: string]: any }; - }; - } -} - -const test = base.extend({ - adminContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('mtresova'), - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'mtresova', 'password'); - await use(context); - await page.close(); - await context.close(); - }, -}); - -let postId: number; -let originalSettings: Record | undefined; - -test.describe('WordPress Nutrition Integration', () => { - test.skip(!hasCredentials, 'EDAMAM_APP_ID and EDAMAM_APP_KEY must be set in .env'); - - test.beforeAll(async () => { - originalSettings = { ...getCookedSettings() }; - const merged = { - ...originalSettings, - enable_nutrition_api: ['enabled'], - nutrition_api_app_id: appId, - nutrition_api_app_key: appKey, - }; - wp(['option', 'update', 'cooked_settings', JSON.stringify(merged), '--format=json']); - }); - - test('Create recipe and calculate nutrition via admin editor', async ({ adminContext }) => { - const adminPage = await adminContext.newPage(); - - // Step 1: Create and publish a recipe with ingredients - await adminPage.goto('/wp-admin/post-new.php?post_type=cp_recipe', { waitUntil: 'networkidle' }); - - const title = 'Chicken Tikka Masala'; - await adminPage.getByLabel('Recipe title ...').fill(title); - - await adminPage.fill('input[name="_recipe_settings[prep_time]"]', '10'); - await adminPage.fill('input[name="_recipe_settings[cook_time]"]', '20'); - - // Click the Set featured image link - await adminPage.locator('#set-post-thumbnail').click(); - - // Wait for the media modal to appear - await adminPage.waitForSelector('.media-modal-content'); - - // If you want to upload a new image, use this: - // await adminPage.setInputFiles('input[type="file"]', 'path/to/your/image.jpg'); - // await adminPage.getByRole('button', { name: 'Upload' }).click(); - - // Or to select an existing image from the media library: - await adminPage.getByRole('tab', { name: 'Media Library' }).click(); - await adminPage.waitForSelector('.attachment-preview'); - - // Select the first image in the media library - await adminPage.locator('.attachment-preview').first().click(); - - // Click the "Set Featured Image" button in the modal - await adminPage.locator('.media-button-select').first().click(); - - // Wait for the featured image to be set - await adminPage.waitForSelector('#remove-post-thumbnail'); - - // Build a richer ingredient list so the nutrition API can parse it reliably. - await adminPage.evaluate(() => { - // Add excerpt because it is included in the nutrition API request payload. - const excerptEditor = window.tinyMCE.get('_recipe_settings_excerpt'); - if (excerptEditor) { - excerptEditor.setContent( - 'Creamy chicken tikka masala made with yogurt, tomato puree, and warm Indian spices for a rich, comforting flavor.' - ); - // Ensure TinyMCE writes back to the underlying textarea that the AJAX request reads. - if (typeof (excerptEditor as any).save === 'function') { - (excerptEditor as any).save(); - } - } - }); - - // Use a full, deterministic ingredient list. We first create enough blocks, - // then fill by index so publish cannot race before inputs are populated. - const recipeIngredients = [ - { amount: '1', measurement: 'lb', name: 'boneless chicken breast' }, - { amount: '0.5', measurement: 'cup', name: 'plain yogurt' }, - { amount: '1', measurement: 'tbsp', name: 'lemon juice' }, - { amount: '1', measurement: 'tbsp', name: 'garlic paste' }, - { amount: '1', measurement: 'tbsp', name: 'ginger paste' }, - { amount: '1', measurement: 'cup', name: 'tomato puree' }, - { amount: '2', measurement: 'tbsp', name: 'butter' }, - { amount: '2', measurement: 'tbsp', name: 'cooking oil' }, - { amount: '2', measurement: 'tsp', name: 'ground cumin' }, - { amount: '2', measurement: 'tsp', name: 'ground coriander' }, - { amount: '1', measurement: 'tsp', name: 'turmeric' }, - { amount: '1', measurement: 'tsp', name: 'chili powder' }, - { amount: '1', measurement: 'tsp', name: 'garam masala' }, - { amount: '1', measurement: 'tsp', name: 'kosher salt' }, - { amount: '2', measurement: 'tbsp', name: 'heavy cream' }, - ]; - - // There is one default ingredient block; create the rest. - // The "Add Ingredient" button lives under the Ingredients tab content. - await adminPage.click('#cooked-recipe-tab-ingredients', { force: true }); - - await adminPage.waitForSelector('#cooked-recipe-tab-content-ingredients .cooked-ingredient-block', { - timeout: 30000, - }); - await adminPage.waitForSelector('#cooked-recipe-tab-content-ingredients .cooked-add-ingredient-button', { - timeout: 30000, - }); - - for (let i = 1; i < recipeIngredients.length; i++) { - await adminPage.click('#cooked-recipe-tab-content-ingredients .cooked-add-ingredient-button'); - } - - await adminPage.waitForFunction((expectedCount: number) => { - return document.querySelectorAll('.cooked-ingredient-block').length >= expectedCount; - }, recipeIngredients.length); - - await adminPage.evaluate((ingredients: Array<{ amount: string; measurement: string; name: string }>) => { - const ingredientBlocks = document.querySelectorAll('.cooked-ingredient-block'); - - ingredients.forEach((ingredient, index) => { - const block = ingredientBlocks[index] as HTMLElement | undefined; - if (!block) return; - - const amount = block.querySelector('input[data-ingredient-part="amount"]') as HTMLInputElement | null; - const measurement = block.querySelector('select[data-ingredient-part="measurement"]') as HTMLSelectElement | null; - const name = block.querySelector('input[data-ingredient-part="name"]') as HTMLInputElement | null; - - if (amount) { - amount.value = ingredient.amount; - amount.dispatchEvent(new Event('input', { bubbles: true })); - amount.dispatchEvent(new Event('change', { bubbles: true })); - } - if (measurement) { - measurement.value = ingredient.measurement; - measurement.dispatchEvent(new Event('change', { bubbles: true })); - } - if (name) { - name.value = ingredient.name; - name.dispatchEvent(new Event('input', { bubbles: true })); - name.dispatchEvent(new Event('change', { bubbles: true })); - } - }); - }, recipeIngredients); - - await adminPage.waitForFunction((expectedCount: number) => { - const blocks = document.querySelectorAll('.cooked-ingredient-block'); - if (blocks.length < expectedCount) return false; - - return Array.from(blocks) - .slice(0, expectedCount) - .every((block) => { - const name = (block.querySelector('input[data-ingredient-part="name"]') as HTMLInputElement | null)?.value?.trim(); - return Boolean(name); - }); - }, recipeIngredients.length); - - // Extra short settle so any plugin listeners complete before publish. - await adminPage.waitForTimeout(300); - - // Fill a direction step so the recipe is valid - await adminPage.click('#cooked-recipe-tab-directions', { force: true }); - await adminPage.evaluate(() => { - const directionEditors = Object.keys(window.tinyMCE.editors).filter(id => - /^direction-\d+-content$/.test(id) - ); - if (directionEditors.length > 0) { - const editor = window.tinyMCE.get(directionEditors[0]); - if (editor) editor.setContent( - 'Marinate chicken with yogurt, garlic, and ginger, then cook with butter and warm spices. Add tomato puree and simmer until thick, then stir in cream for a rich tikka masala sauce.' - ); - } - }); - - await adminPage.click('#cooked-recipe-tab-nutrition', { force: true }); - await adminPage.fill('input[name="_recipe_settings[nutrition][servings]"]', '4'); - - // Publish the recipe - await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - - await Promise.all([ - adminPage.waitForURL(/post\.php\?post=\d+&action=edit/), - adminPage.waitForSelector('.notice-success', { timeout: 10000 }), - ]); - - const url = adminPage.url(); - const postIdMatch = url.match(/post=(\d+)/); - postId = postIdMatch ? parseInt(postIdMatch[1]) : 0; - expect(postId).toBeTruthy(); - - // Step 2: Navigate to the nutrition tab and calculate - await adminPage.click('#cooked-recipe-tab-nutrition', { force: true }); - - // Click the "Calculate Nutrition Information" button to open the tooltip - await adminPage.click('.cooked-auto-nutrition-button'); - - // Wait for the tooltipster tooltip to appear with the Calculate button - await adminPage.waitForSelector('#cooked-auto-nutrition-button', { state: 'visible', timeout: 5000 }); - - // Click "Calculate" inside the tooltip - await adminPage.click('#cooked-auto-nutrition-button'); - - // Stop for debugging - // await adminPage.pause(); - - // Wait for the AJAX to complete — poll until calories field is populated - const caloriesInput = adminPage.locator('input[name="_recipe_settings[nutrition][calories]"]'); - await expect(caloriesInput).not.toHaveValue('', { timeout: 30000000 }); // 30000 - - // Assert: key nutrition fields are populated with numeric values > 0 - const calories = await caloriesInput.inputValue(); - expect(Number(calories)).toBeGreaterThan(0); - - const fat = await adminPage.locator('input[name="_recipe_settings[nutrition][fat]"]').inputValue(); - expect(Number(fat)).toBeGreaterThan(0); - - const protein = await adminPage.locator('input[name="_recipe_settings[nutrition][protein]"]').inputValue(); - expect(Number(protein)).toBeGreaterThan(0); - - const carbs = await adminPage.locator('input[name="_recipe_settings[nutrition][carbs]"]').inputValue(); - expect(Number(carbs)).toBeGreaterThan(0); - - // Assert: etag was saved (indicates a successful API round-trip) - const etag = await adminPage.locator('input[name="_recipe_settings[nutrition][etag]"]').inputValue(); - expect(etag).toBeTruthy(); - }); - - test.afterAll(async () => { - if (postId) { - try { - wp(['post', 'delete', String(postId), '--force']); - } catch (error) { - console.error(`Failed to delete recipe ${postId}:`, error); - } - } - - if (originalSettings !== undefined) { - try { - wp([ - 'option', - 'update', - 'cooked_settings', - JSON.stringify(originalSettings), - '--format=json', - ]); - } catch (error) { - console.error('Failed to restore original settings:', error); - } - } - }); -}); diff --git a/tests/playwright/tests/5_settings/3-profile-pretty-urls.spec.ts.bak b/tests/playwright/tests/5_settings/3-profile-pretty-urls.spec.ts.bak deleted file mode 100644 index 04c284a..0000000 --- a/tests/playwright/tests/5_settings/3-profile-pretty-urls.spec.ts.bak +++ /dev/null @@ -1,51 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { ensureValidAuth, authFile } from '../utils/auth'; - -test.describe('Admin Guest Ratings Settings', () => { - test.beforeEach(async ({ page }) => { - await ensureValidAuth(page); - }); - - test.use({ storageState: authFile }); - - test('Check if the Guest Ratings checkbox is present', async ({ page }) => { - await page.goto('/wp-admin/admin.php?page=cooked_settings#engagement'); - await expect(page.getByRole('checkbox', { name: 'cooked_settings[enable_guest_ratings][]' })).toBeDefined(); - }); - - test('Enable Guest Ratings', async ({ page }) => { - await page.goto('/wp-admin/admin.php?page=cooked_settings#engagement', { waitUntil: 'networkidle' }); - // Check if the checkbox is checked first before clicking it. - let switchery = page.locator('input[name="cooked_settings[enable_guest_ratings][]"] ~ span.switchery'); - const checkbox = page.locator('input[name="cooked_settings[enable_guest_ratings][]"]'); - if (!(await checkbox.isChecked())) { - await switchery.click(); - await page.getByRole('button', { name: 'Update Settings' }).click(); - await expect(page.getByText('Cooked settings has been updated!')).toBeDefined(); - } - }); -}); - -test.describe('Guest Thumbs Up Rating Functionality', () => { - test.use({ storageState: { cookies: [], origins: [] } }); - - test('Guest can view rating form', async ({ page }) => { - await page.goto('/recipes/orange-olive-salad'); - await expect(page.locator('span.cooked-rating')).toBeDefined(); - }); - - test('Guest can submit thumbs up rating', async ({ page }) => { - await page.goto('/recipes/orange-olive-salad'); - // Click the rating form. Get the current count value to compare after. - const currentCount = await page.locator('span.cooked-rating .cooked-rating-thumb').first().locator('span.cooked-rating-thumb-total').textContent(); - const currentCountNumber = parseInt(currentCount || '0'); - - await page.locator('span.cooked-rating .cooked-icon-thumbs-up-solid').click(); - // Wait for the rating count to update. - await page.waitForTimeout(1000); - const newCount = await page.locator('span.cooked-rating .cooked-rating-thumb').first().locator('span.cooked-rating-thumb-total').textContent(); - const newCountNumber = parseInt(newCount || '0'); - expect(newCountNumber).toBe(currentCountNumber + 1); - await page.pause(); - }); -}); diff --git a/tests/playwright/tests/7_accessibility/5-add-recipe-form.spec.ts b/tests/playwright/tests/7_accessibility/5-add-recipe-form.spec.ts deleted file mode 100644 index e20e16d..0000000 --- a/tests/playwright/tests/7_accessibility/5-add-recipe-form.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { test as base } from '@playwright/test'; -import { ensureValidAuth, getAuthPath } from '../../utils/auth'; -import { expectAccessible } from '../../utils/a11y'; - -const main = '#basil-main .basil-main-template'; - -const test = base.extend({ - contribContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('testUserContributor'), - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'testUserContributor', 'password'); - await use(context); - await page.close(); - await context.close(); - }, -}); - -test.describe('Add recipe form accessibility (contributor user)', () => { - test('add recipe form - no accessibility violations', async ({ contribContext }) => { - const contribPage = await contribContext.newPage(); - await contribPage.goto('/profile/add/', { waitUntil: 'networkidle' }); - await contribPage.locator(`${main} input[name="_recipe_settings[post_title]"]`).waitFor(); - await expectAccessible(contribPage); - }); -}); diff --git a/tests/playwright/tests/7_accessibility/6-recipe-ratings.spec.ts b/tests/playwright/tests/7_accessibility/6-recipe-ratings.spec.ts deleted file mode 100644 index 7bf0e39..0000000 --- a/tests/playwright/tests/7_accessibility/6-recipe-ratings.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { test as base, expect } from '@playwright/test'; -import { ensureValidAuth, getAuthPath } from '../../utils/auth'; -import { expectAccessible } from '../../utils/a11y'; - -const STABLE_RECIPE_ID = 3587; -const main = '#basil-main .basil-main-template'; - -const test = base.extend({ - adminContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('mtresova'), - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'mtresova', 'password'); - await use(context); - await page.close(); - await context.close(); - }, -}); - -test.describe('Recipe ratings accessibility', () => { - test('enable guest star ratings', async ({ adminContext }) => { - const adminPage = await adminContext.newPage(); - await adminPage.goto('/wp-admin/admin.php?page=cooked_settings#engagement', { waitUntil: 'networkidle' }); - - const checkbox = adminPage.locator('input[name="cooked_settings[enable_guest_ratings][]"]'); - if (!(await checkbox.isChecked())) { - await adminPage.locator('input[name="cooked_settings[enable_guest_ratings][]"] ~ span.switchery').click(); - } - - const select = adminPage.locator('select[name="cooked_settings[rating_type]"]'); - const currentValue = await select.evaluate((el) => (el as HTMLSelectElement).value); - if (currentValue !== 'stars') { - await select.selectOption('stars'); - } - - await adminPage.getByRole('button', { name: 'Update Settings' }).click(); - await expect(adminPage.getByText('Cooked settings has been updated!')).toBeDefined(); - }); - - test.describe('anonymous user', () => { - test.use({ storageState: { cookies: [], origins: [] } }); - - test('recipe ratings - no accessibility violations', async ({ page }) => { - await page.goto(`/?post_type=cp_recipe&p=${STABLE_RECIPE_ID}`, { waitUntil: 'networkidle' }); - await page.locator(`${main} .cooked-recipe-info, ${main} .cooked-recipe-ingredients`).first().waitFor(); - await page.locator(`${main} .cooked-rating-stars`).first().waitFor(); - await expectAccessible(page); - }); - }); -}); diff --git a/tests/playwright/tests/7_accessibility/1-browse-recipes.spec.ts b/tests/playwright/tests/accessibility/browse.spec.ts similarity index 100% rename from tests/playwright/tests/7_accessibility/1-browse-recipes.spec.ts rename to tests/playwright/tests/accessibility/browse.spec.ts diff --git a/tests/playwright/tests/7_accessibility/2-browse-filters.spec.ts b/tests/playwright/tests/accessibility/filters.spec.ts similarity index 100% rename from tests/playwright/tests/7_accessibility/2-browse-filters.spec.ts rename to tests/playwright/tests/accessibility/filters.spec.ts diff --git a/tests/playwright/tests/7_accessibility/4-recipe-search.spec.ts b/tests/playwright/tests/accessibility/search.spec.ts similarity index 100% rename from tests/playwright/tests/7_accessibility/4-recipe-search.spec.ts rename to tests/playwright/tests/accessibility/search.spec.ts diff --git a/tests/playwright/tests/7_accessibility/3-recipe-single.spec.ts b/tests/playwright/tests/accessibility/single.spec.ts similarity index 100% rename from tests/playwright/tests/7_accessibility/3-recipe-single.spec.ts rename to tests/playwright/tests/accessibility/single.spec.ts diff --git a/tests/playwright/tests/1_admin_user/1-create-recipe.spec.ts b/tests/playwright/tests/admin/create-recipe.spec.ts similarity index 58% rename from tests/playwright/tests/1_admin_user/1-create-recipe.spec.ts rename to tests/playwright/tests/admin/create-recipe.spec.ts index 596966c..ede8cd6 100644 --- a/tests/playwright/tests/1_admin_user/1-create-recipe.spec.ts +++ b/tests/playwright/tests/admin/create-recipe.spec.ts @@ -1,6 +1,5 @@ -import { test as base, expect } from '@playwright/test'; -import { ensureValidAuth, getAuthPath } from '../../utils/auth'; -import { execSync } from 'child_process'; +import { test, expect } from '../../utils/fixtures'; +import { deletePost } from '../../utils/wp-cli'; var postId: number; var title: string; @@ -16,20 +15,7 @@ declare global { } } -// Create a fixture for authentication -const test = base.extend({ - adminContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('mtresova') - }); - // Set the default URL to the WordPress - const page = await context.newPage(); - await ensureValidAuth(page, 'mtresova', 'password'); - await use(context); - await page.close(); - await context.close(); - } -}); +test.describe.configure({ mode: 'serial' }); test.describe('Create a new complete recipe (admin)', () => { test('Create a new recipe (admin)', async ({ adminContext }) => { @@ -37,74 +23,37 @@ test.describe('Create a new complete recipe (admin)', () => { await adminPage.goto('/wp-admin/post-new.php?post_type=cp_recipe', { waitUntil: 'networkidle' }); title = 'Test Recipe Playwright: ' + Date.now(); - // Set recipe title await adminPage.getByLabel('Recipe title ...').fill(title); - // Set difficulty level - await adminPage.selectOption('select[name="_recipe_settings[difficulty_level]"]', '1'); // Beginner + await adminPage.selectOption('select[name="_recipe_settings[difficulty_level]"]', '1'); - // Set times and servings await adminPage.fill('input[name="_recipe_settings[prep_time]"]', '15'); await adminPage.fill('input[name="_recipe_settings[cook_time]"]', '30'); - // Click on the Nutrition tab to make those fields visible await adminPage.click('#cooked-recipe-tab-nutrition', { force: true }); await adminPage.fill('input[name="_recipe_settings[nutrition][servings]"]', '4'); - // Set recipe taxonomies await adminPage.check('input[name="tax_input[cp_recipe_category][]"][value="298"]'); - await adminPage.check('input[name="tax_input[cp_recipe_cooking_method][]"][value="288"]'); - await adminPage.check('input[name="tax_input[cp_recipe_cuisine][]"][value="279"]'); - await adminPage.check('input[name="tax_input[cp_recipe_diet][]"][value="268"]'); - - // Set recipe tags (can be multiple) - await adminPage.locator('input[name="newtag[cp_recipe_tags]"]').fill('butter, egg, flour'); - await adminPage.locator('.button.tagadd').first().click(); - // Click the Set featured image link await adminPage.locator('#set-post-thumbnail').click(); - - // Wait for the media modal to appear await adminPage.waitForSelector('.media-modal-content'); - - // If you want to upload a new image, use this: - // await adminPage.setInputFiles('input[type="file"]', 'path/to/your/image.jpg'); - // await adminPage.getByRole('button', { name: 'Upload' }).click(); - - // Or to select an existing image from the media library: await adminPage.getByRole('tab', { name: 'Media Library' }).click(); await adminPage.waitForSelector('.attachment-preview'); - - // Select the first image in the media library await adminPage.locator('.attachment-preview').first().click(); - - // Click the "Set Featured Image" button in the modal await adminPage.locator('.media-button-select').first().click(); - - // Wait for the featured image to be set await adminPage.waitForSelector('#remove-post-thumbnail'); - // Handle all WYSIWYG editors await adminPage.evaluate(() => { - // Excerpt editor const excerptEditor = window.tinyMCE.get('_recipe_settings_excerpt'); if (excerptEditor) { excerptEditor.setContent('This is a brief description of the recipe.'); } - // Notes editor const notesEditor = window.tinyMCE.get('_recipe_settings_notes'); if (notesEditor) { notesEditor.setContent('Important notes about this recipe.'); } - // Add ingredients - // const addIngredientButton = document.querySelector('.cooked-add-ingredient-button'); - // if (addIngredientButton) { - // (addIngredientButton as HTMLElement).click(); - // } - - // Find the first ingredient fields using regex const ingredientBlocks = document.querySelectorAll('.cooked-ingredient-block'); if (ingredientBlocks.length > 0) { const firstIngredient = ingredientBlocks[0]; @@ -114,19 +63,11 @@ test.describe('Create a new complete recipe (admin)', () => { if (amountInput) amountInput.value = '2'; if (measurementSelect) { - // Set the measurement to "cups" measurementSelect.value = 'cup'; } if (itemInput) itemInput.value = 'flour'; } - // Add a direction step - // const addDirectionButton = document.querySelector('.cooked-add-direction-button'); - // if (addDirectionButton) { - // (addDirectionButton as HTMLElement).click(); - // } - - // Find the first direction editor using regex const directionEditors = Object.keys(window.tinyMCE.editors).filter(id => /^direction-\d+-content$/.test(id)); if (directionEditors.length > 0) { const directionEditor = window.tinyMCE.get(directionEditors[0]); @@ -136,21 +77,15 @@ test.describe('Create a new complete recipe (admin)', () => { } }); - // Click the publish button first await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - // Wait for both URL change and success message await Promise.all([ - // Wait for URL to change to post edit page adminPage.waitForURL(/post\.php\?post=\d+&action=edit/), - // Wait for success message adminPage.waitForSelector('.notice-success', { timeout: 10000 }) ]); - // Check for success message content await expect(adminPage.locator('.notice.notice-success')).toContainText('Post published.'); - // Get the post ID from the URL const url = adminPage.url(); const postIdMatch = url.match(/post=(\d+)/); postId = postIdMatch ? parseInt(postIdMatch[1]) : 0; @@ -159,7 +94,6 @@ test.describe('Create a new complete recipe (admin)', () => { }); test('View the recipe (frontend)', async ({ adminContext }) => { - // Get the frontend URL using WP-CLI if (!postId) { throw new Error('Post ID is not set'); } @@ -169,7 +103,6 @@ test.describe('Create a new complete recipe (admin)', () => { }); test('Edit the recipe (admin)', async ({ adminContext }) => { - // Get the frontend URL using WP-CLI if (!postId) { throw new Error('Post ID is not set'); } @@ -178,35 +111,19 @@ test.describe('Create a new complete recipe (admin)', () => { await expect(adminPage.getByText(title)).toBeDefined(); - // Change the title await adminPage.getByLabel('Recipe title ...').fill('Test Recipe Playwright - Edited - ' + Date.now()); - // Click the publish button first await adminPage.getByRole('button', { name: 'Update', exact: true }).click(); - // Wait for both URL change and success message await Promise.all([ - // Wait for URL to change to post edit page adminPage.waitForURL(/post\.php\?post=\d+&action=edit/), - // Wait for success message adminPage.waitForSelector('.notice.notice-success', { timeout: 10000 }) ]); - // Check for success message content await expect(adminPage.locator('.notice.notice-success')).toContainText('Post updated.'); }); }); -// Cleanup task - Delete the recipe -test.afterAll(async () => { - if (postId) { - try { - console.log(`Cleaning up: Deleting recipe ${postId}`); - execSync(`wp post delete ${postId} --force`); - } catch (error) { - console.error(`Failed to delete recipe ${postId}:`, error); - } - } else { - console.log('No post ID to delete'); - } +test.afterAll(() => { + deletePost(postId); }); diff --git a/tests/playwright/tests/1_admin_user/4-csv-import.spec.ts b/tests/playwright/tests/admin/csv-import.spec.ts similarity index 77% rename from tests/playwright/tests/1_admin_user/4-csv-import.spec.ts rename to tests/playwright/tests/admin/csv-import.spec.ts index fc7178f..8173049 100644 --- a/tests/playwright/tests/1_admin_user/4-csv-import.spec.ts +++ b/tests/playwright/tests/admin/csv-import.spec.ts @@ -1,6 +1,6 @@ -import { test as base, expect, Page } from '@playwright/test'; -import { ensureValidAuth, getAuthPath } from '../../utils/auth'; -import { execSync } from 'child_process'; +import { test, expect } from '../../utils/fixtures'; +import { Page } from '@playwright/test'; +import { deletePostsByTitle } from '../../utils/wp-cli'; import path from 'path'; const TEST_DATA_DIR = path.resolve(__dirname, '../../../test_data'); @@ -15,26 +15,11 @@ const LARGE_CSV_TITLES = [ const allImportedTitles: string[] = []; -const test = base.extend({ - adminContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('mtresova') - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'mtresova', 'password'); - await use(context); - await page.close(); - await context.close(); - } -}); - async function importCsvFile(adminPage: Page, csvFileName: string) { await adminPage.goto('/wp-admin/admin.php?page=cooked_import', { waitUntil: 'networkidle' }); - // Click the anchor inside the CSV Import tab li to trigger jQuery handler await adminPage.locator('#cooked-settings-tab-csv_import a').click(); - // Wait for the tab content panel to become visible await expect(adminPage.locator('#cooked-settings-tab-content-csv_import')).toBeVisible({ timeout: 10000 }); const csvPath = path.join(TEST_DATA_DIR, csvFileName); @@ -108,24 +93,9 @@ test.describe('CSV Import (admin)', () => { }); }); -test.afterAll(async () => { +test.afterAll(() => { const allTitles = [...SMALL_CSV_TITLES, ...MEDIUM_CSV_TITLES, ...LARGE_CSV_TITLES]; for (const title of allTitles) { - try { - const result = execSync( - `wp post list --post_type=cp_recipe --post_status=draft --field=ID --title="${title}" 2>/dev/null`, - { encoding: 'utf-8' } - ).trim(); - - if (result) { - const ids = result.split('\n').filter(id => id.trim()); - for (const id of ids) { - console.log(`Cleaning up: Deleting recipe "${title}" (ID: ${id})`); - execSync(`wp post delete ${id} --force`); - } - } - } catch (error) { - console.error(`Failed to clean up recipe "${title}":`, error); - } + deletePostsByTitle(title); } }); diff --git a/tests/playwright/tests/browse/filter-category.spec.ts b/tests/playwright/tests/browse/filter-category.spec.ts new file mode 100644 index 0000000..ffe83b0 --- /dev/null +++ b/tests/playwright/tests/browse/filter-category.spec.ts @@ -0,0 +1,22 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Filter Browse Recipes (anonymous user)', () => { + test.use({ storageState: { cookies: [], origins: [] } }); + + test('Filter recipes by category', async ({ page }) => { + await page.goto('/browse-recipes', { waitUntil: 'networkidle' }); + + await expect(page.locator('.cooked-recipe-search')).toBeDefined(); + + await page.click('.cooked-browse-select'); + await expect(page.locator('.cooked-browse-select-block')).toBeVisible(); + await page.click('.cooked-tax-column:has-text("Categories") a[href*="/recipe-category/bread"]'); + + await page.waitForURL('**/browse-recipes/recipe-category/bread'); + await page.waitForLoadState('networkidle'); + + const firstRecipe = page.locator('.cooked-recipe').first(); + await expect(firstRecipe.locator('.cooked-recipe-name')).toHaveText('Albanian Bread Stuffed with Cheese (Pogaça me Djathë)'); + await expect(firstRecipe).toHaveAttribute('id', 'cooked-recipe-3587'); + }); +}); diff --git a/tests/playwright/tests/3_anon_user/5-search-browse-recipe.spec.ts b/tests/playwright/tests/browse/search.spec.ts similarity index 76% rename from tests/playwright/tests/3_anon_user/5-search-browse-recipe.spec.ts rename to tests/playwright/tests/browse/search.spec.ts index f3c46c2..e87794e 100644 --- a/tests/playwright/tests/3_anon_user/5-search-browse-recipe.spec.ts +++ b/tests/playwright/tests/browse/search.spec.ts @@ -6,24 +6,17 @@ test.describe('Search Browse Recipes (anonymous user)', () => { test('Search recipes by title (beef)', async ({ page }) => { await page.goto('/browse-recipes', { waitUntil: 'networkidle' }); - // Check if the search section is present await expect(page.locator('.basil-main-template .cooked-recipe-search')).toBeDefined(); - // Enter "beef" in the search input await page.fill('.basil-main-template .cooked-browse-search', 'beef'); - // Trigger form submission via JavaScript event await page.evaluate(() => { document.querySelector('.basil-main-template .cooked-browse-search').closest('form').dispatchEvent(new Event('submit')); }); - // Wait for navigation and URL change await page.waitForURL('**/browse-recipes/search/beef/sort/date_desc'); - - // Wait for the page to finish loading after the search await page.waitForLoadState('networkidle'); - // Check if the first recipe is Albanian Flatbread const firstRecipe = page.locator('.cooked-recipe').first(); await expect(firstRecipe.locator('.cooked-recipe-name')).toHaveText('Albanian Pan-Fried Meatballs (Qofte të Skuqura)'); await expect(firstRecipe).toHaveAttribute('id', 'cooked-recipe-2761'); @@ -32,26 +25,19 @@ test.describe('Search Browse Recipes (anonymous user)', () => { test('Search recipes by title (chicken)', async ({ page }) => { await page.goto('/browse-recipes', { waitUntil: 'networkidle' }); - // Check if the search section is present await expect(page.locator('.basil-main-template .cooked-recipe-search')).toBeDefined(); - // Enter "beef" in the search input await page.fill('.basil-main-template .cooked-browse-search', 'chicken'); - // Trigger form submission via JavaScript event await page.evaluate(() => { document.querySelector('.basil-main-template .cooked-browse-search').closest('form').dispatchEvent(new Event('submit')); }); - // Wait for navigation and URL change await page.waitForURL('**/browse-recipes/search/chicken/sort/date_desc'); - - // Wait for the page to finish loading after the search await page.waitForLoadState('networkidle'); - // Check if the first recipe is Albanian Flatbread const firstRecipe = page.locator('.cooked-recipe').first(); await expect(firstRecipe.locator('.cooked-recipe-name')).toHaveText('Meatball Soup (Supë me Pasha Qofte)'); await expect(firstRecipe).toHaveAttribute('id', 'cooked-recipe-2886'); }); -}); \ No newline at end of file +}); diff --git a/tests/playwright/tests/browse/sort-date.spec.ts b/tests/playwright/tests/browse/sort-date.spec.ts new file mode 100644 index 0000000..860e6cc --- /dev/null +++ b/tests/playwright/tests/browse/sort-date.spec.ts @@ -0,0 +1,35 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Sort Browse Recipes (anonymous user)', () => { + test.use({ storageState: { cookies: [], origins: [] } }); + + test('Sort recipes by date', async ({ page }) => { + await page.goto('/browse-recipes', { waitUntil: 'networkidle' }); + + await expect(page.locator('.cooked-recipe-search')).toBeDefined(); + + await page.selectOption('.cooked-sortby-select', 'date_asc'); + await page.evaluate(() => { + document.querySelector('.cooked-sortby-select').closest('form').dispatchEvent(new Event('submit')); + }); + + await page.waitForURL('**/browse-recipes/sort/date_asc'); + await page.waitForLoadState('networkidle'); + + const firstRecipe = page.locator('.cooked-recipe').first(); + await expect(firstRecipe.locator('.cooked-recipe-name')).toHaveText('Albanian Flatbread (Pite në Tigan)'); + await expect(firstRecipe).toHaveAttribute('id', 'cooked-recipe-1866'); + + await page.selectOption('.cooked-sortby-select', 'date_desc'); + await page.evaluate(() => { + document.querySelector('.cooked-sortby-select').closest('form').dispatchEvent(new Event('submit')); + }); + + await page.waitForURL('**/browse-recipes/sort/date_desc'); + await page.waitForLoadState('networkidle'); + + const secondRecipe = page.locator('.cooked-recipe').first(); + await expect(secondRecipe.locator('.cooked-recipe-name')).toHaveText('Albanian Bread Stuffed with Cheese (Pogaça me Djathë)'); + await expect(secondRecipe).toHaveAttribute('id', 'cooked-recipe-3587'); + }); +}); diff --git a/tests/playwright/tests/0_general/1-home-page.spec.ts b/tests/playwright/tests/home/home-page.spec.ts similarity index 91% rename from tests/playwright/tests/0_general/1-home-page.spec.ts rename to tests/playwright/tests/home/home-page.spec.ts index 50e6bc4..7988042 100644 --- a/tests/playwright/tests/0_general/1-home-page.spec.ts +++ b/tests/playwright/tests/home/home-page.spec.ts @@ -5,7 +5,6 @@ test.describe( 'Check the Home Page', () => { const response = await page.goto('https://dev.mimisrecipes.ddev.site/'); await expect(page.getByText('Welcome to Mimis Recipes!')).toBeVisible(); - // Check the status code 200 expect(response?.status()).toBe(200); }); }); diff --git a/tests/playwright/tests/6_security/1-xss-prevention.spec.ts b/tests/playwright/tests/security/xss-prevention.spec.ts similarity index 84% rename from tests/playwright/tests/6_security/1-xss-prevention.spec.ts rename to tests/playwright/tests/security/xss-prevention.spec.ts index 8333291..3fb10c6 100644 --- a/tests/playwright/tests/6_security/1-xss-prevention.spec.ts +++ b/tests/playwright/tests/security/xss-prevention.spec.ts @@ -1,6 +1,5 @@ -import { test as base, expect } from '@playwright/test'; -import { ensureValidAuth, getAuthPath } from '../../utils/auth'; -import { execSync } from 'child_process'; +import { test, expect } from '../../utils/fixtures'; +import { deletePost } from '../../utils/wp-cli'; var postId: number; @@ -16,7 +15,6 @@ declare global { } } -// XSS payloads to test const XSS_PAYLOADS = { scriptTag: '', imgOnerror: '', @@ -28,27 +26,10 @@ const XSS_PAYLOADS = { nestedScript: '</script>', }; -// Create a fixture for authentication -const test = base.extend({ - adminContext: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: getAuthPath('mtresova') - }); - const page = await context.newPage(); - await ensureValidAuth(page, 'mtresova', 'password'); - await use(context); - await page.close(); - await context.close(); - } -}); - test.describe('XSS Prevention Tests', () => { - // These tests verify that XSS payloads are properly sanitized on save or escaped on output - test('Recipe title should be escaped against XSS on output', async ({ adminContext }) => { const adminPage = await adminContext.newPage(); - - // Track if XSS is triggered via alert dialogs + let xssAlertTriggered = false; adminPage.on('dialog', async (dialog) => { if (dialog.message().includes('XSS')) { @@ -58,33 +39,25 @@ test.describe('XSS Prevention Tests', () => { }); await adminPage.goto('/wp-admin/post-new.php?post_type=cp_recipe', { waitUntil: 'networkidle' }); - + const xssTitle = 'Test XSS ' + XSS_PAYLOADS.scriptTag + ' ' + Date.now(); - - // Set recipe title with XSS payload + await adminPage.getByLabel('Recipe title ...').fill(xssTitle); - - // Set minimum required fields await adminPage.fill('input[name="_recipe_settings[prep_time]"]', '10'); - - // Publish the recipe await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - + await Promise.all([ adminPage.waitForURL(/post\.php\?post=\d+&action=edit/), adminPage.waitForSelector('.notice-success', { timeout: 10000 }) ]); - - // Get the post ID from the URL + const url = adminPage.url(); const postIdMatch = url.match(/post=(\d+)/); postId = postIdMatch ? parseInt(postIdMatch[1]) : 0; expect(postId).toBeTruthy(); - - // View the recipe on the frontend + const frontendPage = await adminContext.newPage(); - - // Track XSS on frontend + let frontendXssTriggered = false; frontendPage.on('dialog', async (dialog) => { if (dialog.message().includes('XSS')) { @@ -92,26 +65,22 @@ test.describe('XSS Prevention Tests', () => { } await dialog.dismiss(); }); - + await frontendPage.goto('/?post_type=cp_recipe&p=' + postId, { waitUntil: 'networkidle' }); - - // Check that the XSS was not triggered via window variable or dialogs + const xssTriggeredOnPage = await frontendPage.evaluate(() => window.xssTriggered === true); - + expect(xssAlertTriggered).toBe(false); expect(frontendXssTriggered).toBe(false); expect(xssTriggeredOnPage).toBe(false); - - // Cleanup + await frontendPage.close(); - - // Delete the recipe - execSync(`wp post delete ${postId} --force`); + deletePost(postId); }); test('Recipe excerpt should be sanitized against XSS', async ({ adminContext }) => { const adminPage = await adminContext.newPage(); - + let xssAlertTriggered = false; adminPage.on('dialog', async (dialog) => { if (dialog.message().includes('XSS')) { @@ -121,37 +90,33 @@ test.describe('XSS Prevention Tests', () => { }); await adminPage.goto('/wp-admin/post-new.php?post_type=cp_recipe', { waitUntil: 'networkidle' }); - + const title = 'Test Recipe XSS Excerpt: ' + Date.now(); - - // Set recipe title + await adminPage.getByLabel('Recipe title ...').fill(title); await adminPage.fill('input[name="_recipe_settings[prep_time]"]', '10'); - - // Set excerpt with XSS payloads via TinyMCE + await adminPage.evaluate((payload) => { const excerptEditor = window.tinyMCE.get('_recipe_settings_excerpt'); if (excerptEditor) { excerptEditor.setContent(payload); } }, XSS_PAYLOADS.imgOnerror); - - // Publish the recipe + await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - + await Promise.all([ adminPage.waitForURL(/post\.php\?post=\d+&action=edit/), adminPage.waitForSelector('.notice-success', { timeout: 10000 }) ]); - + const url = adminPage.url(); const postIdMatch = url.match(/post=(\d+)/); postId = postIdMatch ? parseInt(postIdMatch[1]) : 0; expect(postId).toBeTruthy(); - - // View the recipe on the frontend + const frontendPage = await adminContext.newPage(); - + let frontendXssTriggered = false; frontendPage.on('dialog', async (dialog) => { if (dialog.message().includes('XSS')) { @@ -159,27 +124,26 @@ test.describe('XSS Prevention Tests', () => { } await dialog.dismiss(); }); - + await frontendPage.goto('/?post_type=cp_recipe&p=' + postId, { waitUntil: 'networkidle' }); - + const xssTriggeredOnPage = await frontendPage.evaluate(() => window.xssTriggered === true); - + expect(xssAlertTriggered).toBe(false); expect(frontendXssTriggered).toBe(false); expect(xssTriggeredOnPage).toBe(false); - - // Verify onerror handler is not present + const pageContent = await frontendPage.content(); expect(pageContent).not.toContain('onerror="window.xssTriggered'); expect(pageContent).not.toContain("onerror='window.xssTriggered"); - + await frontendPage.close(); - execSync(`wp post delete ${postId} --force`); + deletePost(postId); }); test('Recipe notes should be sanitized against XSS', async ({ adminContext }) => { const adminPage = await adminContext.newPage(); - + let xssAlertTriggered = false; adminPage.on('dialog', async (dialog) => { if (dialog.message().includes('XSS')) { @@ -189,37 +153,33 @@ test.describe('XSS Prevention Tests', () => { }); await adminPage.goto('/wp-admin/post-new.php?post_type=cp_recipe', { waitUntil: 'networkidle' }); - + const title = 'Test Recipe XSS Notes: ' + Date.now(); - - // Set recipe title + await adminPage.getByLabel('Recipe title ...').fill(title); await adminPage.fill('input[name="_recipe_settings[prep_time]"]', '10'); - - // Set notes with SVG XSS payload via TinyMCE + await adminPage.evaluate((payload) => { const notesEditor = window.tinyMCE.get('_recipe_settings_notes'); if (notesEditor) { notesEditor.setContent(payload); } }, XSS_PAYLOADS.svgOnload); - - // Publish the recipe + await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - + await Promise.all([ adminPage.waitForURL(/post\.php\?post=\d+&action=edit/), adminPage.waitForSelector('.notice-success', { timeout: 10000 }) ]); - + const url = adminPage.url(); const postIdMatch = url.match(/post=(\d+)/); postId = postIdMatch ? parseInt(postIdMatch[1]) : 0; expect(postId).toBeTruthy(); - - // View the recipe on the frontend + const frontendPage = await adminContext.newPage(); - + let frontendXssTriggered = false; frontendPage.on('dialog', async (dialog) => { if (dialog.message().includes('XSS')) { @@ -227,27 +187,26 @@ test.describe('XSS Prevention Tests', () => { } await dialog.dismiss(); }); - + await frontendPage.goto('/?post_type=cp_recipe&p=' + postId, { waitUntil: 'networkidle' }); - + const xssTriggeredOnPage = await frontendPage.evaluate(() => window.xssTriggered === true); - + expect(xssAlertTriggered).toBe(false); expect(frontendXssTriggered).toBe(false); expect(xssTriggeredOnPage).toBe(false); - - // Verify onload handler is not present + const pageContent = await frontendPage.content(); expect(pageContent).not.toContain('onload="window.xssTriggered'); expect(pageContent).not.toContain("onload='window.xssTriggered"); - + await frontendPage.close(); - execSync(`wp post delete ${postId} --force`); + deletePost(postId); }); test('Recipe directions should be sanitized against XSS', async ({ adminContext }) => { const adminPage = await adminContext.newPage(); - + let xssAlertTriggered = false; adminPage.on('dialog', async (dialog) => { if (dialog.message().includes('XSS')) { @@ -257,14 +216,12 @@ test.describe('XSS Prevention Tests', () => { }); await adminPage.goto('/wp-admin/post-new.php?post_type=cp_recipe', { waitUntil: 'networkidle' }); - + const title = 'Test Recipe XSS Directions: ' + Date.now(); - - // Set recipe title + await adminPage.getByLabel('Recipe title ...').fill(title); await adminPage.fill('input[name="_recipe_settings[prep_time]"]', '10'); - - // Set direction content with XSS payload + await adminPage.evaluate((payload) => { const directionEditors = Object.keys(window.tinyMCE.editors).filter(id => /^direction-\d+-content$/.test(id)); if (directionEditors.length > 0) { @@ -274,23 +231,21 @@ test.describe('XSS Prevention Tests', () => { } } }, XSS_PAYLOADS.eventHandler); - - // Publish the recipe + await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - + await Promise.all([ adminPage.waitForURL(/post\.php\?post=\d+&action=edit/), adminPage.waitForSelector('.notice-success', { timeout: 10000 }) ]); - + const url = adminPage.url(); const postIdMatch = url.match(/post=(\d+)/); postId = postIdMatch ? parseInt(postIdMatch[1]) : 0; expect(postId).toBeTruthy(); - - // View the recipe on the frontend + const frontendPage = await adminContext.newPage(); - + let frontendXssTriggered = false; frontendPage.on('dialog', async (dialog) => { if (dialog.message().includes('XSS')) { @@ -298,27 +253,26 @@ test.describe('XSS Prevention Tests', () => { } await dialog.dismiss(); }); - + await frontendPage.goto('/?post_type=cp_recipe&p=' + postId, { waitUntil: 'networkidle' }); - + const xssTriggeredOnPage = await frontendPage.evaluate(() => window.xssTriggered === true); - + expect(xssAlertTriggered).toBe(false); expect(frontendXssTriggered).toBe(false); expect(xssTriggeredOnPage).toBe(false); - - // Verify onclick handler is not present + const pageContent = await frontendPage.content(); expect(pageContent).not.toContain('onclick="window.xssTriggered'); expect(pageContent).not.toContain("onclick='window.xssTriggered"); - + await frontendPage.close(); - execSync(`wp post delete ${postId} --force`); + deletePost(postId); }); test('Recipe ingredient names should be sanitized against XSS', async ({ adminContext }) => { const adminPage = await adminContext.newPage(); - + let xssAlertTriggered = false; adminPage.on('dialog', async (dialog) => { if (dialog.message().includes('XSS')) { @@ -328,42 +282,38 @@ test.describe('XSS Prevention Tests', () => { }); await adminPage.goto('/wp-admin/post-new.php?post_type=cp_recipe', { waitUntil: 'networkidle' }); - + const title = 'Test Recipe XSS Ingredients: ' + Date.now(); - - // Set recipe title + await adminPage.getByLabel('Recipe title ...').fill(title); await adminPage.fill('input[name="_recipe_settings[prep_time]"]', '10'); - - // Set ingredient with XSS payload + await adminPage.evaluate((payload) => { const ingredientBlocks = document.querySelectorAll('.cooked-ingredient-block'); if (ingredientBlocks.length > 0) { const firstIngredient = ingredientBlocks[0]; const amountInput = firstIngredient.querySelector('input[data-ingredient-part="amount"]') as HTMLInputElement; const itemInput = firstIngredient.querySelector('input[data-ingredient-part="name"]') as HTMLInputElement; - + if (amountInput) amountInput.value = '1'; if (itemInput) itemInput.value = payload; } }, XSS_PAYLOADS.scriptTag); - - // Publish the recipe + await adminPage.getByRole('button', { name: 'Publish', exact: true }).click(); - + await Promise.all([ adminPage.waitForURL(/post\.php\?post=\d+&action=edit/), adminPage.waitForSelector('.notice-success', { timeout: 10000 }) ]); - + const url = adminPage.url(); const postIdMatch = url.match(/post=(\d+)/); postId = postIdMatch ? parseInt(postIdMatch[1]) : 0; expect(postId).toBeTruthy(); - - // View the recipe on the frontend + const frontendPage = await adminContext.newPage(); - + let frontendXssTriggered = false; frontendPage.on('dialog', async (dialog) => { if (dialog.message().includes('XSS')) { @@ -371,26 +321,25 @@ test.describe('XSS Prevention Tests', () => { } await dialog.dismiss(); }); - + await frontendPage.goto('/?post_type=cp_recipe&p=' + postId, { waitUntil: 'networkidle' }); - + const xssTriggeredOnPage = await frontendPage.evaluate(() => window.xssTriggered === true); - + expect(xssAlertTriggered).toBe(false); expect(frontendXssTriggered).toBe(false); expect(xssTriggeredOnPage).toBe(false); - - // Verify the script tag is not present as executable + const pageContent = await frontendPage.content(); expect(pageContent).not.toContain(''; + }, + function () { + return Cooked_SEO::json_ld( + [ + 'id' => 1, + 'title' => 'Test Recipe', + 'ingredients' => [], + 'directions' => [], + 'nutrition' => [ 'servings' => 1 ], + ] + ); + } + ); + + $this->assertStringContainsString( '{"filtered":true}', $html ); + } + + public function test_cooked_schema_array_can_add_a_key() { + $schema = $this->with_filter( + 'cooked_schema_array', + function ( $value ) { + $value['filtered'] = 'yes'; + return $value; + }, + function () { + return Cooked_SEO::schema_values( + [ + 'id' => 1, + 'title' => 'Test Recipe', + 'ingredients' => [], + 'directions' => [], + 'nutrition' => [ 'servings' => 1 ], + ] + ); + } + ); + + $this->assertSame( 'yes', $schema['filtered'] ); + $this->assertSame( 'Recipe', $schema['@type'] ); + } + + public function test_cooked_format_author_name_can_replace_name() { + $name = $this->with_filter( + 'cooked_format_author_name', + function () { + return 'Filtered Author'; + }, + function () { + return Cooked_Users::format_author_name( 'John Doe', 'full' ); + } + ); + + $this->assertSame( 'Filtered Author', $name ); + } + + public function test_cooked_format_author_name_safe_array_skips_escaping() { + $name = $this->with_filter( + 'cooked_format_author_name', + function () { + return [ 'Safe', true ]; + }, + function () { + return Cooked_Users::format_author_name( 'John Doe', 'full' ); + } + ); + + $this->assertSame( 'Safe', $name ); + } + + public function test_cooked_recipe_editor_caps_reach_add_role() { + $this->with_filter( + 'cooked_recipe_editor_caps', + function ( $caps ) { + $caps['filtered_cap'] = 1; + return $caps; + }, + function () { + Cooked_Roles::add_roles(); + } + ); + + $this->assertSame( + 1, + $GLOBALS['_cooked_test_registered_roles']['cooked_recipe_editor']['caps']['filtered_cap'] + ); + } + + public function test_cooked_taxonomy_settings_can_change_slug() { + $taxonomies = $this->with_filter( + 'cooked_taxonomy_settings', + function ( $value ) { + $value['cp_recipe_category'] = 'filtered-category'; + return $value; + }, + function () { + return Cooked_Taxonomies::get(); + } + ); + + $this->assertSame( 'filtered-category', $taxonomies['cp_recipe_category']['rewrite']['slug'] ); + } + + public function test_cooked_taxonomies_can_add_a_taxonomy() { + $taxonomies = $this->with_filter( + 'cooked_taxonomies', + function ( $value ) { + $value['cp_recipe_sentinel'] = [ 'labels' => [ 'name' => 'Sentinel' ] ]; + return $value; + }, + function () { + return Cooked_Taxonomies::get(); + } + ); + + $this->assertArrayHasKey( 'cp_recipe_sentinel', $taxonomies ); + } + + public function test_cooked_taxonomy_settings_update_writes_filtered_permalink() { + $GLOBALS['_cooked_test_is_admin'] = true; + $_GET['settings-updated'] = 'true'; + $_GET['page'] = 'cooked_settings'; + $GLOBALS['_cooked_test_options']['cooked_settings'] = [ + 'recipe_permalink' => 'recipes', + 'recipe_author_permalink' => 'recipe-author', + 'recipe_category_permalink' => 'recipe-category', + ]; + + $this->with_filter( + 'cooked_taxonomy_settings_update', + function ( $value ) { + $value['recipe_permalink'] = 'filtered-recipes'; + return $value; + }, + function () { + Cooked_Post_Types::init(); + } + ); + + $this->assertSame( + 'filtered-recipes', + $GLOBALS['_cooked_test_options']['cooked_settings']['recipe_permalink'] + ); + } + + public function test_cooked_post_types_can_add_a_type() { + $types = $this->with_filter( + 'cooked_post_types', + function ( $value ) { + $value['cp_recipe_extra'] = [ 'labels' => [ 'name' => 'Extra' ] ]; + return $value; + }, + function () { + return Cooked_Post_Types::get(); + } + ); + + $this->assertArrayHasKey( 'cp_recipe_extra', $types ); + } + + public function test_cooked_widgets_can_add_a_widget() { + $widgets = new Cooked_Widgets(); + $this->with_filter( + 'cooked_widgets', + function ( $value ) { + $value[] = 'Cooked_Widget_Sentinel'; + return $value; + }, + function () use ( $widgets ) { + $widgets->register_widgets(); + } + ); + + $this->assertContains( 'Cooked_Widget_Sentinel', $GLOBALS['_cooked_test_registered_widgets'] ); + } + + public function test_cooked_can_show_recipe_hides_card_widget() { + $widget = new Cooked_Widget_Recipe_Card(); + $args = [ + 'before_widget' => '
      ', + 'after_widget' => '
      ', + 'before_title' => '

      ', + 'after_title' => '

      ', + ]; + + $hidden = $this->with_filter( + 'cooked_can_show_recipe', + function () { + return false; + }, + function () use ( $widget, $args ) { + return $this->capture_output( + function () use ( $widget, $args ) { + $widget->widget( $args, [ 'recipe_id' => 1, 'title' => 'Card' ] ); + } + ); + } + ); + + $this->assertSame( '', $hidden ); + + $shown = $this->capture_output( + function () use ( $widget, $args ) { + $widget->widget( $args, [ 'recipe_id' => 1, 'title' => 'Card' ] ); + } + ); + + $this->assertStringContainsString( '
      ', $shown ); + } + + public function test_cooked_allergens_can_add_an_allergen() { + $allergens = $this->with_filter( + 'cooked_allergens', + function ( $value ) { + $value['sentinel'] = [ + 'label' => 'Sentinel Allergen', + 'icon' => 'allergen-sentinel', + ]; + return $value; + }, + function () { + return Cooked_Allergens::get_allergens(); + } + ); + + $this->assertArrayHasKey( 'sentinel', $allergens ); + $this->assertSame( 'Sentinel Allergen', $allergens['sentinel']['label'] ); + } + + public function test_cooked_recipe_card_allergen_hooks_can_add_a_hook() { + $hooks = $this->with_filter( + 'cooked_recipe_card_allergen_hooks', + function ( $value ) { + $value[] = 'cooked_sentinel_allergen_hook'; + return $value; + }, + function () { + return Cooked_Allergens::get_recipe_card_hooks(); + } + ); + + $this->assertContains( 'cooked_sentinel_allergen_hook', $hooks ); + } + + public function test_cooked_import_tabs_fields_can_add_a_tab() { + $tabs = $this->with_filter( + 'cooked_import_tabs_fields', + function ( $value ) { + $value['sentinel'] = [ 'name' => 'Sentinel Import' ]; + return $value; + }, + function () { + return Cooked_Import::tabs_fields(); + } + ); + + $this->assertArrayHasKey( 'sentinel', $tabs ); + $this->assertSame( 'Sentinel Import', $tabs['sentinel']['name'] ); + } + + public function test_cp_recipe_metabox_post_types_registers_meta_box() { + $meta = new Cooked_Recipe_Meta(); + + $this->with_filter( + 'cp_recipe_metabox_post_types', + function () { + return [ 'cp_recipe', 'page' ]; + }, + function () use ( $meta ) { + $meta->add_recipe_meta_box( 'page' ); + } + ); + + $screens = array_column( $GLOBALS['_cooked_test_meta_boxes'], 'screen' ); + $this->assertContains( 'page', $screens ); + } + + public function test_cooked_recipe_admin_tabs_can_add_a_tab() { + $GLOBALS['_cooked_test_post_meta'][1]['_recipe_settings'] = [ + 'cooked_version' => COOKED_VERSION, + 'ingredients' => [], + ]; + + $html = $this->with_filter( + 'cooked_recipe_admin_tabs', + function ( $tabs ) { + $tabs['sentinel'] = [ + 'icon' => 'star', + 'name' => 'Sentinel Tab', + 'conditional' => false, + 'value' => false, + ]; + return $tabs; + }, + function () { + return $this->capture_output( + function () { + cooked_render_recipe_fields( 1 ); + } + ); + } + ); + + $this->assertStringContainsString( 'Sentinel Tab', $html ); + } + + public function test_cooked_ingredient_field_classes_appear_in_meta() { + $GLOBALS['_cooked_test_post_meta'][1]['_recipe_settings'] = [ + 'cooked_version' => COOKED_VERSION, + 'ingredients' => [ + [ 'amount' => '1', 'measurement' => 'cup', 'name' => 'Flour' ], + ], + ]; + + $html = $this->with_filter( + 'cooked_ingredient_field_classes', + function ( $classes ) { + return $classes . ' filtered-ingredient'; + }, + function () { + return $this->capture_output( + function () { + cooked_render_recipe_fields( 1 ); + } + ); + } + ); + + $this->assertStringContainsString( 'filtered-ingredient', $html ); + } + + public function test_cooked_available_info_vars_appear_in_shortcodes_tab() { + $GLOBALS['_cooked_test_post_meta'][1]['_recipe_settings'] = [ + 'cooked_version' => COOKED_VERSION, + ]; + + $html = $this->with_filter( + 'cooked_available_info_vars', + function ( $vars ) { + $vars['sentinel'] = 'Sentinel Var'; + return $vars; + }, + function () { + return $this->capture_output( + function () { + cooked_render_recipe_fields( 1 ); + } + ); + } + ); + + $this->assertStringContainsString( 'sentinel', $html ); + $this->assertStringContainsString( 'Sentinel Var', $html ); + } + + public function test_cooked_seo_recipe_content_and_should_update_on_meta_save() { + $_POST['cooked_recipe_custom_box_nonce'] = 'test_nonce'; + $_POST['_recipe_settings'] = [ + 'excerpt' => 'Saved excerpt', + 'title' => 'Saved', + ]; + + $meta = new Cooked_Recipe_Meta(); + + $this->with_filter( + 'cooked_seo_recipe_content', + function () { + return 'FILTERED_SEO_CONTENT'; + }, + function () use ( $meta ) { + $this->with_filter( + 'cooked_should_update_post_content', + function () { + return true; + }, + function () use ( $meta ) { + $meta->save_recipe_meta_box( 7 ); + } + ); + } + ); + + $last = end( $GLOBALS['_cooked_test_updated_posts'] ); + $this->assertSame( 'FILTERED_SEO_CONTENT', $last['post_content'] ); + $this->assertSame( 7, $last['ID'] ); + + $GLOBALS['_cooked_test_updated_posts'] = []; + $this->with_filter( + 'cooked_should_update_post_content', + function () { + return false; + }, + function () use ( $meta ) { + $meta->save_recipe_meta_box( 8 ); + } + ); + + $last = end( $GLOBALS['_cooked_test_updated_posts'] ); + $this->assertArrayNotHasKey( 'post_content', $last ); + $this->assertSame( 8, $last['ID'] ); + } + + public function test_cooked_seo_recipe_content_and_should_update_on_csv_import() { + $this->with_filter( + 'cooked_seo_recipe_content', + function () { + return 'CSV_FILTERED_SEO'; + }, + function () { + Cooked_CSV_Import::import_recipe( + [ + 'title' => 'Imported Recipe', + 'excerpt' => 'Imported excerpt', + ] + ); + } + ); + + $last = end( $GLOBALS['_cooked_test_updated_posts'] ); + $this->assertSame( 'CSV_FILTERED_SEO', $last['post_content'] ); + + $GLOBALS['_cooked_test_updated_posts'] = []; + $this->with_filter( + 'cooked_should_update_post_content', + function () { + return false; + }, + function () { + Cooked_CSV_Import::import_recipe( + [ + 'title' => 'Imported Recipe Two', + 'excerpt' => 'Excerpt', + ] + ); + } + ); + + $last = end( $GLOBALS['_cooked_test_updated_posts'] ); + $this->assertArrayNotHasKey( 'post_content', $last ); + } + + public function test_cooked_timer_sound_mp3_is_localized() { + $enqueues = new Cooked_Enqueues(); + $this->with_filter( + 'cooked_timer_sound_mp3', + function () { + return 'http://example.com/filtered-ding.mp3'; + }, + function () use ( $enqueues ) { + $enqueues->enqueues( '' ); + } + ); + + $found = false; + foreach ( $GLOBALS['_cooked_test_inline_scripts'] as $script ) { + if ( false !== strpos( $script['data'], 'filtered-ding.mp3' ) ) { + $found = true; + break; + } + } + $this->assertTrue( $found ); + } + + public function test_cooked_whats_new_title_appears_in_changelog() { + $html = $this->with_filter( + 'cooked_whats_new_title', + function () { + return 'Filtered Whats New'; + }, + function () { + return Cooked_Functions::parse_readme_changelog(); + } + ); + + $this->assertStringContainsString( 'Filtered Whats New', $html ); + } + + public function test_cooked_default_print_options_check_a_box() { + $html = $this->with_filter( + 'cooked_default_print_options', + function ( $value ) { + $value['print_options_info'] = 'checked'; + return $value; + }, + function () { + return $this->capture_output( + function () { + Cooked_Functions::print_options(); + } + ); + } + ); + + $this->assertMatchesRegularExpression( + '/id="print_options_info"[^>]*checked/', + $html + ); + } + + public function test_cooked_version_updates_adds_a_runnable_tool() { + $tools = $this->with_filter( + 'cooked_version_updates', + function ( $updates ) { + $updates['99.0.0'] = [ 'sentinel_tool' ]; + return $updates; + }, + function () { + return Cooked_Updates::get_runnable_tools(); + } + ); + + $ids = array_column( $tools, 'id' ); + $this->assertContains( 'sentinel_tool', $ids ); + } +} diff --git a/tests/phpunit/RecipesFiltersTest.php b/tests/phpunit/RecipesFiltersTest.php new file mode 100644 index 0000000..45a306b --- /dev/null +++ b/tests/phpunit/RecipesFiltersTest.php @@ -0,0 +1,554 @@ + [] ]; + $GLOBALS['recipe_settings'] = [ + 'id' => 1, + 'title' => 'Test Recipe', + 'nutrition' => [ 'servings' => 4 ], + ]; + $GLOBALS['_cooked_test_post_meta'][1]['_recipe_settings'] = [ + 'cooked_version' => COOKED_VERSION, + 'title' => 'Test Recipe', + 'nutrition' => [ 'servings' => 4 ], + ]; + } + + public function test_cooked_single_recipe_settings_can_add_a_key() { + $settings = $this->with_filter( + 'cooked_single_recipe_settings', + function ( $value, $post_id ) { + $value['filtered'] = 'yes-' . $post_id; + return $value; + }, + function () { + return Cooked_Recipes::get_settings( 1 ); + }, + 2 + ); + + $this->assertSame( 'yes-1', $settings['filtered'] ); + $this->assertSame( 1, $settings['id'] ); + } + + public function test_cooked_default_content_can_replace_layout() { + $content = $this->with_filter( + 'cooked_default_content', + function () { + return '[cooked-filtered]'; + }, + function () { + return Cooked_Recipes::default_content(); + } + ); + + $this->assertSame( '[cooked-filtered]', $content ); + } + + public function test_cooked_print_content_can_replace_layout() { + $content = $this->with_filter( + 'cooked_print_content', + function () { + return '[cooked-print-filtered]'; + }, + function () { + return Cooked_Recipes::print_content(); + } + ); + + $this->assertSame( '[cooked-print-filtered]', $content ); + } + + public function test_cooked_fsm_content_can_replace_layout() { + $content = $this->with_filter( + 'cooked_fsm_content', + function () { + return '[cooked-fsm-filtered]'; + }, + function () { + return Cooked_Recipes::fsm_content(); + } + ); + + $this->assertSame( '[cooked-fsm-filtered]', $content ); + } + + public function test_cooked_difficulty_levels_can_add_a_level() { + $levels = $this->with_filter( + 'cooked_difficulty_levels', + function ( $value ) { + $value[4] = 'Expert'; + return $value; + }, + function () { + return Cooked_Recipes::difficulty_levels(); + } + ); + + $this->assertSame( 'Expert', $levels[4] ); + } + + public function test_cooked_gallery_types_can_add_a_type() { + $GLOBALS['_cooked_test_query_posts'] = [ + (object) [ + 'ID' => 50, + 'post_title' => 'Custom Gallery Post', + ], + ]; + $GLOBALS['_cooked_test_titles'][50] = 'Custom Gallery Post'; + + $types = $this->with_filter( + 'cooked_gallery_types', + function ( $value ) { + $value['custom'] = [ + 'title' => 'Custom Gallery', + 'required_class' => 'Cooked_Filter_Custom_Gallery', + ]; + return $value; + }, + function () { + return Cooked_Recipes::gallery_types(); + } + ); + + $this->assertArrayHasKey( 'custom', $types ); + $this->assertSame( 'Custom Gallery', $types['custom']['title'] ); + $this->assertSame( 'Custom Gallery Post', $types['custom']['posts'][50] ); + } + + public function test_cooked_gallery_type_query_changes_wp_query_args() { + $this->with_filter( + 'cooked_gallery_type_envira_query', + function ( $args ) { + $args['meta_key'] = 'filtered_gallery'; + return $args; + }, + function () { + Cooked_Recipes::gallery_types(); + } + ); + + $this->assertSame( 'filtered_gallery', $GLOBALS['_cooked_test_last_query']['meta_key'] ); + } + + public function test_cooked_servings_switcher_options_appear_in_html() { + $html = $this->with_filter( + 'cooked_servings_switcher_options', + function ( $value ) { + $value['dozen'] = [ 'name' => 'Dozen Servings', 'value' => 12 ]; + return $value; + }, + function () { + return $this->capture_output( + function () { + Cooked_Recipes::serving_size_switcher( 4 ); + } + ); + } + ); + + $this->assertStringContainsString( 'Dozen Servings', $html ); + $this->assertStringContainsString( 'value="12"', $html ); + } + + public function test_cooked_ingredient_name_appears_in_html() { + $html = $this->with_filter( + 'cooked_ingredient_name', + function () { + return 'Filtered Flour'; + }, + function () { + return $this->capture_output( + function () { + Cooked_Recipes::single_ingredient( + [ + 'name' => 'Flour', + 'amount' => '1', + 'measurement' => 'cup', + ] + ); + } + ); + } + ); + + $this->assertStringContainsString( 'Filtered Flour', $html ); + $this->assertStringNotContainsString( '>Flour<', $html ); + } + + public function test_cooked_single_ingredient_html_can_replace_markup() { + $html = $this->with_filter( + 'cooked_single_ingredient_html', + function () { + return '
      X
      '; + }, + function () { + return $this->capture_output( + function () { + Cooked_Recipes::single_ingredient( + [ + 'name' => 'Flour', + 'amount' => '1', + 'measurement' => 'cup', + ] + ); + } + ); + } + ); + + $this->assertSame( '
      X
      ', $html ); + } + + public function test_cooked_direction_image_size_is_passed_to_attachment() { + $this->with_filter( + 'cooked_direction_image_size', + function () { + return 'medium'; + }, + function () { + $this->capture_output( + function () { + Cooked_Recipes::single_direction( + [ + 'content' => 'Mix', + 'image' => 15, + ], + 1, + false, + 1, + [] + ); + } + ); + } + ); + + $this->assertSame( 'medium', $GLOBALS['_cooked_test_attachment_image_calls'][0]['size'] ); + } + + public function test_cooked_direction_image_html_can_rewrite_image() { + $GLOBALS['_cooked_test_attachment_image_html'] = ''; + + $html = $this->with_filter( + 'cooked_direction_image_html', + function () { + return ''; + }, + function () { + return $this->capture_output( + function () { + Cooked_Recipes::single_direction( + [ + 'content' => 'Mix', + 'image' => 15, + ], + 1, + false, + 1, + [ 'amp' => true ] + ); + } + ); + } + ); + + $this->assertStringContainsString( '', $html ); + $this->assertStringNotContainsString( '', $html ); + } + + public function test_cooked_sync_c2_recipe_settings_can_add_a_key() { + $settings = $this->with_filter( + 'cooked_sync_c2_recipe_settings', + function ( $value ) { + $value['filtered'] = true; + return $value; + }, + function () { + return Cooked_Recipes::sync_c2_recipe_settings( [], 1 ); + } + ); + + $this->assertTrue( $settings['filtered'] ); + } + + public function test_cooked_browse_sorting_types_appear_in_search_box() { + $html = $this->with_filter( + 'cooked_browse_sorting_types', + function ( $value ) { + $value['rating_desc'] = [ + 'slug' => 'rating_desc', + 'name' => 'Highest Rated', + ]; + return $value; + }, + function () { + $GLOBALS['recipe_args'] = [ 'orderby' => 'date', 'order' => 'desc' ]; + return $this->capture_output( + function () { + echo Cooked_Recipes::recipe_search_box( + [ + 'hide_sorting' => false, + 'hide_browse' => true, + 'compact' => false, + ] + ); + } + ); + } + ); + + $this->assertStringContainsString( 'Highest Rated', $html ); + $this->assertStringContainsString( 'rating_desc', $html ); + } + + public function test_cooked_recipe_query_args_changes_wp_query() { + $this->with_filter( + 'cooked_recipe_query_args', + function ( $args ) { + $args['meta_key'] = 'filtered_key'; + return $args; + }, + function () { + Cooked_Recipes::list_view( $this->list_view_atts() ); + } + ); + + $this->assertSame( 'filtered_key', $GLOBALS['_cooked_test_last_query']['meta_key'] ); + } + + public function test_cooked_recipe_public_query_filters_changes_wp_query() { + $this->with_filter( + 'cooked_recipe_public_query_filters', + function ( $args ) { + $args['post_status'] = 'private'; + return $args; + }, + function () { + Cooked_Recipes::list_view( $this->list_view_atts() ); + } + ); + + $this->assertSame( 'private', $GLOBALS['_cooked_test_last_query']['post_status'] ); + } + + public function test_cooked_tax_query_filter_is_applied_to_query() { + $this->with_filter( + 'cooked_tax_query_filter', + function ( $tax_query ) { + $tax_query[] = [ + 'taxonomy' => 'cp_recipe_category', + 'field' => 'slug', + 'terms' => [ 'filtered-cat' ], + ]; + return $tax_query; + }, + function () { + Cooked_Recipes::list_view( $this->list_view_atts() ); + } + ); + + $this->assertSame( 'filtered-cat', $GLOBALS['_cooked_test_last_query']['tax_query'][0]['terms'][0] ); + } + + public function test_cooked_recipe_list_style_receives_layout_and_sets_style() { + $GLOBALS['_cooked_test_query_posts'] = [ + (object) [ + 'ID' => 1, + 'post_title' => 'Test Recipe', + 'post_excerpt' => 'A short excerpt', + 'post_author' => 1, + 'post_status' => 'publish', + 'post_type' => 'cp_recipe', + ], + ]; + + $received = null; + $html = $this->with_filter( + 'cooked_recipe_list_style', + function ( $style, $layout ) use ( &$received ) { + $received = $layout; + return [ 'sentinel' => 'Cooked_Filter_List_Renderer' ]; + }, + function () { + return Cooked_Recipes::list_view( + $this->list_view_atts( + [ + 'layout' => 'custom-layout', + 'search' => false, + 'pagination' => false, + ] + ) + ); + }, + 2 + ); + + $this->assertSame( 'custom-layout', $received ); + $this->assertStringContainsString( 'sentinel-list-style', $html ); + $this->assertStringContainsString( 'cooked-recipe-sentinel', $html ); + } + + public function test_cooked_active_taxonomies_rewrites_numeric_term_to_slug() { + $GLOBALS['pagenow'] = 'edit.php'; + $GLOBALS['_cooked_test_terms'][] = (object) [ + 'term_id' => 9, + 'name' => 'Soup', + 'slug' => 'soup', + 'taxonomy' => 'cp_recipe_tag', + 'ID' => 9, + ]; + + $query = new WP_Query(); + $query->query_vars['post_type'] = 'cp_recipe'; + $query->query_vars['cp_recipe_tag'] = 9; + + $this->with_filter( + 'cooked_active_taxonomies', + function () { + return [ 'cp_recipe_tag' ]; + }, + function () use ( $query ) { + $recipes = new Cooked_Recipes(); + $recipes->custom_taxonomy_in_query( $query ); + } + ); + + $this->assertSame( 'soup', $query->query_vars['cp_recipe_tag'] ); + } + + public function test_cooked_pagination_style_and_args_change_output() { + $GLOBALS['current_recipe_page'] = 1; + $GLOBALS['atts'] = []; + $query = new WP_Query(); + $query->max_num_pages = 4; + + $html = $this->with_filter( + 'cooked_pagination_args', + function ( $args ) { + $args['prev_text'] = 'FILTER_PREV'; + return $args; + }, + function () use ( $query ) { + return Cooked_Recipes::pagination( $query, [] ); + } + ); + + $this->assertSame( 'FILTER_PREV', $GLOBALS['_cooked_test_paginate_args']['prev_text'] ); + $this->assertStringContainsString( 'page-numbers', $html ); + } + + public function test_cooked_pagination_style_can_swap_renderer() { + $GLOBALS['current_recipe_page'] = 1; + $GLOBALS['atts'] = []; + $query = new WP_Query(); + $query->max_num_pages = 4; + + $html = $this->with_filter( + 'cooked_pagination_style', + function () { + return [ 'numbered_pagination' => 'Cooked_Recipes' ]; + }, + function () use ( $query ) { + return Cooked_Recipes::pagination( $query, [] ); + } + ); + + $this->assertStringContainsString( 'cooked-pagination-numbered', $html ); + } + + public function test_cooked_query_where_filter_changes_meta_sql() { + $q = new WP_Query(); + $q->set( '_cooked_title', 'pasta' ); + + $sql = $this->with_filter( + 'cooked_query_where_filter', + function () { + return "wp_posts.post_title like '%FILTERED%'"; + }, + function () use ( $q ) { + Cooked_Recipes::cooked_pre_get_posts( $q ); + return apply_filters( + 'get_meta_sql', + [ 'where' => ' AND extra_clause' ] + ); + } + ); + + $this->assertStringContainsString( 'FILTERED', $sql['where'] ); + } + + public function test_cooked_recipe_content_filter_changes_singular_content() { + $GLOBALS['_cooked_content_unfiltered'] = false; + $GLOBALS['_cooked_test_is_singular'] = true; + $GLOBALS['post'] = get_post( 1 ); + $GLOBALS['wp_embed'] = new class { + public function autoembed( $content ) { + return $content; + } + }; + $GLOBALS['_cooked_test_post_meta'][1]['_recipe_settings'] = [ + 'cooked_version' => COOKED_VERSION, + 'title' => 'Test Recipe', + 'content' => '[cooked-ingredients]', + 'nutrition' => [ 'servings' => 1 ], + ]; + + $recipes = new Cooked_Recipes(); + $filtered = $this->with_filter( + 'cooked_recipe_content_filter', + function () { + return 'FILTERED_RECIPE_CONTENT'; + }, + function () use ( $recipes ) { + return $recipes->recipe_template( 'original' ); + } + ); + + $this->assertSame( 'FILTERED_RECIPE_CONTENT', $filtered ); + } + + public function test_cooked_term_name_appears_in_taxonomy_list() { + if ( ! class_exists( 'Cooked_Taxonomies' ) ) { + require_once COOKED_DIR . 'includes/class.cooked-taxonomies.php'; + } + + $GLOBALS['_cooked_test_terms'][] = (object) [ + 'term_id' => 5, + 'name' => 'Desserts', + 'slug' => 'desserts', + 'taxonomy' => 'cp_recipe_category', + ]; + + $html = $this->with_filter( + 'cooked_term_name', + function () { + return 'Filtered Desserts'; + }, + function () { + return $this->capture_output( + function () { + Cooked_Taxonomies::single_taxonomy_block( 5, 'list' ); + } + ); + } + ); + + $this->assertStringContainsString( 'Filtered Desserts', $html ); + $this->assertStringNotContainsString( '>Desserts<', $html ); + } +} + +class Envira_Gallery {} + +class Cooked_Filter_Custom_Gallery {} + +class Cooked_Filter_List_Renderer { + public static function list_style_sentinel( $atts ) { + echo '
      '; + } +} + diff --git a/tests/phpunit/RelatedRecipesFiltersTest.php b/tests/phpunit/RelatedRecipesFiltersTest.php new file mode 100644 index 0000000..616e227 --- /dev/null +++ b/tests/phpunit/RelatedRecipesFiltersTest.php @@ -0,0 +1,73 @@ + 1, + 'title' => 'Source Recipe', + 'nutrition' => [ 'servings' => 1 ], + ]; + } + + protected function related_atts() { + return Cooked_Related_Recipes::get_default_atts(); + } + + public function test_cooked_related_recipes_default_atts_can_add_a_key() { + $atts = $this->with_filter( + 'cooked_related_recipes_default_atts', + function ( $value ) { + $value['sentinel'] = 'yes'; + return $value; + }, + function () { + return Cooked_Related_Recipes::get_default_atts(); + } + ); + + $this->assertSame( 'yes', $atts['sentinel'] ); + } + + public function test_cooked_related_recipes_query_args_changes_wp_query() { + $GLOBALS['_cooked_test_object_terms']['1:cp_recipe_category'] = [ 3 ]; + + $this->with_filter( + 'cooked_related_recipes_query_args', + function ( $args ) { + $args['posts_per_page'] = 11; + $args['orderby'] = 'date'; + return $args; + }, + function () { + Cooked_Related_Recipes::find_related_recipes( + $this->source_recipe(), + $this->related_atts() + ); + } + ); + + $this->assertSame( 11, (int) $GLOBALS['_cooked_test_last_query']['posts_per_page'] ); + $this->assertSame( 'date', $GLOBALS['_cooked_test_last_query']['orderby'] ); + } + + public function test_cooked_related_recipes_result_can_replace_ids() { + $GLOBALS['_cooked_test_object_terms']['1:cp_recipe_category'] = [ 3 ]; + $GLOBALS['_cooked_test_query_posts'] = [ 8, 9 ]; + + $result = $this->with_filter( + 'cooked_related_recipes_result', + function () { + return [ [ 'id' => 42 ] ]; + }, + function () { + return Cooked_Related_Recipes::find_related_recipes( + $this->source_recipe(), + $this->related_atts() + ); + } + ); + + $this->assertSame( [ [ 'id' => 42 ] ], $result ); + } +} diff --git a/tests/phpunit/SettingsFiltersTest.php b/tests/phpunit/SettingsFiltersTest.php new file mode 100644 index 0000000..a49f3c9 --- /dev/null +++ b/tests/phpunit/SettingsFiltersTest.php @@ -0,0 +1,197 @@ +with_filter( + 'cooked_get_settings', + function ( $value ) { + $value['filter_sentinel'] = 'yes'; + return $value; + }, + function () { + return Cooked_Settings::get(); + } + ); + + $this->assertSame( 'yes', $settings['filter_sentinel'] ); + } + + /** + * @dataProvider settings_option_filter_provider + */ + public function test_settings_option_filters_change_tabs_fields( $hook, $path, $mode ) { + $result = $this->with_filter( + $hook, + function ( $value ) use ( $mode ) { + if ( $mode === 'string' ) { + return 'FILTERED DESC'; + } + if ( $mode === 'defaults' ) { + $value[] = 'sentinel_default'; + return $value; + } + $value['sentinel'] = 'Sentinel Option'; + return $value; + }, + function () { + return Cooked_Settings::tabs_fields(); + } + ); + + $current = $result; + foreach ( $path as $segment ) { + $this->assertArrayHasKey( $segment, $current ); + $current = $current[ $segment ]; + } + + if ( $mode === 'string' ) { + $this->assertSame( 'FILTERED DESC', $current ); + } elseif ( $mode === 'defaults' ) { + $this->assertContains( 'sentinel_default', $current ); + } else { + $this->assertArrayHasKey( 'sentinel', $current ); + $this->assertSame( 'Sentinel Option', $current['sentinel'] ); + } + } + + public function settings_option_filter_provider() { + return [ + 'cooked_taxonomy_options' => [ + 'cooked_taxonomy_options', + [ 'recipe_settings', 'fields', 'recipe_taxonomies', 'options' ], + 'options', + ], + 'cooked_recipe_info_display_options' => [ + 'cooked_recipe_info_display_options', + [ 'recipe_settings', 'fields', 'recipe_info_display_options', 'options' ], + 'options', + ], + 'cooked_recipe_info_display_options_defaults' => [ + 'cooked_recipe_info_display_options_defaults', + [ 'recipe_settings', 'fields', 'recipe_info_display_options', 'default' ], + 'defaults', + ], + 'cooked_print_view_display_options' => [ + 'cooked_print_view_display_options', + [ 'recipe_settings', 'fields', 'print_view_display_options', 'options' ], + 'options', + ], + 'cooked_settings_carb_formats' => [ + 'cooked_settings_carb_formats', + [ 'recipe_settings', 'fields', 'carb_format', 'options' ], + 'options', + ], + 'cooked_settings_author_formats' => [ + 'cooked_settings_author_formats', + [ 'recipe_settings', 'fields', 'author_name_format', 'options' ], + 'options', + ], + 'cooked_author_link_options' => [ + 'cooked_author_link_options', + [ 'recipe_settings', 'fields', 'disable_author_links', 'options' ], + 'options', + ], + 'cooked_settings_sort_options' => [ + 'cooked_settings_sort_options', + [ 'recipe_settings', 'fields', 'browse_default_sort', 'options' ], + 'options', + ], + 'cooked_settings_section_heading_default_html_tag_options' => [ + 'cooked_settings_section_heading_default_html_tag_options', + [ 'recipe_settings', 'fields', 'section_heading_default_html_tag', 'options' ], + 'options', + ], + 'cooked_recipe_wp_editor_roles_defaults' => [ + 'cooked_recipe_wp_editor_roles_defaults', + [ 'recipe_settings', 'fields', 'recipe_wp_editor_roles', 'default' ], + 'defaults', + ], + 'cooked_advanced_options' => [ + 'cooked_advanced_options', + [ 'recipe_settings', 'fields', 'advanced', 'options' ], + 'options', + ], + 'cooked_dark_mode_options' => [ + 'cooked_dark_mode_options', + [ 'design', 'fields', 'dark_mode', 'options' ], + 'options', + ], + 'cooked_dark_mode_field_desc' => [ + 'cooked_dark_mode_field_desc', + [ 'design', 'fields', 'dark_mode', 'desc' ], + 'string', + ], + 'cooked_author_image_options' => [ + 'cooked_author_image_options', + [ 'design', 'fields', 'hide_author_avatars', 'options' ], + 'options', + ], + ]; + } + + public function test_cooked_settings_tabs_fields_can_add_a_tab() { + $tabs = $this->with_filter( + 'cooked_settings_tabs_fields', + function ( $tabs ) { + $tabs['sentinel_tab'] = [ + 'name' => 'Sentinel', + 'icon' => 'star', + 'fields' => [], + ]; + return $tabs; + }, + function () { + return Cooked_Settings::tabs_fields(); + } + ); + + $this->assertArrayHasKey( 'sentinel_tab', $tabs ); + $this->assertSame( 'Sentinel', $tabs['sentinel_tab']['name'] ); + } + + public function test_cooked_per_page_options_can_add_an_option() { + $options = $this->with_filter( + 'cooked_per_page_options', + function ( $value ) { + $value['99'] = 'Ninety Nine'; + return $value; + }, + function () { + return Cooked_Settings::per_page_array(); + } + ); + + $this->assertSame( 'Ninety Nine', $options['99'] ); + } + + public function test_cooked_settings_pages_array_can_add_a_page() { + $pages = $this->with_filter( + 'cooked_settings_pages_array', + function ( $value ) { + $value[42] = 'Filter Page (ID:42)'; + return $value; + }, + function () { + return Cooked_Settings::pages_array( 'Choose a page...' ); + } + ); + + $this->assertSame( 'Filter Page (ID:42)', $pages[42] ); + } + + public function test_cooked_settings_term_array_can_add_a_term() { + $terms = $this->with_filter( + 'cooked_settings_cp_recipe_category_array', + function ( $value ) { + $value[7] = 'Filtered Category'; + return $value; + }, + function () { + return Cooked_Settings::terms_array( 'cp_recipe_category', 'Choose...' ); + } + ); + + $this->assertSame( 'Filtered Category', $terms[7] ); + } +} diff --git a/tests/phpunit/ShortcodesFiltersTest.php b/tests/phpunit/ShortcodesFiltersTest.php new file mode 100644 index 0000000..a0488a2 --- /dev/null +++ b/tests/phpunit/ShortcodesFiltersTest.php @@ -0,0 +1,358 @@ +SENTINEL'; + } +} + +class ShortcodesFiltersTest extends FilterTestCase { + + protected $shortcodes; + + protected function setUp(): void { + parent::setUp(); + $this->shortcodes = new Cooked_Shortcodes(); + $GLOBALS['wp_query'] = (object) [ 'query' => [] ]; + $GLOBALS['recipe_settings'] = [ + 'id' => 1, + 'title' => 'Test Recipe', + 'nutrition' => [ 'servings' => 4 ], + ]; + $GLOBALS['_cooked_settings']['recipe_info_display_options'] = [ + 'author', + 'difficulty_level', + 'servings', + ]; + $GLOBALS['_cooked_settings']['browse_page'] = 10; + $GLOBALS['_cooked_settings']['recipe_author_permalink'] = 'recipe-author'; + $GLOBALS['_cooked_test_options']['permalink_structure'] = '/%postname%/'; + $GLOBALS['_cooked_test_options']['page_on_front'] = 0; + } + + public function test_cooked_browse_shortcode_default_attributes_reach_query() { + $this->with_filter( + 'cooked_browse_shortcode_default_attributes', + function ( $atts ) { + $atts['show'] = 7; + return $atts; + }, + function () { + $this->shortcodes->cooked_browse_shortcode( [] ); + } + ); + + $this->assertSame( 7, (int) $GLOBALS['_cooked_test_last_query']['posts_per_page'] ); + } + + public function test_cooked_recipe_shortcode_output_can_replace_not_found_html() { + $output = $this->with_filter( + 'cooked_recipe_shortcode_output', + function () { + return '
      X
      '; + }, + function () { + return $this->shortcodes->cooked_recipe_shortcode( [ 'id' => 99 ] ); + } + ); + + $this->assertSame( '
      X
      ', $output ); + } + + public function test_cooked_recipe_embed_blocked_message_can_replace_text() { + $property = new ReflectionProperty( 'Cooked_Shortcodes', 'recipe_embed_stack' ); + if ( PHP_VERSION_ID < 80100 ) { + $property->setAccessible( true ); + } + $property->setValue( null, [ 5 ] ); + + $output = $this->with_filter( + 'cooked_recipe_embed_blocked_message', + function () { + return 'BLOCKED_BY_FILTER'; + }, + function () { + return $this->shortcodes->cooked_recipe_shortcode( [ 'id' => 5 ] ); + } + ); + + $property->setValue( null, [] ); + $this->assertStringContainsString( 'BLOCKED_BY_FILTER', $output ); + } + + public function test_cooked_recipe_gallery_options_appear_in_markup() { + $GLOBALS['recipe_settings']['gallery'] = [ + 'type' => 'cooked', + 'items' => [ 11 ], + 'video_url' => '', + ]; + + $html = $this->with_filter( + 'cooked_recipe_gallery_options', + function ( $options ) { + $options['data-fit'] = 'contain'; + return $options; + }, + function () { + return $this->shortcodes->cooked_gallery_shortcode( [] ); + } + ); + + $this->assertStringContainsString( 'data-fit="contain"', $html ); + } + + public function test_cooked_gallery_video_last_option_moves_video() { + $GLOBALS['recipe_settings']['gallery'] = [ + 'type' => 'cooked', + 'items' => [ 11 ], + 'video_url' => 'https://example.com/video.mp4', + ]; + + $html = $this->with_filter( + 'cooked_gallery_video_last_option', + function () { + return true; + }, + function () { + return $this->shortcodes->cooked_gallery_shortcode( [] ); + } + ); + + $video_pos = strpos( $html, 'https://example.com/video.mp4' ); + $image_pos = strpos( $html, 'img-11.jpg' ); + $this->assertNotFalse( $video_pos ); + $this->assertNotFalse( $image_pos ); + $this->assertGreaterThan( $image_pos, $video_pos ); + } + + public function test_cooked_gallery_items_output_can_replace_items() { + $GLOBALS['recipe_settings']['gallery'] = [ + 'type' => 'cooked', + 'items' => [ 11 ], + 'video_url' => '', + ]; + + $html = $this->with_filter( + 'cooked_gallery_items_output', + function () { + return [ 22 ]; + }, + function () { + return $this->shortcodes->cooked_gallery_shortcode( [] ); + } + ); + + $this->assertStringContainsString( 'img-22.jpg', $html ); + $this->assertStringNotContainsString( 'img-11.jpg', $html ); + } + + public function test_cooked_default_info_array_and_methods_render_custom_field() { + $html = $this->with_filter( + 'cooked_default_info_array', + function ( $value ) { + $value['sentinel'] = 'Sentinel'; + return $value; + }, + function () { + return $this->with_filter( + 'cooked_available_info_shortcode_methods', + function ( $methods ) { + $methods['cooked_info_sentinel'] = 'Cooked_Filter_Info_Helper'; + return $methods; + }, + function () { + return $this->shortcodes->cooked_info_shortcode( [ 'include' => 'sentinel' ] ); + } + ); + } + ); + + $this->assertStringContainsString( 'SENTINEL', $html ); + } + + public function test_cooked_info_shortcode_output_can_replace_html() { + $html = $this->with_filter( + 'cooked_info_shortcode_output', + function () { + return '
      INFO
      '; + }, + function () { + return $this->shortcodes->cooked_info_shortcode( [ 'include' => 'servings' ] ); + } + ); + + $this->assertSame( '
      INFO
      ', $html ); + } + + public function test_cooked_author_permalink_appears_in_author_info() { + $GLOBALS['recipe_settings']['author'] = [ + 'id' => 3, + 'user_nicename' => 'chef', + 'name' => 'Chef', + 'profile_photo' => '', + ]; + + $html = $this->with_filter( + 'cooked_author_permalink', + function () { + return 'http://example.com/filtered-author/'; + }, + function () { + return $this->capture_output( + function () { + Cooked_Shortcodes::cooked_info_author(); + } + ); + } + ); + + $this->assertStringContainsString( 'http://example.com/filtered-author/', $html ); + } + + public function test_cooked_show_difficulty_level_can_replace_html() { + $html = $this->with_filter( + 'cooked_show_difficulty_level', + function () { + return 'Hard'; + }, + function () { + return $this->capture_output( + function () { + Cooked_Shortcodes::cooked_info_difficulty( + [ 'difficulty_level' => 3 ] + ); + } + ); + } + ); + + $this->assertStringContainsString( 'filtered-difficulty', $html ); + $this->assertStringContainsString( 'Hard', $html ); + } + + public function test_cooked_directions_shortcode_atts_can_add_amp() { + $GLOBALS['recipe_settings']['directions'] = [ + [ 'content' => 'Mix well', 'image' => 15 ], + ]; + + $html = $this->with_filter( + 'cooked_directions_shortcode_atts', + function ( $atts ) { + $atts['amp'] = true; + return $atts; + }, + function () { + return $this->with_filter( + 'cooked_direction_image_html', + function ( $image, $atts ) { + return ! empty( $atts['amp'] ) ? '' : $image; + }, + function () { + return $this->shortcodes->cooked_directions_shortcode( [] ); + }, + 2 + ); + } + ); + + $this->assertStringContainsString( '', $html ); + } + + protected function seed_related_recipe( $post_id ) { + $post = (object) [ + 'ID' => $post_id, + 'post_title' => 'Recipe ' . $post_id, + 'post_excerpt' => 'Excerpt ' . $post_id, + 'post_author' => 1, + 'post_status' => 'publish', + 'post_type' => 'cp_recipe', + ]; + $GLOBALS['_cooked_test_posts'][ $post_id ] = $post; + $GLOBALS['_cooked_test_titles'][ $post_id ] = 'Recipe ' . $post_id; + $GLOBALS['_cooked_test_post_meta'][ $post_id ]['_recipe_settings'] = [ + 'cooked_version' => COOKED_VERSION, + 'title' => 'Recipe ' . $post_id, + 'excerpt' => 'Excerpt ' . $post_id, + 'nutrition' => [ 'servings' => 1 ], + ]; + } + + public function test_cooked_related_recipes_display_ids_and_output() { + $GLOBALS['_cooked_test_object_terms']['1:cp_recipe_category'] = [ 3 ]; + $this->seed_related_recipe( 1 ); + $this->seed_related_recipe( 8 ); + + $output = $this->with_filter( + 'cooked_related_recipes_result', + function () { + return [ [ 'id' => 8 ] ]; + }, + function () { + return $this->with_filter( + 'cooked_related_recipes_output', + function () { + return ''; + }, + function () { + return $this->shortcodes->cooked_related_recipes_shortcode( + [ + 'id' => 1, + 'hide_excerpt' => true, + 'hide_author' => true, + ] + ); + } + ); + } + ); + + $this->assertSame( '', $output ); + } + + public function test_cooked_related_recipes_display_ids_changes_ids_passed_to_output() { + $GLOBALS['_cooked_test_object_terms']['1:cp_recipe_category'] = [ 3 ]; + $this->seed_related_recipe( 1 ); + $this->seed_related_recipe( 99 ); + + $seen_ids = null; + $this->with_filter( + 'cooked_related_recipes_result', + function () { + return [ [ 'id' => 8 ] ]; + }, + function () use ( &$seen_ids ) { + return $this->with_filter( + 'cooked_related_recipes_display_ids', + function () { + return [ 99 ]; + }, + function () use ( &$seen_ids ) { + return $this->with_filter( + 'cooked_related_recipes_output', + function ( $output, $recipe_ids ) use ( &$seen_ids ) { + $seen_ids = $recipe_ids; + return '
      ok
      '; + }, + function () { + return $this->shortcodes->cooked_related_recipes_shortcode( + [ + 'id' => 1, + 'hide_excerpt' => true, + 'hide_author' => true, + ] + ); + }, + 2 + ); + } + ); + } + ); + + $this->assertSame( [ 99 ], $seen_ids ); + } +} diff --git a/tests/phpunit/TemplateFiltersTest.php b/tests/phpunit/TemplateFiltersTest.php new file mode 100644 index 0000000..09a78b8 --- /dev/null +++ b/tests/phpunit/TemplateFiltersTest.php @@ -0,0 +1,175 @@ +
      '; + } +} + +class TemplateFiltersTest extends FilterTestCase { + + protected function seed_recipe( $post_id = 1 ) { + $GLOBALS['post'] = (object) [ + 'ID' => $post_id, + 'post_title' => 'Test Recipe', + 'post_excerpt' => '', + 'post_author' => 1, + 'post_status' => 'publish', + 'post_type' => 'cp_recipe', + ]; + $GLOBALS['_cooked_test_posts'][ $post_id ] = $GLOBALS['post']; + $GLOBALS['_cooked_test_post_meta'][ $post_id ]['_recipe_settings'] = [ + 'cooked_version' => COOKED_VERSION, + 'id' => $post_id, + 'title' => 'Test Recipe', + 'content' => '

      [cooked-ingredients]

      Visible copy', + 'excerpt' => 'A short excerpt', + 'nutrition' => [ 'servings' => 4 ], + ]; + $GLOBALS['_cooked_test_query_posts'] = [ $GLOBALS['post'] ]; + $GLOBALS['recipe'] = [ + 'id' => $post_id, + 'title' => 'Test Recipe', + 'excerpt' => 'A short excerpt', + 'author' => [ 'name' => 'Test Author' ], + ]; + $GLOBALS['recipe_settings'] = $GLOBALS['_cooked_test_post_meta'][ $post_id ]['_recipe_settings']; + $GLOBALS['_cooked_settings']['recipe_info_display_options'] = [ 'author', 'excerpt' ]; + } + + public function test_cooked_pre_recipe_content_and_recipe_content_on_nonsingular_path() { + $this->seed_recipe(); + $GLOBALS['_cooked_test_is_singular'] = false; + $GLOBALS['_cooked_test_is_feed'] = false; + + $html = $this->with_filter( + 'cooked_pre_recipe_content', + function ( $content ) { + return $content . '

      PRE_FILTER

      '; + }, + function () { + return $this->with_filter( + 'cooked_recipe_content', + function ( $content ) { + return $content . '

      CONTENT_FILTER

      '; + }, + function () { + return $this->capture_output( + function () { + $recipe_seo_content = ''; + include COOKED_DIR . 'templates/front/recipe.php'; + } + ); + } + ); + } + ); + + $this->assertStringContainsString( 'PRE_FILTER', $html ); + $this->assertStringContainsString( 'CONTENT_FILTER', $html ); + $this->assertStringNotContainsString( '[cooked-ingredients]', $html ); + } + + public function test_cooked_author_template_override_replaces_author_heading() { + $this->seed_recipe(); + $GLOBALS['recipes'] = [ [ 'id' => 1, 'title' => 'Test Recipe' ] ]; + $GLOBALS['recipe_args'] = [ 'author' => 1 ]; + $GLOBALS['current_recipe_page'] = 1; + $GLOBALS['list_id_counter'] = 0; + $GLOBALS['atts'] = $this->list_view_atts( + [ + 'search' => false, + 'pagination' => false, + ] + ); + + $html = $this->with_filter( + 'cooked_author_template_override', + function () { + return '
      Filtered Author
      '; + }, + function () { + return $this->capture_output( + function () { + include COOKED_DIR . 'templates/front/recipe-list.php'; + } + ); + } + ); + + $this->assertStringContainsString( 'Filtered Author', $html ); + $this->assertStringNotContainsString( 'Recipes by', $html ); + } + + public function test_cooked_recipe_list_style_changes_list_markup() { + $this->seed_recipe(); + $GLOBALS['recipes'] = [ [ 'id' => 1, 'title' => 'Test Recipe' ] ]; + $GLOBALS['recipe_args'] = []; + $GLOBALS['current_recipe_page'] = 1; + $GLOBALS['list_id_counter'] = 0; + $GLOBALS['atts'] = $this->list_view_atts( + [ + 'search' => false, + 'pagination' => false, + 'layout' => 'grid', + ] + ); + + $html = $this->with_filter( + 'cooked_recipe_list_style', + function () { + return [ 'sentinel' => 'Cooked_Filter_Template_List_Renderer' ]; + }, + function () { + return $this->capture_output( + function () { + include COOKED_DIR . 'templates/front/recipe-list.php'; + } + ); + } + ); + + $this->assertStringContainsString( 'sentinel-list-style', $html ); + $this->assertStringContainsString( 'cooked-recipe-sentinel', $html ); + } + + public function test_cooked_single_recipe_classes_appear_on_card() { + $this->seed_recipe(); + $GLOBALS['recipe_classes'] = false; + + $html = $this->with_filter( + 'cooked_single_recipe_classes', + function ( $classes ) { + $classes[] = 'filtered-recipe-class'; + return $classes; + }, + function () { + return $this->capture_output( + function () { + include COOKED_DIR . 'templates/front/recipe-single.php'; + } + ); + } + ); + + $this->assertStringContainsString( 'filtered-recipe-class', $html ); + } + + public function test_cooked_welcome_banner_img_changes_src() { + $html = $this->with_filter( + 'cooked_welcome_banner_img', + function () { + return 'http://example.com/filtered-banner.png'; + }, + function () { + return $this->capture_output( + function () { + include COOKED_DIR . 'templates/admin/welcome.php'; + } + ); + } + ); + + $this->assertStringContainsString( 'http://example.com/filtered-banner.png', $html ); + } +} diff --git a/tests/phpunit/bootstrap.php b/tests/phpunit/bootstrap.php index 61cbd16..4b2cf1e 100644 --- a/tests/phpunit/bootstrap.php +++ b/tests/phpunit/bootstrap.php @@ -14,6 +14,8 @@ define( 'COOKED_VERSION', '1.16.0' ); define( 'COOKED_DEV', false ); define( 'COOKED_URL', 'http://example.com/wp-content/plugins/cooked/' ); +define( 'COOKED_FOLDER', 'cooked' ); +define( 'COOKED_PLUGIN_FILE', COOKED_DIR . 'cooked.php' ); define( 'OBJECT', 'OBJECT' ); /** @@ -24,7 +26,10 @@ function _x( $text, $context, $domain = 'default' ) { return $text; } function _n( $single, $plural, $number, $domain = 'default' ) { return $number <= 1 ? $single : $plural; } function _e( $text, $domain = 'default' ) { echo $text; } function esc_html__( $text, $domain = 'default' ) { return $text; } +function esc_html_e( $text, $domain = 'default' ) { echo $text; } function esc_html_x( $text, $context, $domain = 'default' ) { return $text; } +function esc_attr__( $text, $domain = 'default' ) { return $text; } +function esc_attr_e( $text, $domain = 'default' ) { echo $text; } /** * Filter and action stubs @@ -67,9 +72,32 @@ function remove_filter( $tag, $callback, $priority = 10 ) { return false; } -function add_action( $tag, $callback, $priority = 10, $accepted_args = 1 ) { return true; } -function do_action( $tag, ...$args ) { return; } -function remove_action( $tag, $callback, $priority = 10 ) { return true; } +function add_action( $tag, $callback, $priority = 10, $accepted_args = 1 ) { + $GLOBALS['_cooked_test_actions'][ $tag ][ $priority ][] = $callback; + return true; +} +function do_action( $tag, ...$args ) { + if ( empty( $GLOBALS['_cooked_test_actions'][ $tag ] ) ) { + return; + } + ksort( $GLOBALS['_cooked_test_actions'][ $tag ] ); + foreach ( $GLOBALS['_cooked_test_actions'][ $tag ] as $callbacks ) { + foreach ( $callbacks as $callback ) { + call_user_func_array( $callback, $args ); + } + } +} +function remove_action( $tag, $callback, $priority = 10 ) { + if ( empty( $GLOBALS['_cooked_test_actions'][ $tag ][ $priority ] ) ) { + return true; + } + foreach ( $GLOBALS['_cooked_test_actions'][ $tag ][ $priority ] as $index => $registered_callback ) { + if ( $registered_callback === $callback ) { + unset( $GLOBALS['_cooked_test_actions'][ $tag ][ $priority ][ $index ] ); + } + } + return true; +} /** * Option stubs @@ -101,6 +129,10 @@ function esc_attr( $text ) { return htmlspecialchars( $text ?? '', ENT_QUOTES, ' function esc_url( $url ) { return filter_var( $url, FILTER_SANITIZE_URL ); } function esc_url_raw( $url ) { return filter_var( $url, FILTER_SANITIZE_URL ); } function wp_kses_post( $data ) { return $data; } +function esc_textarea( $text ) { return htmlspecialchars( $text ?? '', ENT_QUOTES, 'UTF-8' ); } +function wp_editor( $content, $editor_id, $settings = [] ) { + echo ''; +} function sanitize_key( $key ) { return strtolower( preg_replace( '/[^a-zA-Z0-9_\-]/', '', $key ) ); } function sanitize_title( $title ) { return strtolower( preg_replace( '/[^a-zA-Z0-9_\-]/', '-', $title ) ); } function sanitize_title_with_dashes( $title, $raw_title = '', $context = 'display' ) { return strtolower( preg_replace( '/[^a-zA-Z0-9_\-]/', '-', $title ) ); } @@ -148,13 +180,62 @@ function shortcode_parse_atts( $text ) { return $atts; } function do_shortcode( $content ) { return $content; } +function shortcode_atts( $pairs, $atts, $shortcode = '' ) { + $atts = (array) $atts; + $out = []; + foreach ( $pairs as $name => $default ) { + $out[ $name ] = array_key_exists( $name, $atts ) ? $atts[ $name ] : $default; + } + return $out; +} +function strip_shortcodes( $content ) { + return preg_replace( '/\[[^\]]+\]/', '', $content ); +} +function shortcode_unautop( $content ) { + return $content; +} +function shortcode_exists( $tag ) { + return false; +} +function add_shortcode( $tag, $callback ) { + return true; +} /** * Post and attachment stubs */ -function wp_get_attachment_image( $attachment_id, $size = 'thumbnail', $icon = false, $attr = [] ) { return ''; } -function get_the_title( $post_id = 0 ) { return 'Test Recipe'; } -function get_permalink( $post_id = 0 ) { return 'http://example.com/recipe/'; } +function wp_get_attachment_image( $attachment_id, $size = 'thumbnail', $icon = false, $attr = [] ) { + $GLOBALS['_cooked_test_attachment_image_calls'][] = [ + 'attachment_id' => $attachment_id, + 'size' => $size, + 'icon' => $icon, + 'attr' => $attr, + ]; + if ( isset( $GLOBALS['_cooked_test_attachment_image_html'] ) ) { + return $GLOBALS['_cooked_test_attachment_image_html']; + } + return $attachment_id ? '' : ''; +} +function get_the_title( $post_id = 0 ) { + if ( is_object( $post_id ) && isset( $post_id->post_title ) ) { + return $post_id->post_title; + } + if ( ! $post_id && isset( $GLOBALS['post'] ) ) { + $post_id = $GLOBALS['post']; + if ( is_object( $post_id ) && isset( $post_id->post_title ) ) { + return $post_id->post_title; + } + $post_id = is_object( $post_id ) ? $post_id->ID : $post_id; + } + if ( isset( $GLOBALS['_cooked_test_titles'][ $post_id ] ) ) { + return $GLOBALS['_cooked_test_titles'][ $post_id ]; + } + return 'Test Recipe'; +} +function get_permalink( $post_id = 0 ) { + $id = is_object( $post_id ) ? $post_id->ID : $post_id; + return 'http://example.com/recipe/' . (int) $id . '/'; +} function has_post_thumbnail( $post_id = 0 ) { return false; } function get_the_post_thumbnail( $post_id = 0, $size = 'post-thumbnail', $attr = [] ) { return ''; } function get_the_post_thumbnail_url( $post_id = 0, $size = 'post-thumbnail' ) { return ''; } @@ -162,6 +243,9 @@ function get_post( $post_id = null, $output = OBJECT, $filter = 'raw' ) { if ( is_object( $post_id ) ) { return $post_id; } + if ( isset( $GLOBALS['_cooked_test_posts'][ $post_id ] ) ) { + return $GLOBALS['_cooked_test_posts'][ $post_id ]; + } return (object) [ 'ID' => $post_id, @@ -170,25 +254,47 @@ function get_post( $post_id = null, $output = OBJECT, $filter = 'raw' ) { 'post_author' => 1, 'post_status' => 'publish', 'post_type' => 'cp_recipe', + 'post_name' => 'test-recipe', + 'post_content' => '', ]; } -function get_post_meta( $post_id, $key = '', $single = false ) { return []; } -function wp_update_post( $postarr = [], $wp_error = false, $fire_after_hooks = true ) { return 0; } +function get_post_meta( $post_id, $key = '', $single = false ) { + if ( $key !== '' && isset( $GLOBALS['_cooked_test_post_meta'][ $post_id ][ $key ] ) ) { + $value = $GLOBALS['_cooked_test_post_meta'][ $post_id ][ $key ]; + return $single ? $value : (array) $value; + } + if ( $key === '' && isset( $GLOBALS['_cooked_test_post_meta'][ $post_id ] ) ) { + return $GLOBALS['_cooked_test_post_meta'][ $post_id ]; + } + return []; +} +function wp_update_post( $postarr = [], $wp_error = false, $fire_after_hooks = true ) { + $GLOBALS['_cooked_test_updated_posts'][] = $postarr; + return isset( $postarr['ID'] ) ? $postarr['ID'] : 0; +} /** * Query stubs */ -function get_query_var( $var, $default = '' ) { return $default; } -function add_query_arg( $args, $url = '' ) { return $url; } +function get_query_var( $var, $default = '' ) { + return isset( $GLOBALS['_cooked_test_query_vars'][ $var ] ) ? $GLOBALS['_cooked_test_query_vars'][ $var ] : $default; +} +function add_query_arg( $args, $url = '' ) { + if ( is_array( $args ) ) { + $query = http_build_query( $args ); + return $url . ( strpos( $url, '?' ) === false ? '?' : '&' ) . $query; + } + return $url; +} function get_pagenum_link( $page ) { return 'http://example.com/page/' . $page; } -function paginate_links( $args = '' ) { return ''; } +function paginate_links( $args = '' ) { + $GLOBALS['_cooked_test_paginate_args'] = $args; + return '
      1 2 3
      '; +} /** * User stubs */ -function get_current_user_id() { return 0; } -function is_user_logged_in() { return false; } -function wp_get_current_user() { return (object) [ 'ID' => 0, 'user_login' => '' ]; } function set_transient( $transient, $value, $expiration = 0 ) { return true; } function get_transient( $transient ) { return false; } function delete_transient( $transient ) { return true; } @@ -196,11 +302,47 @@ function delete_transient( $transient ) { return true; } /** * Taxonomy stubs */ -function get_term_by( $field, $value, $taxonomy = '', $output = OBJECT, $filter = 'raw' ) { return false; } -function get_terms( $args = [], $deprecated = '' ) { return []; } +function get_term_by( $field, $value, $taxonomy = '', $output = OBJECT, $filter = 'raw' ) { + foreach ( isset( $GLOBALS['_cooked_test_terms'] ) ? $GLOBALS['_cooked_test_terms'] : [] as $term ) { + if ( $taxonomy && $term->taxonomy !== $taxonomy ) { + continue; + } + if ( ( $field === 'id' || $field === 'term_id' ) && (int) $term->term_id === (int) $value ) { + return $term; + } + if ( $field === 'slug' && $term->slug === $value ) { + return $term; + } + if ( $field === 'name' && $term->name === $value ) { + return $term; + } + } + return false; +} +function get_term( $term, $taxonomy = '' ) { + if ( is_object( $term ) ) { + return $term; + } + return get_term_by( 'id', $term, $taxonomy ); +} +function get_term_link( $term, $taxonomy = '' ) { + $term_obj = is_object( $term ) ? $term : get_term( $term, $taxonomy ); + $slug = $term_obj && isset( $term_obj->slug ) ? $term_obj->slug : $term; + return 'http://example.com/term/' . $slug . '/'; +} +function get_terms( $args = [], $deprecated = '' ) { + $taxonomy = is_array( $args ) && isset( $args['taxonomy'] ) ? $args['taxonomy'] : $deprecated; + $terms = []; + foreach ( isset( $GLOBALS['_cooked_test_terms'] ) ? $GLOBALS['_cooked_test_terms'] : [] as $term ) { + if ( ! $taxonomy || $term->taxonomy === $taxonomy ) { + $terms[] = $term; + } + } + return $terms; +} function get_taxonomy( $taxonomy ) { return (object) [ 'label' => $taxonomy, 'name' => $taxonomy ]; } function wp_dropdown_categories( $args = '' ) { return ''; } -function is_wp_error( $thing ) { return false; } +function is_wp_error( $thing ) { return $thing instanceof WP_Error; } /** * WP_Error stub class @@ -231,17 +373,83 @@ class WP_Query { public $in_the_loop = false; public $post = null; public $query_vars = []; + public $query = []; public function __construct( $query = '' ) { $this->query_vars = is_array( $query ) ? $query : []; - $this->posts = []; - $this->post_count = 0; - $this->found_posts = 0; - $this->max_num_pages = 0; + $this->query = $this->query_vars; + $GLOBALS['_cooked_test_last_query'] = $this->query_vars; + + $posts = isset( $GLOBALS['_cooked_test_query_posts'] ) ? $GLOBALS['_cooked_test_query_posts'] : []; + + if ( ! empty( $this->query_vars['post__in'] ) && is_array( $this->query_vars['post__in'] ) ) { + $want = array_map( 'intval', $this->query_vars['post__in'] ); + $filtered = []; + foreach ( $posts as $post ) { + $id = is_object( $post ) ? (int) $post->ID : (int) $post; + if ( in_array( $id, $want, true ) ) { + $filtered[] = $post; + } + } + if ( empty( $filtered ) ) { + foreach ( $want as $id ) { + if ( isset( $GLOBALS['_cooked_test_posts'][ $id ] ) ) { + $filtered[] = $GLOBALS['_cooked_test_posts'][ $id ]; + } + } + } + $posts = $filtered; + } + + if ( ! empty( $this->query_vars['post__not_in'] ) && is_array( $this->query_vars['post__not_in'] ) ) { + $not = array_map( 'intval', $this->query_vars['post__not_in'] ); + $posts = array_values( + array_filter( + $posts, + function ( $post ) use ( $not ) { + $id = is_object( $post ) ? (int) $post->ID : (int) $post; + return ! in_array( $id, $not, true ); + } + ) + ); + } + + if ( isset( $this->query_vars['fields'] ) && $this->query_vars['fields'] === 'ids' ) { + $posts = array_map( + function ( $post ) { + return is_object( $post ) ? (int) $post->ID : (int) $post; + }, + $posts + ); + } + $this->posts = $posts; + $this->post_count = count( $posts ); + $this->found_posts = count( $posts ); + $this->max_num_pages = isset( $GLOBALS['_cooked_test_max_num_pages'] ) + ? (int) $GLOBALS['_cooked_test_max_num_pages'] + : ( $this->post_count ? 1 : 0 ); + $this->current_post = -1; + $this->post = $this->post_count ? $this->posts[0] : null; } - public function have_posts() { return false; } - public function the_post() {} + public function get( $var, $default = '' ) { + return isset( $this->query_vars[ $var ] ) ? $this->query_vars[ $var ] : $default; + } + + public function set( $var, $value ) { + $this->query_vars[ $var ] = $value; + } + + public function have_posts() { + return ( $this->current_post + 1 ) < $this->post_count; + } + + public function the_post() { + $this->current_post++; + $this->in_the_loop = true; + $this->post = $this->posts[ $this->current_post ]; + $GLOBALS['post'] = is_object( $this->post ) ? $this->post : (object) [ 'ID' => $this->post ]; + } } /** @@ -266,10 +474,19 @@ function get_user_by( $field, $value ) { return (object) [ 'ID' => 1, 'user_logi function get_userdata( $user_id ) { return (object) [ 'ID' => $user_id, 'user_login' => 'admin', 'user_nicename' => 'admin', 'display_name' => 'Admin User', 'user_email' => 'admin@example.com', 'roles' => [ 'administrator' ] ]; } function get_user_meta( $user_id, $key = '', $single = false ) { return $key === 'cooked_user_meta' ? [] : ''; } function update_user_meta( $user_id, $meta_key, $meta_value, $prev_value = '' ) { return true; } -function get_posts( $args = [] ) { return []; } -function update_post_meta( $post_id, $meta_key, $meta_value, $prev_value = '' ) { return true; } -function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) { return true; } -function wp_insert_post( $postarr = [], $wp_error = false, $fire_after_hooks = true ) { return 1; } +function get_posts( $args = [] ) { return isset( $GLOBALS['_cooked_test_get_posts'] ) ? $GLOBALS['_cooked_test_get_posts'] : []; } +function update_post_meta( $post_id, $meta_key, $meta_value, $prev_value = '' ) { + $GLOBALS['_cooked_test_post_meta'][ $post_id ][ $meta_key ] = $meta_value; + return true; +} +function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) { + $GLOBALS['_cooked_test_post_meta'][ $post_id ][ $meta_key ] = $meta_value; + return true; +} +function wp_insert_post( $postarr = [], $wp_error = false, $fire_after_hooks = true ) { + $GLOBALS['_cooked_test_inserted_posts'][] = $postarr; + return 1; +} function register_setting( $option_group, $option_name, $args = [] ) { return true; } function wp_create_nonce( $action = -1 ) { return 'test_nonce'; } function wp_verify_nonce( $nonce, $action = -1 ) { return true; } @@ -277,9 +494,12 @@ function current_user_can( $capability, ...$args ) { return true; } function wp_nonce_field( $action = -1, $name = '_wpnonce', $referer = true, $echo = true ) { return ''; } function wp_doing_ajax() { return false; } function wp_doing_cron() { return false; } -function is_admin() { return false; } -function is_page( $page = '' ) { return false; } -function is_singular( $post_types = '' ) { return false; } +function is_admin() { return ! empty( $GLOBALS['_cooked_test_is_admin'] ); } +function is_page( $page = '' ) { return ! empty( $GLOBALS['_cooked_test_is_page'] ); } +function is_singular( $post_types = '' ) { return ! empty( $GLOBALS['_cooked_test_is_singular'] ); } +function is_feed() { return ! empty( $GLOBALS['_cooked_test_is_feed'] ); } +function is_main_query() { return ! isset( $GLOBALS['_cooked_test_is_main_query'] ) || $GLOBALS['_cooked_test_is_main_query']; } +function post_password_required( $post = null ) { return false; } function wp_reset_postdata() {} function wp_set_object_terms( $object_id, $terms, $taxonomy, $append = false ) { return []; } function wp_kses( $string, $allowed_html, $allowed_protocols = [] ) { return $string; } @@ -290,16 +510,31 @@ function get_the_date( $format = 'Y-m-d', $post = null ) { return '2024-01-15'; function get_the_terms( $post_id, $taxonomy ) { return [ (object) [ 'term_id' => 1, 'name' => 'Test Category', 'slug' => 'test-category' ] ]; } function get_avatar( $id_or_email, $size = 96, $default = '', $alt = '' ) { return ''; } function get_avatar_url( $id_or_email, $args = [] ) { return 'http://example.com/avatar.jpg'; } -function wp_get_attachment_image_src( $attachment_id, $size = 'thumbnail', $icon = false ) { return false; } +function wp_get_attachment_image_src( $attachment_id, $size = 'thumbnail', $icon = false ) { + if ( empty( $attachment_id ) ) { + return false; + } + return [ 'http://example.com/img-' . (int) $attachment_id . '.jpg', 900, 900, false ]; +} function wp_attachment_is_image( $attachment_id ) { return $attachment_id > 0; } function taxonomy_exists( $taxonomy ) { return true; } function wp_enqueue_style( $handle, $src = '', $deps = [], $ver = false, $media = 'all' ) { return true; } function wp_enqueue_script( $handle, $src = '', $deps = [], $ver = false, $in_footer = false ) { return true; } function wp_register_style( $handle, $src, $deps = [], $ver = false, $media = 'all' ) { return true; } function wp_register_script( $handle, $src, $deps = [], $ver = false, $in_footer = false ) { return true; } -function wp_localize_script( $handle, $object_name, $l10n ) { return true; } +function wp_localize_script( $handle, $object_name, $l10n ) { + $GLOBALS['_cooked_test_localized'][ $handle ][ $object_name ] = $l10n; + return true; +} function wp_add_inline_style( $handle, $data ) { return true; } -function wp_add_inline_script( $handle, $data, $position = 'after' ) { return true; } +function wp_add_inline_script( $handle, $data, $position = 'after' ) { + $GLOBALS['_cooked_test_inline_scripts'][] = [ + 'handle' => $handle, + 'data' => $data, + 'position' => $position, + ]; + return true; +} function wp_style_is( $handle, $list = 'enqueued' ) { return false; } function wp_script_is( $handle, $list = 'enqueued' ) { return false; } function add_rewrite_tag( $tag, $regex, $query = '' ) { return true; } @@ -314,6 +549,221 @@ function add_rewrite_rule( $regex, $query, $after = 'bottom' ) { return true; } ]; $GLOBALS['_cooked_settings'] = []; +$GLOBALS['_cooked_test_filters'] = []; +$GLOBALS['_cooked_test_actions'] = []; +$GLOBALS['_cooked_test_options'] = []; +$GLOBALS['_cooked_test_post_meta'] = []; +$GLOBALS['_cooked_test_posts'] = []; +$GLOBALS['_cooked_test_titles'] = []; +$GLOBALS['_cooked_test_query_vars'] = []; +$GLOBALS['_cooked_test_query_posts'] = []; +$GLOBALS['_cooked_test_terms'] = []; +$GLOBALS['_cooked_test_updated_posts'] = []; +$GLOBALS['_cooked_test_inserted_posts'] = []; +$GLOBALS['_cooked_test_attachment_image_calls'] = []; +$GLOBALS['_cooked_test_localized'] = []; +$GLOBALS['_cooked_test_inline_scripts'] = []; +$GLOBALS['_cooked_test_registered_post_types'] = []; +$GLOBALS['_cooked_test_registered_widgets'] = []; +$GLOBALS['_cooked_test_registered_roles'] = []; +$GLOBALS['_cooked_test_meta_boxes'] = []; +$GLOBALS['_cooked_test_paginate_args'] = null; +$GLOBALS['_cooked_test_last_query'] = []; + +class Cooked_Test_wpdb { + public $posts = 'wp_posts'; + public $prefix = 'wp_'; + + public function prepare( $query, ...$args ) { + foreach ( $args as $arg ) { + $query = preg_replace( '/%s/', "'" . $arg . "'", $query, 1 ); + } + return $query; + } + + public function esc_like( $text ) { + return addcslashes( $text, '_%\\' ); + } +} +$GLOBALS['wpdb'] = new Cooked_Test_wpdb(); + +class WP_Widget { + public $id_base; + public $name; + public $widget_options; + + public function __construct( $id_base, $name, $widget_options = [] ) { + $this->id_base = $id_base; + $this->name = $name; + $this->widget_options = $widget_options; + } + + public function get_field_id( $field ) { + return $this->id_base . '-' . $field; + } + + public function get_field_name( $field ) { + return $this->id_base . '[' . $field . ']'; + } +} + +class WP_Post { + public $ID; + public $post_title; + public $post_type = 'cp_recipe'; + public $post_status = 'publish'; + public $post_author = 1; +} + +function load_template( $template, $load_once = true, $args = [] ) { + if ( is_array( $args ) ) { + extract( $args, EXTR_SKIP ); + } + if ( $load_once ) { + include_once $template; + } else { + include $template; + } +} + +function locate_template( $template_names, $load = false, $require_once = true, $args = [] ) { + return ''; +} + +function wp_suspend_cache_addition( $suspend = true ) { + return true; +} + +function wp_reset_query() {} + +function tag_escape( $tag ) { + return preg_replace( '/[^a-zA-Z0-9_:]/', '', $tag ); +} + +function selected( $selected, $current = true, $echo = true ) { + $result = ( (string) $selected === (string) $current ) ? ' selected="selected"' : ''; + if ( $echo ) { + echo $result; + } + return $result; +} + +function checked( $checked, $current = true, $echo = true ) { + $result = ( (string) $checked === (string) $current ) ? ' checked="checked"' : ''; + if ( $echo ) { + echo $result; + } + return $result; +} + +function get_the_ID() { + return isset( $GLOBALS['post']->ID ) ? $GLOBALS['post']->ID : 0; +} + +function get_post_type( $post = null ) { + if ( is_object( $post ) && isset( $post->post_type ) ) { + return $post->post_type; + } + $p = get_post( $post ); + return $p ? $p->post_type : false; +} + +function get_post_type_object( $post_type ) { + return (object) [ + 'name' => $post_type, + 'labels' => (object) [ 'not_found' => 'No recipes found.' ], + ]; +} + +function get_post_status( $post = null ) { + $p = get_post( $post ); + return $p ? $p->post_status : false; +} + +function wp_get_object_terms( $object_id, $taxonomy, $args = [] ) { + $key = $object_id . ':' . $taxonomy; + if ( isset( $GLOBALS['_cooked_test_object_terms'][ $key ] ) ) { + return $GLOBALS['_cooked_test_object_terms'][ $key ]; + } + return []; +} + +function wp_get_attachment_image_url( $attachment_id, $size = 'thumbnail', $icon = false ) { + if ( empty( $attachment_id ) ) { + return false; + } + return 'http://example.com/wp-content/uploads/image-' . (int) $attachment_id . '.jpg'; +} + +function wp_get_attachment_url( $attachment_id ) { + return $attachment_id ? 'http://example.com/wp-content/uploads/file-' . (int) $attachment_id . '.mp4' : false; +} + +function add_meta_box( $id, $title, $callback, $screen = null, $context = 'advanced', $priority = 'default', $callback_args = null ) { + $GLOBALS['_cooked_test_meta_boxes'][] = [ + 'id' => $id, + 'title' => $title, + 'screen' => $screen, + ]; +} + +function register_post_type( $post_type, $args = [] ) { + $GLOBALS['_cooked_test_registered_post_types'][ $post_type ] = $args; + return true; +} + +function register_taxonomy( $taxonomy, $object_type, $args = [] ) { + $GLOBALS['_cooked_test_registered_taxonomies'][ $taxonomy ] = $args; + return true; +} + +function register_widget( $widget ) { + $GLOBALS['_cooked_test_registered_widgets'][] = $widget; + return true; +} + +function add_role( $role, $display_name, $capabilities = [] ) { + $GLOBALS['_cooked_test_registered_roles'][ $role ] = [ + 'name' => $display_name, + 'caps' => $capabilities, + ]; + return true; +} + +function get_role( $role ) { + return false; +} + +function remove_role( $role ) { + return true; +} + +function register_activation_hook( $file, $callback ) {} + +function add_image_size( $name, $width = 0, $height = 0, $crop = false ) { + return true; +} + +function get_adjacent_post( $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) { + $key = $previous ? 'prev' : 'next'; + return isset( $GLOBALS['_cooked_test_adjacent'][ $key ] ) ? $GLOBALS['_cooked_test_adjacent'][ $key ] : null; +} + +function is_user_logged_in() { + return ! empty( $GLOBALS['_cooked_test_logged_in'] ); +} + +function get_current_user_id() { + return ! empty( $GLOBALS['_cooked_test_logged_in'] ) ? 1 : 0; +} + +function wp_get_current_user() { + return (object) [ + 'ID' => get_current_user_id(), + 'user_login' => 'admin', + 'roles' => [ 'administrator' ], + ]; +} /** * Load Composer autoloader @@ -336,3 +786,4 @@ function add_rewrite_rule( $regex, $query, $after = 'bottom' ) { return true; } require_once COOKED_DIR . 'includes/class.cooked-related-recipes.php'; require_once COOKED_DIR . 'includes/class.cooked-updates.php'; require_once COOKED_DIR . 'includes/class.cooked-seo.php'; +require_once __DIR__ . '/FilterTestCase.php'; From d623267e6666206de017b9a960d2d27abf8c1d4a Mon Sep 17 00:00:00 2001 From: Armand Tresova Date: Sat, 15 Aug 2026 20:42:32 -0400 Subject: [PATCH 11/36] Plugin Check (PCP) - Fixes PCP Wave 4: output escaping --- assets/css/colors.php | 84 ++++++++++++------------ assets/css/responsive.php | 2 +- includes/class.cooked-admin-menus.php | 8 +-- includes/class.cooked-ajax.php | 2 +- includes/class.cooked-allergens.php | 6 +- includes/class.cooked-functions.php | 22 +++---- includes/class.cooked-import.php | 18 +++--- includes/class.cooked-measurements.php | 12 ++-- includes/class.cooked-migration.php | 2 +- includes/class.cooked-multilingual.php | 2 +- includes/class.cooked-post-types.php | 14 ++-- includes/class.cooked-recipe-meta.php | 90 +++++++++++++------------- includes/class.cooked-recipes.php | 52 +++++++-------- includes/class.cooked-settings.php | 34 +++++----- includes/class.cooked-shortcodes.php | 56 ++++++++-------- includes/class.cooked-updates.php | 27 ++++++-- includes/class.cooked-widgets.php | 2 +- includes/widgets/nutrition.php | 2 +- includes/widgets/recipe-card.php | 2 +- includes/widgets/recipe-categories.php | 2 +- includes/widgets/recipe-list.php | 2 +- includes/widgets/search.php | 2 +- templates/admin/import.php | 4 +- templates/admin/pro.php | 10 +-- templates/admin/settings.php | 4 +- templates/admin/welcome.php | 18 +++--- templates/front/recipe-list.php | 8 +-- templates/front/recipe-print.php | 2 +- templates/front/recipe-single.php | 6 +- templates/front/recipe.php | 8 ++- 30 files changed, 263 insertions(+), 240 deletions(-) diff --git a/assets/css/colors.php b/assets/css/colors.php index 3ca30bb..34ba2a9 100644 --- a/assets/css/colors.php +++ b/assets/css/colors.php @@ -29,50 +29,50 @@ }; ?>/* Dark Mode */ - { background:rgba(255,255,255,0.075); box-shadow:inset 0 0 0 1px rgba(255,255,255,0.05); } - { background:rgba(255,255,255,0.10); box-shadow:inset 0 0 0 1px rgba(255,255,255,0.05); } - { background:rgba(0,0,0,0.95); box-shadow:none; border-radius:5px; } - { background:rgba(0,0,0,0.25); border-color:rgba(255,255,255,0.15); } - { background:rgba(255,255,255,0.10); } - { background:rgba(255,255,255,0.15); } - select' ); ?> { border-color:rgba(0,0,0,0.15); } - select > option' ); ?> { color:#333; } - span.cooked-tax-column-title' ); ?> { border-bottom-color:rgba(255,255,255,0.15); } - { color:rgba(255,255,255,0.5); } - { color:#fff; } - { background:rgba(0,0,0,0.25); box-shadow:none !important } - span' ); ?> { color:rgba(255,255,255,0.5); } - a, .cooked-recipe-info span.cooked-fsm-button' ); ?> { color:rgba(255,255,255,0.5); } - a:hover, .cooked-recipe-info span.cooked-fsm-button:hover' ); ?> { color:#fff; } - { color:#fff; opacity:0.5; } - { border-color:rgba(255,255,255,0.25); } - { border-color:rgba(255,255,255,0.25); } - { border-color:rgba(255,255,255,0.25); color:#fff; } - { border-color:rgba(255,255,255,0.5); } - { background:rgba(0,0,0,0.25); border:none; border-radius:5px; } - { color:#fff; } - { border-color:rgba(255,255,255,0.15); } - { background:rgba(255,255,255,0.15); } - { border-color:rgba(255,255,255,0.15); } - { background:rgba(0,0,0,0.25); box-shadow:none !important; } - { box-shadow:none !important; } - { background:rgba(255,255,255,0.25); } - { background:rgba(255,255,255,0.15); } - { background:#000; color:#fff; } - { background:rgba(255,255,255,0.1); } - .cooked-rating-star.cooked-rating-star-empty, .cooked-recipe .cooked-ratable .cooked-rating-stars.cooked-user-rated > .cooked-rating-star.cooked-rating-star-empty' ); ?> { color:rgba(255,255,255,0.25); } - { color:rgba(255,255,255,0.5); } - { color:#fff; } - { background:#191919; color:#fff; box-shadow:0 -5px 30px rgba(0,0,0,0.4); } - { color:rgba(255,255,255,0.35); } - { color:rgba(255,255,255,0.65); } - { background:rgba(255,255,255,0.05); } - { color:rgba(255,255,255,0.5); } - { background:rgba(255,255,255,0.15); } + { background:rgba(255,255,255,0.075); box-shadow:inset 0 0 0 1px rgba(255,255,255,0.05); } + { background:rgba(255,255,255,0.10); box-shadow:inset 0 0 0 1px rgba(255,255,255,0.05); } + { background:rgba(0,0,0,0.95); box-shadow:none; border-radius:5px; } + { background:rgba(0,0,0,0.25); border-color:rgba(255,255,255,0.15); } + { background:rgba(255,255,255,0.10); } + { background:rgba(255,255,255,0.15); } + select' ), [] ); ?> { border-color:rgba(0,0,0,0.15); } + select > option' ), [] ); ?> { color:#333; } + span.cooked-tax-column-title' ), [] ); ?> { border-bottom-color:rgba(255,255,255,0.15); } + { color:rgba(255,255,255,0.5); } + { color:#fff; } + { background:rgba(0,0,0,0.25); box-shadow:none !important } + span' ), [] ); ?> { color:rgba(255,255,255,0.5); } + a, .cooked-recipe-info span.cooked-fsm-button' ), [] ); ?> { color:rgba(255,255,255,0.5); } + a:hover, .cooked-recipe-info span.cooked-fsm-button:hover' ), [] ); ?> { color:#fff; } + { color:#fff; opacity:0.5; } + { border-color:rgba(255,255,255,0.25); } + { border-color:rgba(255,255,255,0.25); } + { border-color:rgba(255,255,255,0.25); color:#fff; } + { border-color:rgba(255,255,255,0.5); } + { background:rgba(0,0,0,0.25); border:none; border-radius:5px; } + { color:#fff; } + { border-color:rgba(255,255,255,0.15); } + { background:rgba(255,255,255,0.15); } + { border-color:rgba(255,255,255,0.15); } + { background:rgba(0,0,0,0.25); box-shadow:none !important; } + { box-shadow:none !important; } + { background:rgba(255,255,255,0.25); } + { background:rgba(255,255,255,0.15); } + { background:#000; color:#fff; } + { background:rgba(255,255,255,0.1); } + .cooked-rating-star.cooked-rating-star-empty, .cooked-recipe .cooked-ratable .cooked-rating-stars.cooked-user-rated > .cooked-rating-star.cooked-rating-star-empty' ), [] ); ?> { color:rgba(255,255,255,0.25); } + { color:rgba(255,255,255,0.5); } + { color:#fff; } + { background:#191919; color:#fff; box-shadow:0 -5px 30px rgba(0,0,0,0.4); } + { color:rgba(255,255,255,0.35); } + { color:rgba(255,255,255,0.65); } + { background:rgba(255,255,255,0.05); } + { color:rgba(255,255,255,0.5); } + { background:rgba(255,255,255,0.15); } { background:rgba(255, 255,255,0.25); color:#fff; } +echo wp_kses( $dm( '.cooked-related-recipes-empty' ), [] ); ?> { background:rgba(255, 255,255,0.25); color:#fff; } { background:rgba(79, 79, 79, 1); color:#fff; } +echo wp_kses( $dm( '.cooked-recipe-search select.cooked-sortby-select option' ), [] ); ?> { background:rgba(79, 79, 79, 1); color:#fff; } { background:#000; color:#fff; } { background:#000; color:#fff; } diff --git a/includes/class.cooked-admin-menus.php b/includes/class.cooked-admin-menus.php index f0f7572..a1d2745 100644 --- a/includes/class.cooked-admin-menus.php +++ b/includes/class.cooked-admin-menus.php @@ -87,7 +87,7 @@ public function parent_file_filter($parent_file) { // Settings Panel public function cooked_settings_page() { if (!current_user_can('edit_cooked_settings')) { - wp_die(__('You do not have sufficient permissions to access this page.', 'cooked')); + wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'cooked')); } include COOKED_DIR . 'templates/admin/settings.php'; @@ -96,7 +96,7 @@ public function cooked_settings_page() { // Import Page public function cooked_import_page() { if (!current_user_can('edit_cooked_settings')) { - wp_die(__('You do not have sufficient permissions to access this page.', 'cooked')); + wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'cooked')); } include COOKED_DIR . 'templates/admin/import.php'; @@ -105,7 +105,7 @@ public function cooked_import_page() { // Welcome Page public function cooked_welcome_content() { if (!current_user_can('edit_cooked_settings')) { - wp_die(__('You do not have sufficient permissions to access this page.', 'cooked')); + wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'cooked')); } include COOKED_DIR . 'templates/admin/welcome.php'; @@ -114,7 +114,7 @@ public function cooked_welcome_content() { // Cooked Pro public function cooked_pro() { if (!current_user_can('edit_cooked_settings')) { - wp_die(__('You do not have sufficient permissions to access this page.', 'cooked')); + wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'cooked')); } include COOKED_DIR . 'templates/admin/pro.php'; diff --git a/includes/class.cooked-ajax.php b/includes/class.cooked-ajax.php index b2e6096..42b71b5 100644 --- a/includes/class.cooked-ajax.php +++ b/includes/class.cooked-ajax.php @@ -362,7 +362,7 @@ public function save_default() { $_cooked_settings['default_content'] = wp_kses_post( $_POST['default_content'] ); update_option('cooked_settings', $_cooked_settings); } else { - echo __( 'No default content provided.', 'cooked' ); + echo esc_html__( 'No default content provided.', 'cooked' ); } wp_die(); diff --git a/includes/class.cooked-allergens.php b/includes/class.cooked-allergens.php index e02cc01..f59bc44 100644 --- a/includes/class.cooked-allergens.php +++ b/includes/class.cooked-allergens.php @@ -271,7 +271,7 @@ public static function render_from_recipe( $recipe ) { } $recipe_settings = Cooked_Recipes::get_settings( $recipe['id'] ); - echo self::render( $recipe_settings ); + echo wp_kses_post( self::render( $recipe_settings ) ); } /** @@ -361,8 +361,8 @@ public static function cooked_info_allergens( $recipe_settings ) { } echo ''; - echo '' . __( 'Allergens', 'cooked' ) . ''; - echo $html; + echo '' . esc_html__( 'Allergens', 'cooked' ) . ''; + echo wp_kses_post( $html ); echo ''; } diff --git a/includes/class.cooked-functions.php b/includes/class.cooked-functions.php index 5b61300..c4ae09e 100644 --- a/includes/class.cooked-functions.php +++ b/includes/class.cooked-functions.php @@ -124,17 +124,17 @@ public static function print_options() { echo '
      '; - echo ''; - echo '

      ' . __( 'Print Options:','cooked') . '

      '; - - echo ' '; - echo ' '; - echo ' '; - echo ' '; - echo ' '; - echo ' '; - echo ' '; - echo ' '; + echo ''; + echo '

      ' . esc_html__( 'Print Options:','cooked') . '

      '; + + echo ' '; + echo ' '; + echo ' '; + echo ' '; + echo ' '; + echo ' '; + echo ' '; + echo ' '; echo '
      '; } diff --git a/includes/class.cooked-import.php b/includes/class.cooked-import.php index 11d1cd8..60541f6 100644 --- a/includes/class.cooked-import.php +++ b/includes/class.cooked-import.php @@ -203,35 +203,35 @@ public static function field_import_button( $name, $field_options, $color, $fiel if ($total > 0) { echo '

      '; - echo ''; + echo ''; echo '

      '; echo '

      '; echo '0 / ' . esc_html( $total ) . ''; echo '

      '; - echo '

      Import Complete! You can now ' . __( 'reload', 'cooked' ) . ' the import screen.

      '; + echo '

      Import Complete! You can now ' . esc_html__( 'reload', 'cooked' ) . ' the import screen.

      '; } } public static function field_message( $name, $field_options, $color, $field ) { - echo '

      ' . $field['message'] . '

      '; + echo '

      ' . wp_kses_post( $field['message'] ) . '

      '; } public static function field_csv_upload( $name, $field_options, $color, $field ) { - echo '

      ' . __( 'Download sample CSV files:', 'cooked' ) . ' '; - echo '' . __( 'Small (1 recipe)', 'cooked' ) . ', '; - echo '' . __( 'Medium (3 recipes)', 'cooked' ) . ', '; - echo '' . __( 'Large (10 recipes)', 'cooked' ) . '

      '; + echo '

      ' . esc_html__( 'Download sample CSV files:', 'cooked' ) . ' '; + echo '' . esc_html__( 'Small (1 recipe)', 'cooked' ) . ', '; + echo '' . esc_html__( 'Medium (3 recipes)', 'cooked' ) . ', '; + echo '' . esc_html__( 'Large (10 recipes)', 'cooked' ) . '

      '; echo ''; echo '

      '; echo ''; echo '

      '; echo '

      '; - echo ''; + echo ''; echo '

      '; echo '

      '; echo ''; echo '

      '; - echo ''; + echo ''; echo ''; echo ''; } diff --git a/includes/class.cooked-measurements.php b/includes/class.cooked-measurements.php index 28f6a4c..06ea0ed 100644 --- a/includes/class.cooked-measurements.php +++ b/includes/class.cooked-measurements.php @@ -719,7 +719,7 @@ public static function time_format( $minutes, $format = 'default' ) { return 'PT0H'.intval( $minutes ).'M'; else: /* translators: singular and plural number of minutes (shorthand) */ - echo self::singular_plural( sprintf( __( '%d min','cooked' ), number_format_i18n($minutes) ), sprintf( __( '%d mins','cooked' ), number_format_i18n($minutes) ), $minutes ); + echo esc_html( self::singular_plural( sprintf( esc_html__( '%d min','cooked' ), number_format_i18n($minutes) ), sprintf( esc_html__( '%d mins','cooked' ), number_format_i18n($minutes) ), $minutes ) ); endif; elseif ( $minutes < 1440 ): $hours = floor( $minutes / 60 ); @@ -728,9 +728,9 @@ public static function time_format( $minutes, $format = 'default' ) { return 'PT'.intval( $hours ).'H'.( $minutes_left ? intval( $minutes_left ) : 0 ).'M'; else: /* translators: singular and plural number of hours (shorthand) */ - echo self::singular_plural( sprintf( __( '%d hr','cooked' ), number_format_i18n($hours) ), sprintf( __( '%d hrs','cooked' ), number_format_i18n($hours) ), $hours ); + echo esc_html( self::singular_plural( sprintf( esc_html__( '%d hr','cooked' ), number_format_i18n($hours) ), sprintf( esc_html__( '%d hrs','cooked' ), number_format_i18n($hours) ), $hours ) ); /* translators: singular and plural number of minutes (shorthand) */ - echo ( $minutes_left ? ' ' . self::singular_plural( sprintf( __( '%d min','cooked' ), number_format_i18n($minutes_left) ), sprintf( __( '%d mins','cooked' ), number_format_i18n($minutes_left) ), $minutes_left ) : '' ); + echo ( $minutes_left ? ' ' . esc_html( self::singular_plural( sprintf( esc_html__( '%d min','cooked' ), number_format_i18n($minutes_left) ), sprintf( esc_html__( '%d mins','cooked' ), number_format_i18n($minutes_left) ), $minutes_left ) ) : '' ); endif; else: $days = floor( $minutes / 24 / 60 ); @@ -744,11 +744,11 @@ public static function time_format( $minutes, $format = 'default' ) { return 'P'.intval( $days ).'DT'.( $hours_left ? intval( $hours_left ) : 0 ).'H'.( $minutes_left ? intval( $minutes_left ) : 0 ).'M'; else: /* translators: singular and plural number of days */ - echo self::singular_plural( sprintf( __( '%d day','cooked' ), number_format_i18n($days) ), sprintf( __( '%d days','cooked' ), number_format_i18n($days) ), $days ); + echo esc_html( self::singular_plural( sprintf( esc_html__( '%d day','cooked' ), number_format_i18n($days) ), sprintf( esc_html__( '%d days','cooked' ), number_format_i18n($days) ), $days ) ); /* translators: singular and plural number of hours (shorthand) */ - echo ( $hours_left ? ' ' . self::singular_plural( sprintf( __( '%d hr','cooked' ), number_format_i18n($hours_left) ), sprintf( __( '%d hrs','cooked' ), number_format_i18n($hours_left) ), $hours_left ) : '' ); + echo ( $hours_left ? ' ' . esc_html( self::singular_plural( sprintf( esc_html__( '%d hr','cooked' ), number_format_i18n($hours_left) ), sprintf( esc_html__( '%d hrs','cooked' ), number_format_i18n($hours_left) ), $hours_left ) ) : '' ); /* translators: singular and plural number of minutes (shorthand) */ - echo ( $minutes_left ? ' ' . self::singular_plural( sprintf( __( '%d min','cooked' ), number_format_i18n($minutes_left) ), sprintf( __( '%d mins','cooked' ), number_format_i18n($minutes_left) ), $minutes_left ) : '' ); + echo ( $minutes_left ? ' ' . esc_html( self::singular_plural( sprintf( esc_html__( '%d min','cooked' ), number_format_i18n($minutes_left) ), sprintf( esc_html__( '%d mins','cooked' ), number_format_i18n($minutes_left) ), $minutes_left ) ) : '' ); endif; endif; diff --git a/includes/class.cooked-migration.php b/includes/class.cooked-migration.php index 4bb0936..502de4d 100644 --- a/includes/class.cooked-migration.php +++ b/includes/class.cooked-migration.php @@ -87,7 +87,7 @@ public static function old_recipes_message() { if ($total > 0) { $class = 'notice notice-error'; /* translators: for displaying singular or plural versions depending on the number of recipes. */ - $message = sprintf( esc_html( _n( 'There is %1$s recipe that is from an older version of Cooked. Please %2$s to migrate this recipe.', 'There are %1$s recipes that are from an older version of Cooked. Please %2$s to migrate these recipes.', $total, 'cooked' ) ), '' . number_format( $total ) . '', '' . __( 'click here', 'cooked' ) . '' ); + $message = sprintf( esc_html( _n( 'There is %1$s recipe that is from an older version of Cooked. Please %2$s to migrate this recipe.', 'There are %1$s recipes that are from an older version of Cooked. Please %2$s to migrate these recipes.', $total, 'cooked' ) ), '' . number_format( $total ) . '', '' . __( 'click here', 'cooked' ) . '' ); printf('

      %2$s

      ', esc_attr($class), wp_kses_post($message)); } } diff --git a/includes/class.cooked-multilingual.php b/includes/class.cooked-multilingual.php index be95c69..7afaa38 100644 --- a/includes/class.cooked-multilingual.php +++ b/includes/class.cooked-multilingual.php @@ -267,7 +267,7 @@ public function translation_notice() { $plugin_name ) . ''; - printf( '

      %2$s

      ', esc_attr( $class ), $message ); + printf( '

      %2$s

      ', esc_attr( $class ), wp_kses_post( $message ) ); } } } diff --git a/includes/class.cooked-post-types.php b/includes/class.cooked-post-types.php index bc10ff8..3c488e5 100644 --- a/includes/class.cooked-post-types.php +++ b/includes/class.cooked-post-types.php @@ -95,7 +95,7 @@ function custom_columns( $columns ) { function custom_columns_data( $column, $post_id ) { if ( $column == 'featured_image' ): echo ''; - echo the_post_thumbnail( 'thumbnail' ); + echo wp_kses_post( get_the_post_thumbnail( $post_id, 'thumbnail' ) ); echo ''; endif; } @@ -164,9 +164,15 @@ public static function cooked_meta_tags() { - ID ) ); ?>"> [ + 'name' => true, + 'content' => true, + 'property' => true, + ], + ] ); } } diff --git a/includes/class.cooked-recipe-meta.php b/includes/class.cooked-recipe-meta.php index e6d4517..3eba350 100644 --- a/includes/class.cooked-recipe-meta.php +++ b/includes/class.cooked-recipe-meta.php @@ -213,10 +213,12 @@ public function recipe_embed_shortcode_admin_notice() { printf( '

      %s

      ', - sprintf( - /* translators: %s: recipe embed shortcode */ - __( 'This recipe is set up to include itself in the Recipe Template (containing shortcode %s), which can break the page. Remove the embed that references this same recipe.', 'cooked' ), - $shortcode + wp_kses_post( + sprintf( + /* translators: %s: recipe embed shortcode */ + __( 'This recipe is set up to include itself in the Recipe Template (containing shortcode %s), which can break the page. Remove the embed that references this same recipe.', 'cooked' ), + '' . esc_html( $shortcode ) . '' + ) ) ); } @@ -263,7 +265,7 @@ public static function bulk_add_modal() {

      - +

  • @@ -489,7 +491,7 @@ function cooked_render_recipe_fields( $post_id ) {
    - +
    @@ -497,7 +499,7 @@ function cooked_render_recipe_fields( $post_id ) {

    @@ -520,7 +522,7 @@ function cooked_render_recipe_fields( $post_id ) {
    -

    ' . __( 'Default Recipe Template','cooked') . '' . __( 'Choose from the options below to use this layout as the default for new recipes or for all recipes.', 'cooked') . '' . __( 'Save as Default','cooked' ) . '  ' . __( 'Apply to All','cooked' ) . '0 / 0' ); ?>" class="button cooked-layout-save-default">' . __( 'Recipe Template','cooked') . '' . __( 'Using the built-in recipe shortcodes found on the "Shortcodes" tab, you can create the layout of your recipe below. Use the "Save as Default" button to save your template.','cooked') ); ?>">

    +

    ' . esc_html__( 'Default Recipe Template','cooked') . '' . esc_html__( 'Choose from the options below to use this layout as the default for new recipes or for all recipes.', 'cooked') . '' . esc_html__( 'Save as Default','cooked' ) . '  ' . esc_html__( 'Apply to All','cooked' ) . '0 / 0' ); ?>" class="button cooked-layout-save-default">' . esc_html__( 'Recipe Template','cooked') . '' . esc_html__( 'Using the built-in recipe shortcodes found on the "Shortcodes" tab, you can create the layout of your recipe below. Use the "Save as Default" button to save your template.','cooked') ); ?>">

    @@ -538,7 +540,7 @@ function cooked_render_recipe_fields( $post_id ) {
    -

    +

    @@ -561,7 +563,7 @@ function cooked_render_recipe_fields( $post_id ) {

    -

    +

    @@ -599,7 +601,7 @@ function cooked_render_recipe_fields( $post_id ) {
    -

    +

    - - ' ); ?> + + ' ); ?>
    @@ -920,7 +922,7 @@ function cooked_render_recipe_fields( $post_id ) {
    - + @@ -953,8 +955,8 @@ function cooked_render_recipe_fields( $post_id ) { 'h6' => 'h6' ]; foreach ($heading_elements as $element => $label): ?> - @@ -976,34 +978,34 @@ function cooked_render_recipe_fields( $post_id ) { 1
    - - - + + +
    - +
    true, 'media_buttons' => false, 'wpautop' => false, 'editor_height' => 250, - 'textarea_name' => '_recipe_settings[directions][' . $random_key . '][content]', + 'textarea_name' => '_recipe_settings[directions][' . esc_attr( $random_key ) . '][content]', 'quicktags' => true ]); ?> - +
    - - + +
    - +
    @@ -1064,8 +1066,8 @@ function cooked_render_recipe_fields( $post_id ) { 'h6' => 'h6' ]; foreach ($heading_elements as $element => $label): ?> - @@ -1164,16 +1166,16 @@ function cooked_render_recipe_fields( $post_id ) { echo '
      '; if ($sub_slug === 'trans_fat'): echo '
    • '; - echo $sub_nf['nutrition_info_name'] . ' ___' . ( isset($sub_nf['measurement']) ? '' . esc_html( $sub_nf['measurement'] ) . '' : '' ); + echo wp_kses_post( $sub_nf['nutrition_info_name'] ) . ' ___' . ( isset($sub_nf['measurement']) ? '' . esc_html( $sub_nf['measurement'] ) . '' : '' ); echo '
    • '; elseif ($sub_slug === 'added_sugars'): echo '
      • '; - echo __('Includes', 'cooked') . ' ___' . ( isset($sub_nf['measurement']) ? '' . esc_html( $sub_nf['measurement'] ) . '' : '' ) . ' ' . esc_html($sub_nf['name']); + echo esc_html__('Includes', 'cooked') . ' ___' . ( isset($sub_nf['measurement']) ? '' . esc_html( $sub_nf['measurement'] ) . '' : '' ) . ' ' . esc_html($sub_nf['name']); echo ( isset( $sub_nf['pdv'] ) ? '0%' : '' ); echo '
      '; else: echo '
    • '; - echo $sub_nf['name'] . ' ___' . ( isset($sub_nf['measurement']) ? '' . esc_html( $sub_nf['measurement'] ) . '' : '' ); + echo esc_html( $sub_nf['name'] ) . ' ___' . ( isset($sub_nf['measurement']) ? '' . esc_html( $sub_nf['measurement'] ) . '' : '' ); echo ( isset( $sub_nf['pdv'] ) ? '0%' : '' ); echo '
    • '; endif; @@ -1190,7 +1192,7 @@ function cooked_render_recipe_fields( $post_id ) { $nf ): echo '
    • '; - echo $nf['name'] . ' ___' . ( isset($nf['measurement']) ? '' . esc_html( $nf['measurement'] ) . '' : '' ); + echo esc_html( $nf['name'] ) . ' ___' . ( isset($nf['measurement']) ? '' . esc_html( $nf['measurement'] ) . '' : '' ); echo ( isset( $nf['pdv'] ) ? '0%' : '' ); echo '
    • '; endforeach; ?> @@ -1207,7 +1209,7 @@ function cooked_render_recipe_fields( $post_id ) {
    - + @@ -1243,11 +1245,11 @@ function cooked_render_recipe_fields( $post_id ) {

    @@ -1261,7 +1263,7 @@ function cooked_render_recipe_fields( $post_id ) { $image_thumb = wp_get_attachment_image( $g_item, 'thumbnail' ); $image_title = get_the_title( $g_item ); - echo ''; + echo ''; endforeach; endif; @@ -1293,7 +1295,7 @@ function cooked_render_recipe_fields( $post_id ) {

    @@ -1303,7 +1305,7 @@ function cooked_render_recipe_fields( $post_id ) {

    + echo sprintf( esc_html__( '"%1$s" and "%2$s"','cooked' ), 'left','right' ); ?>

    @@ -1325,7 +1327,7 @@ function cooked_render_recipe_fields( $post_id ) { 'difficulty_level' => __('Difficulty', 'cooked'), 'servings' => __('Servings Switcher', 'cooked'), 'taxonomies' => __('Category', 'cooked'), - 'print' => __('Print Mode', 'cooked'), + 'print' => esc_html__('Print Mode', 'cooked'), 'fullscreen' => __('Full-Screen Mode', 'cooked') ]); @@ -1505,22 +1507,22 @@ function cooked_render_recipe_fields( $post_id ) { width


    ratio


    nav


    allowfullscreen

    @@ -1601,7 +1603,7 @@ function cooked_render_recipe_fields( $post_id ) {

    diff --git a/includes/class.cooked-recipes.php b/includes/class.cooked-recipes.php index 0c52211..9eae424 100644 --- a/includes/class.cooked-recipes.php +++ b/includes/class.cooked-recipes.php @@ -285,7 +285,7 @@ public static function recipe_list( $orderby = 'date', $show = 5, $recipes = fal echo '
    '; - echo has_post_thumbnail($rid) && !$hide_image ? '' : ''; + echo has_post_thumbnail($rid) && !$hide_image ? '' : ''; echo '
    '; @@ -295,7 +295,7 @@ public static function recipe_list( $orderby = 'date', $show = 5, $recipes = fal echo '
    '; $author = $recipe['author']; /* translators: stating the recipe author with a "By" in front of it. (ex: "By John Smith") */ - echo sprintf( __( 'By %s', 'cooked' ), '' . wp_kses_post( $author['name'] ) . '' ); + echo sprintf( esc_html__( 'By %s', 'cooked' ), '' . wp_kses_post( $author['name'] ) . '' ); echo '
    '; endif; @@ -329,7 +329,7 @@ public static function card( $rid, $width = false, $hide_image = false, $hide_ti do_action( 'cooked_recipe_grid_before_image', $recipe ); - echo has_post_thumbnail($rid) && !$hide_image ? '' : ''; + echo has_post_thumbnail($rid) && !$hide_image ? '' : ''; //do_action( 'cooked_recipe_grid_after_image', $recipe ); @@ -349,7 +349,7 @@ public static function card( $rid, $width = false, $hide_image = false, $hide_ti echo ''; $author = $recipe['author']; /* translators: stating the recipe author with a "By" in front of it. (ex: "By John Smith") */ - echo sprintf( __( 'By %s', 'cooked' ), '' . $author['name'] . '' ); + echo wp_kses_post( sprintf( __( 'By %s', 'cooked' ), '' . esc_html( $author['name'] ) . '' ) ); echo ''; endif; @@ -938,13 +938,13 @@ public static function measurement_system_switcher() { ]; $current_label = isset( $labels[ $current ] ) ? $labels[ $current ] : $labels['']; echo ''; - echo '' . __( 'Units', 'cooked' ) . ''; + echo '' . esc_html__( 'Units', 'cooked' ) . ''; echo '' . esc_html( $current_label ) . ''; - echo ''; + echo ''; echo ''; echo ''; endif; @@ -993,22 +993,22 @@ public static function serving_size_switcher( $servings ) { endif; echo ''; - echo '' . __('Yields','cooked') . ''; + echo '' . esc_html__('Yields','cooked') . ''; if ( !$printing && !$switcher_disabled ): /* translators: singular and plural "serving" sizes */ $servings_string = sprintf( esc_html( _n( '%s Serving', '%s Servings', $servings, 'cooked' ) ), $servings ); - echo '' . $servings_string . ''; - echo ''; + echo '' . esc_html( $servings_string ) . ''; + echo ''; echo ''; else: /* translators: singular and plural "serving" sizes */ - echo '' . sprintf( esc_html( _n( '%s Serving', '%s Servings', $servings, 'cooked' ) ), $servings ) . ''; + echo '' . esc_html( sprintf( _n( '%s Serving', '%s Servings', $servings, 'cooked' ), $servings ) ) . ''; endif; echo ''; @@ -1035,7 +1035,7 @@ public static function single_ingredient( $ing, $checkboxes = true, $plain_text ? ($ing['section_heading_element'] === 'div' ? $default_element : $ing['section_heading_element']) : $default_element; - echo '<' . $element . ' class="cooked-single-ingredient cooked-heading">' . esc_html($ing['section_heading_name']) . ''; + echo '<' . tag_escape( $element ) . ' class="cooked-single-ingredient cooked-heading">' . esc_html($ing['section_heading_name']) . ''; } } elseif ( isset($ing['name']) && $ing['name'] ) { @@ -1154,7 +1154,7 @@ public static function single_ingredient( $ing, $checkboxes = true, $plain_text if ( $sub_name ) { echo ''; - echo ' ' . __('or', 'cooked') . ' '; + echo ' ' . esc_html__('or', 'cooked') . ' '; echo ( $sub_amount ? '' . wp_kses_post($sub_amount) . ' ' . wp_kses_post( $sub_measurement ) . ' ' : '' ); echo '' . wp_kses_post( $sub_name ) . ''; echo ''; @@ -1164,7 +1164,7 @@ public static function single_ingredient( $ing, $checkboxes = true, $plain_text } $ing_html = ob_get_clean(); - echo apply_filters( 'cooked_single_ingredient_html', $ing_html, $ing, $checkboxes, $plain_text ); + echo wp_kses_post( apply_filters( 'cooked_single_ingredient_html', $ing_html, $ing, $checkboxes, $plain_text ) ); } public static function single_direction($dir, $number = false, $plain_text = false, $step = false, $atts = false) { @@ -1183,7 +1183,7 @@ public static function single_direction($dir, $number = false, $plain_text = fal ? ($dir['section_heading_element'] === 'div' ? $default_element : $dir['section_heading_element']) : $default_element; - echo '<' . $element . ' class="cooked-single-direction cooked-heading">' . esc_html($dir['section_heading_name']) . ''; + echo '<' . tag_escape( $element ) . ' class="cooked-single-direction cooked-heading">' . esc_html($dir['section_heading_name']) . ''; } } elseif ( !empty($dir['content']) || !empty($dir['image']) || !empty($dir['video']) ) { @@ -1201,9 +1201,9 @@ public static function single_direction($dir, $number = false, $plain_text = fal /* translators: singular and plural "steps" */ $step_string = sprintf( __( 'Step %d', 'cooked' ), $step ); - echo '
    '; + echo '
    '; echo $number ? '' . esc_html($number) . '' : ''; - echo '
    ' . do_shortcode($content) . ($image ? wpautop($image) : '') . ($video ? '' : '') . '
    '; + echo '
    ' . do_shortcode($content) . ($image ? wp_kses_post( wpautop($image) ) : '') . ($video ? '' : '') . '
    '; echo '
    '; } } @@ -1259,7 +1259,7 @@ public static function recipe_search_box( $options = false ) { echo '
    '; echo ''; - echo '' . ( isset($active_taxonomy) ? esc_html( $active_taxonomy ) : __('Browse','cooked') ) . ''; + echo '' . ( isset($active_taxonomy) ? esc_html( $active_taxonomy ) : esc_html__('Browse','cooked') ) . ''; echo ''; endif; @@ -1280,9 +1280,9 @@ public static function recipe_search_box( $options = false ) { $terms_array = Cooked_Settings::terms_array( 'cp_recipe_category', false, __('No categories','cooked'), true, true, false ); if ( !empty($terms_array) ): echo ''; - echo '' . __('Categories','cooked') . ''; + echo '' . esc_html__('Categories','cooked') . ''; echo '
    '; - echo ( $view_all_recipes_url ? '' . __( 'All Categories','cooked' ) . '' : '' ); + echo ( $view_all_recipes_url ? '' . esc_html__( 'All Categories','cooked' ) . '' : '' ); foreach ( $terms_array as $key => $val ): if ( $key ): $term = get_term( $key ); @@ -1351,11 +1351,11 @@ public static function recipe_search_box( $options = false ) { echo '
    '; - echo !$options['hide_browse'] && $taxonomy_search_fields ? $taxonomy_search_fields : ''; + echo !$options['hide_browse'] && $taxonomy_search_fields ? wp_kses_post( $taxonomy_search_fields ) : ''; - echo ''; + echo ''; - echo ''; + echo ''; echo '
    '; diff --git a/includes/class.cooked-settings.php b/includes/class.cooked-settings.php index 1d50d02..fa778c3 100644 --- a/includes/class.cooked-settings.php +++ b/includes/class.cooked-settings.php @@ -129,9 +129,9 @@ function browse_page_missing_notice() { '' . __( 'Cooked Plugin Setup', 'cooked' ) . ' ' . /* translators: %s: Browse/Search Recipes Page link */ __( 'To display your recipes properly, please set up your %s.', 'cooked' ), - '' . __( 'Browse/Search Recipes Page', 'cooked' ) . '' + '' . __( 'Browse/Search Recipes Page', 'cooked' ) . '' ); - printf( '

    %2$s

    ', esc_attr( $class ), $message ); + printf( '

    %2$s

    ', esc_attr( $class ), wp_kses_post( $message ) ); } } @@ -233,7 +233,7 @@ public static function prefix_dark_mode_css( $selectors ) { } if ( '' === $scope ) { - return $selectors; + return wp_kses( $selectors, [] ); } $parts = array_map( 'trim', explode( ',', $selectors ) ); @@ -244,15 +244,15 @@ function ( $part ) use ( $scope ) { $parts ); - return implode( ', ', $parts ); + return wp_kses( implode( ', ', $parts ), [] ); } public static function tabs_fields() { $pages_array = self::pages_array( __('Choose a page...','cooked'), __('No pages','cooked') ); $categories_array = self::terms_array( 'cp_recipe_category', __('No default', 'cooked'), __('No categories', 'cooked') ); $recipes_per_page_array = self::per_page_array(); - $recipe_archive_slug = sanitize_title_with_dashes( __( 'Recipe Archive', 'cooked' ) ); - $recipe_archive_url = home_url( '/' . $recipe_archive_slug . '/' ); + $recipe_archive_slug = sanitize_title_with_dashes( __( 'Recipe Archive', 'cooked' ) ); + $recipe_archive_url = home_url( '/' . $recipe_archive_slug . '/' ); // Dynamically load roles. $role_options = []; @@ -285,7 +285,7 @@ public static function tabs_fields() { 'recipes_per_page' => [ 'title' => __('Recipes Per Page', 'cooked'), /* translators: a description on how to choose the default number of recipes per page. */ - 'desc' => sprintf(__('Choose the default (set via the %s panel) or choose a different number here.', 'cooked'), '' . __('Settings > Reading', 'cooked') . ''), + 'desc' => sprintf(__('Choose the default (set via the %s panel) or choose a different number here.', 'cooked'), '' . __('Settings > Reading', 'cooked') . ''), 'type' => 'select', 'default' => 9, 'options' => $recipes_per_page_array @@ -336,7 +336,7 @@ public static function tabs_fields() { ], 'print_view_display_options' => [ 'title' => __('Print View', 'cooked'), - 'desc' => __('When enabled, the website logo will appear at the top of the recipe print screen.', 'cooked'), + 'desc' => esc_html__('When enabled, the website logo will appear at the top of the recipe print screen.', 'cooked'), 'type' => 'checkboxes', 'default' => [], 'options' => apply_filters( @@ -673,8 +673,8 @@ public static function field_radio( $field_name, $options ) { $combined_extras = $is_disabled . $conditional_value; - if ( $conditional_requirement ): echo ''; endif; - echo ''; + if ( $conditional_requirement ): echo ''; endif; + echo ''; echo ' '; echo '
    '; if ( $conditional_requirement ): echo '
    '; endif; @@ -694,7 +694,7 @@ public static function field_select( $field_name, $options, $color = false, $fie } echo '

    '; - echo ''; + echo ''; foreach ( $options as $value => $name) { echo ''; } @@ -722,12 +722,12 @@ public static function field_migrate_button( $field_name, $title ) { if ($total > 0) { echo '

    '; - echo ''; + echo ''; echo '

    '; echo '

    '; echo '0 / ' . esc_html( $total ) . ''; echo '

    '; - echo '

    Migration Complete! You can now ' . __( 'reload', 'cooked' ) . ' the settings screen.

    '; + echo '

    Migration Complete! You can now ' . esc_html__( 'reload', 'cooked' ) . ' the settings screen.

    '; } } } @@ -771,7 +771,7 @@ public static function field_permalink_field($field_name, $end_of_url) { } echo ''; } @@ -800,7 +800,7 @@ public static function field_image_field( $field_name, $default ) { echo '
    '; echo ''; - echo ''; + echo ''; if ( $has_image ) { echo wp_get_attachment_image( $attachment_id, 'thumbnail', false, [ @@ -866,11 +866,11 @@ public static function field_checkboxes($field_name, $options, $color = false, $ if ($is_disabled) { echo ''; - echo ''; } else { - echo ''; diff --git a/includes/class.cooked-shortcodes.php b/includes/class.cooked-shortcodes.php index c02c26d..1fc474b 100644 --- a/includes/class.cooked-shortcodes.php +++ b/includes/class.cooked-shortcodes.php @@ -164,7 +164,7 @@ public function cooked_browse_shortcode( $sc_atts, $content = null ) { if ( isset($_cooked_settings['advanced']) && !empty($_cooked_settings['advanced']) && in_array( 'disable_public_recipes', $_cooked_settings['advanced'] ) ) { /* translators: referring to the bottom of the Settings page. */ - return current_user_can( 'edit_cooked_settings' ) ? wpautop( sprintf( __('Public recipes are currently disabled. You can change this at the bottom of the %s page.','cooked'), '' . __( 'Settings', 'cooked' ) . '' ) ) : false; + return current_user_can( 'edit_cooked_settings' ) ? wpautop( sprintf( __('Public recipes are currently disabled. You can change this at the bottom of the %s page.','cooked'), '' . __( 'Settings', 'cooked' ) . '' ) ) : false; } if ( is_admin() ) return false; @@ -226,9 +226,9 @@ public function cooked_recipe_card_shortcode( $atts, $content = null ) { ob_start(); if ( $recipe_id ) { - echo Cooked_Recipes::card( $recipe_id, $width, $hide_image, $hide_title, $hide_excerpt, $hide_author, $style ); + echo wp_kses_post( Cooked_Recipes::card( $recipe_id, $width, $hide_image, $hide_title, $hide_excerpt, $hide_author, $style ) ); } elseif ( $category_id ) { - echo Cooked_Taxonomies::card( $category_id, $width, $hide_image, $hide_total, $style ); + echo wp_kses_post( Cooked_Taxonomies::card( $category_id, $width, $hide_image, $hide_total, $style ) ); } return ob_get_clean(); @@ -682,7 +682,7 @@ public static function cooked_info_author() { echo ''; echo !$hide_avatars ? '' . ( !empty($author) ? wp_kses_post( $author['profile_photo'] ) : '' ) . '' : ''; - echo '' . __('Author', 'cooked') . '' . ( $clickable && $permalink ? '' : '' ) . (!empty($author) ? $author['name'] : '') . ( $clickable && $permalink ? '' : '' ); + echo '' . esc_html__('Author', 'cooked') . '' . ( $clickable && $permalink ? '' : '' ) . (!empty($author) ? esc_html( $author['name'] ) : '') . ( $clickable && $permalink ? '' : '' ); echo ''; wp_reset_postdata(); @@ -693,8 +693,8 @@ public static function cooked_info_difficulty( $recipe ) { global $_cooked_settings; if (in_array('difficulty_level', $_cooked_settings['recipe_info_display_options']) && isset($recipe['difficulty_level']) && $recipe['difficulty_level']) { - $dl_html = '' . __('Difficulty','cooked') . '' . Cooked_Recipes::difficulty_level( $recipe['difficulty_level'] ) . ''; - echo apply_filters( 'cooked_show_difficulty_level', $dl_html, $recipe['difficulty_level'] ); + $dl_html = '' . esc_html__('Difficulty','cooked') . '' . Cooked_Recipes::difficulty_level( $recipe['difficulty_level'] ) . ''; + echo wp_kses_post( apply_filters( 'cooked_show_difficulty_level', $dl_html, $recipe['difficulty_level'] ) ); } } @@ -715,13 +715,13 @@ public static function cooked_info_print() { $query_args['print'] = 1; $servings = (float)esc_html( get_query_var( 'servings', false ) ); $query_args['servings'] = !empty($servings) ? $servings : false; - echo ''; + echo ''; } public static function cooked_info_fullscreen() { global $recipe_settings, $_cooked_settings; - echo ''; + echo ''; wp_enqueue_script('cooked-nosleep'); } @@ -730,7 +730,7 @@ public static function cooked_info_prep_time( $recipe ) { if (!empty($_cooked_settings['recipe_info_display_options']) && in_array('timing_prep',$_cooked_settings['recipe_info_display_options'])) { $prep_time = isset($recipe['prep_time']) ? esc_html( $recipe['prep_time'] ) : 0; - echo $prep_time ? '' . __('Prep Time','cooked') . '' . Cooked_Measurements::time_format( $prep_time ) . '' : ''; + echo $prep_time ? '' . esc_html__('Prep Time','cooked') . '' . wp_kses_post( Cooked_Measurements::time_format( $prep_time ) ) . '' : ''; } } @@ -739,7 +739,7 @@ public static function cooked_info_cook_time( $recipe ) { if (!empty($_cooked_settings['recipe_info_display_options']) && in_array('timing_cook', $_cooked_settings['recipe_info_display_options'])) { $cook_time = isset($recipe['cook_time']) ? esc_html( $recipe['cook_time'] ) : 0; - echo $cook_time ? '' . __('Cook Time','cooked') . '' . Cooked_Measurements::time_format( $cook_time ) . '' : ''; + echo $cook_time ? '' . esc_html__('Cook Time','cooked') . '' . wp_kses_post( Cooked_Measurements::time_format( $cook_time ) ) . '' : ''; } } @@ -750,14 +750,14 @@ public static function cooked_info_total_time( $recipe ) { $total_time = isset($recipe['total_time']) ? esc_html( $recipe['total_time'] ) : 0; if ( $total_time ) { - echo $total_time ? '' . __('Total Time','cooked') . '' . Cooked_Measurements::time_format( $total_time ) . '' : ''; + echo $total_time ? '' . esc_html__('Total Time','cooked') . '' . wp_kses_post( Cooked_Measurements::time_format( $total_time ) ) . '' : ''; } else { $prep_time = isset($recipe['prep_time']) ? esc_html( $recipe['prep_time'] ) : 0; $cook_time = isset($recipe['cook_time']) ? esc_html( $recipe['cook_time'] ) : 0; if ( $prep_time && $cook_time ) { $total_time = $prep_time + $cook_time; - echo $total_time ? '' . __('Total Time','cooked') . '' . Cooked_Measurements::time_format( $total_time ) . '' : ''; + echo $total_time ? '' . esc_html__('Total Time','cooked') . '' . wp_kses_post( Cooked_Measurements::time_format( $total_time ) ) . '' : ''; } } } @@ -797,7 +797,7 @@ public static function cooked_info_taxonomies() { do_action( 'cooked_info_taxonomies_shortcode_after', $recipe_settings ); if ( $recipe_terms_list ): - echo wp_filter_post_kses( $recipe_terms_list ); + echo wp_kses_post( $recipe_terms_list ); endif; endif; @@ -834,10 +834,10 @@ public function cooked_notes_shortcode($atts, $content = null) { if (isset($recipe_settings['notes']) && !empty($recipe_settings['notes'])) { $notes = Cooked_Recipes::format_content($recipe_settings['notes']); - $show_header = $show_header ? '
    ' . __('Notes', 'cooked') . '
    ' : ''; + $show_header = $show_header ? '
    ' . esc_html__( 'Notes', 'cooked' ) . '
    ' : ''; echo '
    '; - echo $show_header; + echo wp_kses_post( $show_header ); echo do_shortcode($notes); echo '
    '; } @@ -976,7 +976,7 @@ public function cooked_nutrition_shortcode($atts, $content = null) { echo '

    ' . esc_html( isset($nutrition_facts[$slug]) ? $nutrition_facts[$slug] : '' ) . '

    '; echo '
    '; else: - echo '

    ' . $servings_change . ' ' . esc_html(strtolower($nf['name'])) . '

    '; + echo '

    ' . esc_html( $servings_change ) . ' ' . esc_html(strtolower($nf['name'])) . '

    '; endif; endforeach; echo '
    '; @@ -1018,8 +1018,8 @@ public function cooked_nutrition_shortcode($atts, $content = null) { if ( isset( $nutrition_facts[$slug] ) && $nutrition_facts[$slug] || isset( $nutrition_facts[$slug] ) && $nutrition_facts[$slug] === '0' ): echo '
    '; - echo '' . $nf['name'] . ' ' . esc_html( $nutrition_facts[$slug] ) . '' . ( isset($nf['measurement']) ? '' . esc_html( $nf['measurement'] ) . '' : '' ); - echo ( isset( $nf['pdv'] ) && $nutrition_facts[$slug] ? '' . ceil( ( esc_html( $nutrition_facts[$slug] ) / $nf['pdv'] ) * 100 ) . '%' : '' ); + echo '' . esc_html( $nf['name'] ) . ' ' . esc_html( $nutrition_facts[$slug] ) . '' . ( isset($nf['measurement']) ? '' . esc_html( $nf['measurement'] ) . '' : '' ); + echo ( isset( $nf['pdv'] ) && $nutrition_facts[$slug] ? '' . esc_html( ceil( ( $nutrition_facts[$slug] / $nf['pdv'] ) * 100 ) ) . '%' : '' ); if ( isset($nf['subs']) ): foreach( $nf['subs'] as $sub_slug => $sub_nf ): @@ -1027,17 +1027,17 @@ public function cooked_nutrition_shortcode($atts, $content = null) { echo '
    '; if ($sub_slug === 'trans_fat'): echo '
    '; - echo $sub_nf['nutrition_info_name'] . ' ' . esc_html( $nutrition_facts[$sub_slug] ) . '' . ( isset($sub_nf['measurement']) ? '' . esc_html( $sub_nf['measurement'] ) . '' : '' ); + echo wp_kses_post( $sub_nf['nutrition_info_name'] ) . ' ' . esc_html( $nutrition_facts[$sub_slug] ) . '' . ( isset($sub_nf['measurement']) ? '' . esc_html( $sub_nf['measurement'] ) . '' : '' ); echo '
    '; elseif ($sub_slug === 'added_sugars'): echo '
    '; - echo __('Includes', 'cooked') . ' ' . esc_html( $nutrition_facts[$sub_slug] ) . '' . ( isset($sub_nf['measurement']) ? '' . esc_html( $sub_nf['measurement'] ) . '' : '' ) . ' ' . esc_html($sub_nf['name']); - echo ( isset( $sub_nf['pdv'] ) ? '' . ceil( ( esc_html( $nutrition_facts[$sub_slug] ) / $sub_nf['pdv'] ) * 100 ) . '%' : '' ); + echo esc_html__('Includes', 'cooked') . ' ' . esc_html( $nutrition_facts[$sub_slug] ) . '' . ( isset($sub_nf['measurement']) ? '' . esc_html( $sub_nf['measurement'] ) . '' : '' ) . ' ' . esc_html($sub_nf['name']); + echo ( isset( $sub_nf['pdv'] ) ? '' . esc_html( ceil( ( $nutrition_facts[$sub_slug] / $sub_nf['pdv'] ) * 100 ) ) . '%' : '' ); echo '
    '; else: echo '
    '; echo esc_html( $sub_nf['name'] ) . ' ' . esc_html( $nutrition_facts[$sub_slug] ) . '' . ( isset($sub_nf['measurement']) ? '' . esc_html( $sub_nf['measurement'] ) . '' : '' ); - echo ( isset( $sub_nf['pdv'] ) && $nutrition_facts[$sub_slug] ? '' . ceil( ( esc_html( $nutrition_facts[$sub_slug] ) / $sub_nf['pdv'] ) * 100 ) . '%' : '' ); + echo ( isset( $sub_nf['pdv'] ) && $nutrition_facts[$sub_slug] ? '' . esc_html( ceil( ( $nutrition_facts[$sub_slug] / $sub_nf['pdv'] ) * 100 ) ) . '%' : '' ); echo '
    '; endif; echo '
    '; @@ -1067,7 +1067,7 @@ public function cooked_nutrition_shortcode($atts, $content = null) { if ( isset( $nutrition_facts[$slug] ) && $nutrition_facts[$slug] || isset( $nutrition_facts[$slug] ) && $nutrition_facts[$slug] === '0' ): echo '
    '; echo '' . esc_html($nf['name']) . ' ' . esc_html( $nutrition_facts[$slug] ) . '' . ( isset($nf['measurement']) ? '' . esc_html( $nf['measurement'] ) . '' : '' ); - echo ( isset( $nf['pdv'] ) ? '' . ceil( ( esc_html( $nutrition_facts[$slug] ) / $nf['pdv'] ) * 100 ) . '%' : '' ); + echo ( isset( $nf['pdv'] ) ? '' . esc_html( ceil( ( $nutrition_facts[$slug] / $nf['pdv'] ) * 100 ) ) . '%' : '' ); echo '
    '; endif; endforeach; @@ -1089,7 +1089,7 @@ public function cooked_nutrition_shortcode($atts, $content = null) { echo '
    '; echo '
    '; - echo '
    ' . __('Amount per serving','cooked') . '
    '; + echo '
    ' . esc_html__('Amount per serving','cooked') . '
    '; if ( isset($mid_facts_content) && $mid_facts_content ): echo '
    '; @@ -1099,7 +1099,7 @@ public function cooked_nutrition_shortcode($atts, $content = null) { if ( isset($main_facts_content) && $main_facts_content ): echo '
    '; - echo '
    ' . __('% Daily Value *','cooked'). '
    '; + echo '
    ' . esc_html__('% Daily Value *','cooked'). '
    '; echo '
    '; echo wp_kses_post( $main_facts_content ); echo '
    '; @@ -1121,11 +1121,11 @@ public function cooked_nutrition_shortcode($atts, $content = null) { if ( isset($nutrition_facts_content) && $nutrition_facts_content ): echo '
    '; - echo '
    ' . __('Nutrition Facts', 'cooked') . '
    '; + echo '
    ' . esc_html__('Nutrition Facts', 'cooked') . '
    '; echo wp_kses_post( $nutrition_facts_content ); if ( isset($main_facts_content) && $main_facts_content || isset($bottom_facts_content) && $bottom_facts_content ): echo '
    '; - echo '

    * ' . __('The % Daily Value (DV) tells you how much a nutrient in a serving of food contributes to a daily diet. 2,000 calories a day is used for general nutrition advice.','cooked') . '

    '; + echo '

    * ' . esc_html__('The % Daily Value (DV) tells you how much a nutrient in a serving of food contributes to a daily diet. 2,000 calories a day is used for general nutrition advice.','cooked') . '

    '; endif; // Add the Edamam attribution "Powered by Edamam" if the Edamam API is enabled. @@ -1250,7 +1250,7 @@ public function cooked_related_recipes_shortcode($atts, $content = null) { foreach ($recipe_ids as $rid) { echo '
    '; - echo Cooked_Recipes::card($rid, false, $hide_image, false, $hide_excerpt, $hide_author); + echo wp_kses_post( Cooked_Recipes::card($rid, false, $hide_image, false, $hide_excerpt, $hide_author) ); echo '
    '; } diff --git a/includes/class.cooked-updates.php b/includes/class.cooked-updates.php index 1038d64..1a3fc90 100644 --- a/includes/class.cooked-updates.php +++ b/includes/class.cooked-updates.php @@ -114,8 +114,10 @@ private static function run_updates() { update_option( 'cooked_pro_settings_version', self::$current_pro_version ); } - // Log the update - error_log( sprintf( 'Cooked: Updated from version %s to %s', $old_version, self::$current_version ) ); + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + error_log( sprintf( 'Cooked: Updated from version %s to %s', $old_version, self::$current_version ) ); + } } /** @@ -131,7 +133,10 @@ private static function run_version_updates() { try { call_user_func( [__CLASS__, $method] ); } catch ( Exception $e ) { - error_log( sprintf( 'Cooked: Error running update method %s: %s', $method, $e->getMessage() ) ); + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + error_log( sprintf( 'Cooked: Error running update method %s: %s', $method, $e->getMessage() ) ); + } } } } @@ -329,7 +334,8 @@ private static function fix_recipe_line_endings() { } // Log the update if any recipes were modified - if ( $updated_count > 0 ) { + if ( $updated_count > 0 && defined( 'WP_DEBUG' ) && WP_DEBUG ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log( sprintf( 'Cooked: Fixed line endings in %d recipes for WordPress exporter/importer compatibility.', $updated_count ) ); } } @@ -347,7 +353,10 @@ private static function purge_legacy_related_recipes_cache() { delete_option( 'cooked_related_version' ); delete_option( 'cooked_related_calculation_last' ); - error_log( 'Cooked: Purged legacy related-recipes cache and options.' ); + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + error_log( 'Cooked: Purged legacy related-recipes cache and options.' ); + } } /** @@ -380,7 +389,8 @@ private static function remove_recipes_from_cooked_user_meta() { $updated_count++; } - if ( $updated_count > 0 ) { + if ( $updated_count > 0 && defined( 'WP_DEBUG' ) && WP_DEBUG ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log( sprintf( 'Cooked: Removed legacy recipes key from %d user(s) cooked_user_meta.', $updated_count ) ); } } @@ -392,7 +402,10 @@ private static function remove_recipes_from_cooked_user_meta() { */ private static function update_rewrite_rules() { flush_rewrite_rules(); - error_log( 'Cooked: Flushed rewrite rules due to version update.' ); + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + error_log( 'Cooked: Flushed rewrite rules due to version update.' ); + } } } diff --git a/includes/class.cooked-widgets.php b/includes/class.cooked-widgets.php index d4896f3..9d6c8b2 100644 --- a/includes/class.cooked-widgets.php +++ b/includes/class.cooked-widgets.php @@ -37,7 +37,7 @@ public function register_widgets() { public static function recipe_finder( $field_id = '', $field_name = '', $included = '' ) { $button_title = ( ! empty( $included ) ? __( 'Edit Recipe(s)...', 'cooked' ) : __( 'Choose recipe(s)...', 'cooked' ) ); echo ''; - echo ''; if ( ! empty( $included ) ) : foreach ( $included as $recipe ) : $recipe_status = get_post_status( $recipe ); diff --git a/includes/widgets/nutrition.php b/includes/widgets/nutrition.php index e0f2c7e..649d0dd 100644 --- a/includes/widgets/nutrition.php +++ b/includes/widgets/nutrition.php @@ -29,7 +29,7 @@ public function widget( $args, $instance ) { echo wp_kses_post( $args['before_widget'] ); if ( ! empty( $instance['title'] ) ) { - echo wp_kses_post( $args['before_title'] ) . apply_filters( 'widget_title', $instance['title'] ) . wp_kses_post( $args['after_title'] ); + echo wp_kses_post( $args['before_title'] ) . esc_html( apply_filters( 'widget_title', $instance['title'] ) ) . wp_kses_post( $args['after_title'] ); } echo do_shortcode( '[cooked-nutrition]' ); echo wp_kses_post( $args['after_widget'] ); diff --git a/includes/widgets/recipe-card.php b/includes/widgets/recipe-card.php index d4c95fd..7450502 100644 --- a/includes/widgets/recipe-card.php +++ b/includes/widgets/recipe-card.php @@ -39,7 +39,7 @@ public function widget( $args, $instance ) { echo wp_kses_post( $args['before_widget'] ); if ( ! empty( $instance['title'] ) ) { - echo wp_kses_post( $args['before_title'] ) . apply_filters( 'widget_title', $instance['title'] ) . wp_kses_post( $args['after_title'] ); + echo wp_kses_post( $args['before_title'] ) . esc_html( apply_filters( 'widget_title', $instance['title'] ) ) . wp_kses_post( $args['after_title'] ); } echo do_shortcode( '[cooked-recipe-card' . wp_kses_post( $recipe_id . $width . $hide_image . $hide_title . $hide_excerpt . $hide_author . $style ) . ']' ); diff --git a/includes/widgets/recipe-categories.php b/includes/widgets/recipe-categories.php index 16fc186..508d14d 100644 --- a/includes/widgets/recipe-categories.php +++ b/includes/widgets/recipe-categories.php @@ -29,7 +29,7 @@ public function widget( $args, $instance ) { echo wp_kses_post( $args['before_widget'] ); if ( ! empty( $instance['title'] ) ) { - echo wp_kses_post( $args['before_title'] ) . apply_filters( 'widget_title', $instance['title'] ) . wp_kses_post( $args['after_title'] ); + echo wp_kses_post( $args['before_title'] ) . esc_html( apply_filters( 'widget_title', $instance['title'] ) ) . wp_kses_post( $args['after_title'] ); } $width = isset($instance['width']) && $instance['width'] ? ' width="' . esc_attr( $instance['width'] ) . '"' : ''; diff --git a/includes/widgets/recipe-list.php b/includes/widgets/recipe-list.php index b2f2d92..f71c272 100644 --- a/includes/widgets/recipe-list.php +++ b/includes/widgets/recipe-list.php @@ -30,7 +30,7 @@ public function widget( $args, $instance ) { echo wp_kses_post( $args['before_widget'] ); if ( ! empty( $instance['title'] ) ) { - echo wp_kses_post( $args['before_title'] ) . apply_filters( 'widget_title', $instance['title'] ) . wp_kses_post( $args['after_title'] ); + echo wp_kses_post( $args['before_title'] ) . esc_html( apply_filters( 'widget_title', $instance['title'] ) ) . wp_kses_post( $args['after_title'] ); } $recipes = isset($instance['orderby']) && $instance['orderby'] == 'ids' && isset($instance['recipes']) && !empty($instance['recipes']) ? ' recipes="' . implode( ',', $instance['recipes'] ) . '"' : ''; diff --git a/includes/widgets/search.php b/includes/widgets/search.php index ca4e8d6..3a09699 100644 --- a/includes/widgets/search.php +++ b/includes/widgets/search.php @@ -29,7 +29,7 @@ public function widget( $args, $instance ) { echo wp_kses_post( $args['before_widget'] ); if ( ! empty( $instance['title'] ) ) { - echo wp_kses_post( $args['before_title'] ) . apply_filters( 'widget_title', $instance['title'] ) . wp_kses_post( $args['after_title'] ); + echo wp_kses_post( $args['before_title'] ) . esc_html( apply_filters( 'widget_title', $instance['title'] ) ) . wp_kses_post( $args['after_title'] ); } $size = isset($instance['size']) && $instance['size'] == 'compact' ? ' compact="true"' : ''; $browse = isset($instance['hide_browse']) && $instance['hide_browse'] ? ' hide_browse="true"' : ''; diff --git a/templates/admin/import.php b/templates/admin/import.php index b1e7508..b946f21 100644 --- a/templates/admin/import.php +++ b/templates/admin/import.php @@ -71,7 +71,7 @@ endif; echo $conditional_requirement ? '' : ''; - echo ''; + echo ''; echo !$notitle ? '

    ' . wp_kses_post( $field['title'] ) . '

    ' : ''; echo isset($field['desc']) && $field['desc'] ? '

    ' . wp_kses_post( $field['desc'] ). '

    ' : ''; $Cooked_Import->$field_type( $name, $field_options, $color, $field ); @@ -109,7 +109,7 @@ var vm = new Vue({ el: '#cooked-settings-panel', data: { - + } }); diff --git a/templates/admin/pro.php b/templates/admin/pro.php index 69ab98e..f000ef9 100644 --- a/templates/admin/pro.php +++ b/templates/admin/pro.php @@ -2,16 +2,16 @@
    - +

    @@ -45,11 +45,11 @@
    COOKED10', '10%' ); + echo wp_kses_post( sprintf( __( 'Use coupon code %1$s for %2$s off!', 'cooked' ), 'COOKED10', '10%' ) ); ?>
    diff --git a/templates/admin/settings.php b/templates/admin/settings.php index bad6051..9b6410b 100644 --- a/templates/admin/settings.php +++ b/templates/admin/settings.php @@ -76,7 +76,7 @@ endif; echo ( $conditional_requirement ? '' : '' ); - echo ''; + echo ''; echo ( !$notitle ? '

    ' . wp_kses_post( $field['title'] ) . '

    ' : '' ); echo ( isset($field['desc']) && $field['desc'] ? '

    ' . wp_kses_post( $field['desc'] ). '

    ' : '' ); $Cooked_Settings->$field_type( $name, $field_options, $color, $field ); @@ -114,7 +114,7 @@ var vm = new Vue({ el: '#cooked-settings-panel', data: { - + } }); diff --git a/templates/admin/welcome.php b/templates/admin/welcome.php index 356d80e..84e1327 100644 --- a/templates/admin/welcome.php +++ b/templates/admin/welcome.php @@ -2,24 +2,24 @@
    - + @@ -30,14 +30,14 @@
    •   
    •   
    • -
    •   
    • -
    •   
    • -
    •   
    • +
    •   
    • +
    •   
    • +
    •   
    - +
    diff --git a/templates/front/recipe-list.php b/templates/front/recipe-list.php index 88b1763..45b2dd6 100644 --- a/templates/front/recipe-list.php +++ b/templates/front/recipe-list.php @@ -32,11 +32,11 @@ $hide_avatars = ( isset( $_cooked_settings['hide_author_avatars'][0] ) && $_cooked_settings['hide_author_avatars'][0] == 'hidden' ? true : false ); echo '
    '; - echo ( isset($author['profile_photo']) && $author['profile_photo'] ? ( !$hide_avatars ? '' . esc_html( $author['profile_photo'] ) . '' : '' ) : '' ); + echo ( isset($author['profile_photo']) && $author['profile_photo'] ? ( !$hide_avatars ? '' . wp_kses_post( $author['profile_photo'] ) . '' : '' ) : '' ); /* translators: referring to the author (ex: Recipes by John Smith) */ - echo '' . sprintf( __('Recipes by %s','cooked'), $author['name'] ) . ''; + echo '' . sprintf( esc_html__('Recipes by %s','cooked'), esc_html( $author['name'] ) ) . ''; $browse_page_id = Cooked_Multilingual::get_browse_page_id(); - echo ( $browse_page_id ? '
    ' . __( 'View all recipes','cooked' ) . '' : '' ); + echo ( $browse_page_id ? '
    ' . esc_html__( 'View all recipes','cooked' ) . '' : '' ); echo '
    '; elseif ( $atts['search'] === 'true' ): @@ -97,7 +97,7 @@ echo '
    '; if ( $atts['pagination'] === 'true' ): - echo Cooked_Recipes::pagination( $recipes['raw'], $recipe_args ); + echo wp_kses_post( Cooked_Recipes::pagination( $recipes['raw'], $recipe_args ) ); endif; wp_enqueue_script( 'cooked-appear' ); diff --git a/templates/front/recipe-print.php b/templates/front/recipe-print.php index 1918362..e09ba3d 100644 --- a/templates/front/recipe-print.php +++ b/templates/front/recipe-print.php @@ -27,7 +27,7 @@ function cooked_print_enqueues() { Cooked_Functions::print_options(); echo '

    ' . esc_html( get_the_title() ) . '

    '; -echo wpautop( do_shortcode( Cooked_Recipes::print_content() ) ); +echo wp_kses_post( wpautop( do_shortcode( Cooked_Recipes::print_content() ) ) ); Cooked_Functions::print_options_js(); diff --git a/templates/front/recipe-single.php b/templates/front/recipe-single.php index 8000431..ec340cf 100644 --- a/templates/front/recipe-single.php +++ b/templates/front/recipe-single.php @@ -16,13 +16,13 @@ $recipe_classes = []; } -echo '
    '; +echo '
    '; do_action( 'cooked_recipe_grid_before_recipe', $recipe ); do_action( 'cooked_recipe_grid_before_image', $recipe ); - echo has_post_thumbnail( $recipe['id'] ) ? '' : ''; + echo has_post_thumbnail( $recipe['id'] ) ? '' : ''; do_action( 'cooked_recipe_grid_after_image', $recipe ); @@ -44,7 +44,7 @@ echo ''; $author = $recipe['author']; /* translators: referring to the author (ex: By John Smith) */ - echo sprintf( __( 'By %s', 'cooked' ), '' . $author['name'] . '' ); + echo wp_kses_post( sprintf( __( 'By %s', 'cooked' ), '' . esc_html( $author['name'] ) . '' ) ); echo ''; endif; diff --git a/templates/front/recipe.php b/templates/front/recipe.php index 65c99a9..139a3f8 100644 --- a/templates/front/recipe.php +++ b/templates/front/recipe.php @@ -30,10 +30,12 @@ global $wp_embed; $recipe_content = $wp_embed->autoembed( $recipe_content ); $recipe_content .= Cooked_Recipes::get_fsm_markup( $recipe_id, $recipe_settings ); - - $recipe_content .= isset($recipe_seo_content) ? $recipe_seo_content : ''; else: $recipe_content = strip_shortcodes( $recipe_content ); endif; -echo apply_filters( 'cooked_recipe_content', $recipe_content, $recipe_id ); +echo wp_kses_post( apply_filters( 'cooked_recipe_content', $recipe_content, $recipe_id ) ); + +if ( ! empty( $recipe_seo_content ) ) { + echo $recipe_seo_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON-LD from wp_json_encode. +} From f9e7d1cf5015be2c59c2332febf7618591355397 Mon Sep 17 00:00:00 2001 From: Armand Tresova Date: Sun, 16 Aug 2026 13:10:25 -0400 Subject: [PATCH 12/36] Plugin Check (PCP) - Fixes PCP Wave 5: Cooked register_setting --- includes/class.cooked-import.php | 8 ++++++-- includes/class.cooked-settings.php | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/includes/class.cooked-import.php b/includes/class.cooked-import.php index 60541f6..33e64b7 100644 --- a/includes/class.cooked-import.php +++ b/includes/class.cooked-import.php @@ -25,8 +25,12 @@ public function __construct() { } public static function init() { - register_setting( 'cooked_import_group', 'cooked_import' ); - register_setting( 'cooked_import_group', 'cooked_import_saved' ); + register_setting( 'cooked_import_group', 'cooked_import', ['sanitize_callback' => [__CLASS__, 'sanitize_saved_flag']] ); + register_setting( 'cooked_import_group', 'cooked_import_saved', ['sanitize_callback' => [__CLASS__, 'sanitize_saved_flag']] ); + } + + public static function sanitize_saved_flag( $value ) { + return rest_sanitize_boolean( $value ); } public static function tabs_fields() { diff --git a/includes/class.cooked-settings.php b/includes/class.cooked-settings.php index fa778c3..e5e4038 100644 --- a/includes/class.cooked-settings.php +++ b/includes/class.cooked-settings.php @@ -56,7 +56,11 @@ public static function init() { $list_id_counter = 0; $_cooked_settings = Cooked_Settings::get(); register_setting( 'cooked_settings_group', 'cooked_settings', ['sanitize_callback' => [__CLASS__, 'sanitize_settings']] ); - register_setting( 'cooked_settings_group', 'cooked_settings_saved' ); + register_setting( 'cooked_settings_group', 'cooked_settings_saved', ['sanitize_callback' => [__CLASS__, 'sanitize_saved_flag']] ); + } + + public static function sanitize_saved_flag( $value ) { + return rest_sanitize_boolean( $value ); } // Add this new method to handle settings sanitization. From db9116f55287023a389cd877450e371df7bd92c0 Mon Sep 17 00:00:00 2001 From: Armand Tresova Date: Sun, 16 Aug 2026 18:05:32 -0400 Subject: [PATCH 13/36] Harden rewrite flushes after updates so browse/taxonomy URLs recover without a Permalinks save. - Always soft-flush rewrite rules on a Cooked or Pro version change - Queue a one-shot flush when stored rules are missing browse page_id mappings - Delete rewrite_rules on deactivation so the next request rebuilds them - Cover version-bump flush, missing-rule queue, and core deactivation in PHPUnit --- includes/class.cooked-post-types.php | 7 ++ includes/class.cooked-updates.php | 103 ++++++++++++++++++++++++++- tests/phpunit/FilterTestCase.php | 5 ++ tests/phpunit/UpdatesTest.php | 79 +++++++++++++++++++- tests/phpunit/bootstrap.php | 22 +++++- 5 files changed, 211 insertions(+), 5 deletions(-) diff --git a/includes/class.cooked-post-types.php b/includes/class.cooked-post-types.php index 3c488e5..168ebb5 100644 --- a/includes/class.cooked-post-types.php +++ b/includes/class.cooked-post-types.php @@ -21,6 +21,7 @@ class Cooked_Post_Types { function __construct() { register_activation_hook( COOKED_PLUGIN_FILE, [&$this, 'activation'] ); + register_deactivation_hook( COOKED_PLUGIN_FILE, [ __CLASS__, 'deactivation' ] ); add_action( 'init', [&$this, 'init'] ); add_filter( 'admin_init', [&$this, 'init_roles'] ); @@ -196,6 +197,10 @@ public static function activation() { flush_rewrite_rules(); } + public static function deactivation() { + delete_option( 'rewrite_rules' ); + } + public static function init_roles() { // Clean up for any old caps or caps that were inserted incorrectly. if ( $role_object = get_role( 'subscriber' ) ) { @@ -300,6 +305,8 @@ public static function init() { register_post_type( $slug, $args ); } } + + Cooked_Updates::maybe_queue_rewrite_flush(); } /** diff --git a/includes/class.cooked-updates.php b/includes/class.cooked-updates.php index 1a3fc90..a655a85 100644 --- a/includes/class.cooked-updates.php +++ b/includes/class.cooked-updates.php @@ -45,6 +45,13 @@ class Cooked_Updates { */ private static $cooked_settings_saved; + /** + * Whether rewrite rules were already flushed this request. + * + * @var bool + */ + private static $rewrite_rules_flushed = false; + /** * Initialize the updates system */ @@ -78,6 +85,8 @@ public static function init() { if ( self::needs_update() ) { self::run_updates(); } + + self::maybe_flush_queued_rewrite_rules(); } /** @@ -108,6 +117,8 @@ private static function run_updates() { // Run version-specific updates self::run_version_updates(); + self::update_rewrite_rules(); + // Update both version numbers. update_option( 'cooked_settings_version', self::$current_version ); if ( defined('COOKED_PRO_VERSION') ) { @@ -395,13 +406,103 @@ private static function remove_recipes_from_cooked_user_meta() { } } + /** + * Queue a one-shot rewrite flush when browse-page pretty permalinks are missing + * from the stored rewrite_rules option. + * + * @since 1.16.0 + * @return void + */ + public static function maybe_queue_rewrite_flush() { + if ( self::browse_rewrite_rules_missing() ) { + update_option( 'cooked_flush_rewrite_rules', '1' ); + } + } + + /** + * Whether stored rewrite rules are missing Cooked browse-page mappings. + * + * @since 1.16.0 + * @return bool + */ + public static function browse_rewrite_rules_missing() { + if ( function_exists( 'wp_installing' ) && wp_installing() ) { + return false; + } + + if ( ! get_option( 'permalink_structure' ) ) { + return false; + } + + $browse_pages = Cooked_Multilingual::get_all_browse_pages(); + if ( empty( $browse_pages ) ) { + return false; + } + + $rules = get_option( 'rewrite_rules' ); + if ( ! is_array( $rules ) || empty( $rules ) ) { + return true; + } + + foreach ( $browse_pages as $page_data ) { + $page_id = isset( $page_data['id'] ) ? (int) $page_data['id'] : 0; + if ( ! $page_id ) { + continue; + } + + $found = false; + foreach ( $rules as $query ) { + if ( is_string( $query ) && preg_match( '/[?&]page_id=' . $page_id . '(?:&|$)/', $query ) ) { + $found = true; + break; + } + } + + if ( ! $found ) { + return true; + } + } + + return false; + } + + /** + * Soft-flush rewrite rules when a previous request queued it. + * + * @since 1.16.0 + * @return void + */ + public static function maybe_flush_queued_rewrite_rules() { + if ( ! get_option( 'cooked_flush_rewrite_rules' ) ) { + return; + } + + self::update_rewrite_rules(); + delete_option( 'cooked_flush_rewrite_rules' ); + } + + /** + * Reset per-request flush state. Used by tests. + * + * @since 1.16.0 + * @return void + */ + public static function reset_rewrite_flush_state() { + self::$rewrite_rules_flushed = false; + } + /** * Update rewrite rules if needed * * @since 1.11.2 */ private static function update_rewrite_rules() { - flush_rewrite_rules(); + if ( self::$rewrite_rules_flushed ) { + return; + } + + flush_rewrite_rules( false ); + self::$rewrite_rules_flushed = true; if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log( 'Cooked: Flushed rewrite rules due to version update.' ); diff --git a/tests/phpunit/FilterTestCase.php b/tests/phpunit/FilterTestCase.php index 68ff363..ca4b0f0 100644 --- a/tests/phpunit/FilterTestCase.php +++ b/tests/phpunit/FilterTestCase.php @@ -54,6 +54,11 @@ protected function reset_filter_test_state() { unset( $GLOBALS['_cooked_test_logged_in'] ); unset( $GLOBALS['cooked_modified_where'] ); $GLOBALS['_cooked_settings'] = []; + $GLOBALS['_cooked_test_flush_rewrite_count'] = 0; + $GLOBALS['_cooked_test_flush_rewrite_hard'] = []; + if ( class_exists( 'Cooked_Updates' ) ) { + Cooked_Updates::reset_rewrite_flush_state(); + } } protected function with_filter( $tag, $callback, $run, $accepted_args = 10 ) { diff --git a/tests/phpunit/UpdatesTest.php b/tests/phpunit/UpdatesTest.php index b346bc4..b9a5e95 100644 --- a/tests/phpunit/UpdatesTest.php +++ b/tests/phpunit/UpdatesTest.php @@ -1,8 +1,6 @@ assertInstanceOf(WP_Error::class, $result); } + + public function test_version_bump_flushes_rewrite_rules() { + $GLOBALS['_cooked_test_options']['cooked_settings_saved'] = true; + $GLOBALS['_cooked_test_options']['cooked_settings_version'] = '1.15.0'; + + Cooked_Updates::init(); + + $this->assertSame( 1, $GLOBALS['_cooked_test_flush_rewrite_count'] ); + $this->assertContains( false, $GLOBALS['_cooked_test_flush_rewrite_hard'] ); + $this->assertSame( COOKED_VERSION, get_option( 'cooked_settings_version' ) ); + } + + public function test_matching_version_does_not_flush_rewrite_rules() { + $GLOBALS['_cooked_test_options']['cooked_settings_saved'] = true; + $GLOBALS['_cooked_test_options']['cooked_settings_version'] = COOKED_VERSION; + + Cooked_Updates::init(); + + $this->assertSame( 0, $GLOBALS['_cooked_test_flush_rewrite_count'] ); + } + + public function test_missing_browse_rewrite_rules_queue_a_flush() { + $GLOBALS['_cooked_settings'] = [ 'browse_page' => 66 ]; + $GLOBALS['_cooked_test_options']['permalink_structure'] = '/%postname%/'; + $GLOBALS['_cooked_test_options']['rewrite_rules'] = [ + 'foo/([^/]+)/?$' => 'index.php?name=$matches[1]', + ]; + + $this->assertTrue( Cooked_Updates::browse_rewrite_rules_missing() ); + + Cooked_Updates::maybe_queue_rewrite_flush(); + $this->assertSame( '1', get_option( 'cooked_flush_rewrite_rules' ) ); + + Cooked_Updates::maybe_flush_queued_rewrite_rules(); + $this->assertSame( 1, $GLOBALS['_cooked_test_flush_rewrite_count'] ); + $this->assertFalse( get_option( 'cooked_flush_rewrite_rules' ) ); + } + + public function test_present_browse_rewrite_rules_do_not_queue_a_flush() { + $GLOBALS['_cooked_settings'] = [ 'browse_page' => 66 ]; + $GLOBALS['_cooked_test_options']['permalink_structure'] = '/%postname%/'; + $GLOBALS['_cooked_test_options']['rewrite_rules'] = [ + 'recipes/recipe-category/([^/]*)/?' => 'index.php?page_id=66&cp_recipe_category=$matches[1]', + ]; + + $this->assertFalse( Cooked_Updates::browse_rewrite_rules_missing() ); + + Cooked_Updates::maybe_queue_rewrite_flush(); + $this->assertFalse( get_option( 'cooked_flush_rewrite_rules' ) ); + } + + public function test_plain_permalinks_do_not_queue_a_flush() { + $GLOBALS['_cooked_settings'] = [ 'browse_page' => 66 ]; + $GLOBALS['_cooked_test_options']['permalink_structure'] = ''; + + $this->assertFalse( Cooked_Updates::browse_rewrite_rules_missing() ); + Cooked_Updates::maybe_queue_rewrite_flush(); + $this->assertFalse( get_option( 'cooked_flush_rewrite_rules' ) ); + } + + public function test_no_browse_page_does_not_queue_a_flush() { + $GLOBALS['_cooked_settings'] = []; + $GLOBALS['_cooked_test_options']['permalink_structure'] = '/%postname%/'; + + $this->assertFalse( Cooked_Updates::browse_rewrite_rules_missing() ); + } + + public function test_core_deactivation_deletes_rewrite_rules() { + require_once COOKED_DIR . 'includes/class.cooked-post-types.php'; + $GLOBALS['_cooked_test_options']['rewrite_rules'] = [ 'keep' => 'me' ]; + + Cooked_Post_Types::deactivation(); + + $this->assertFalse( get_option( 'rewrite_rules' ) ); + } } diff --git a/tests/phpunit/bootstrap.php b/tests/phpunit/bootstrap.php index 4b2cf1e..3920c9d 100644 --- a/tests/phpunit/bootstrap.php +++ b/tests/phpunit/bootstrap.php @@ -164,6 +164,18 @@ function load_plugin_textdomain( $domain, $deprecated, $plugin_rel_path ) { retu function wpautop( $pee, $br = true ) { return $pee; } function make_clickable( $text ) { return $text; } function wp_unslash( $value ) { return is_string( $value ) ? stripslashes( $value ) : $value; } +function rest_sanitize_boolean( $value ) { + if ( is_bool( $value ) ) { + return $value; + } + if ( is_string( $value ) ) { + $value = strtolower( $value ); + if ( in_array( $value, [ 'false', '0', 'no', 'off', '' ], true ) ) { + return false; + } + } + return (bool) $value; +} /** * Shortcode stubs @@ -460,7 +472,13 @@ function register_uninstall_hook( $file, $callback ) {} /** * Flush rewrite rules stub */ -function flush_rewrite_rules( $hard = true ) {} +$GLOBALS['_cooked_test_flush_rewrite_count'] = 0; +$GLOBALS['_cooked_test_flush_rewrite_hard'] = []; + +function flush_rewrite_rules( $hard = true ) { + $GLOBALS['_cooked_test_flush_rewrite_count']++; + $GLOBALS['_cooked_test_flush_rewrite_hard'][] = $hard; +} /** * Additional WP stubs needed by various tests @@ -740,6 +758,8 @@ function remove_role( $role ) { function register_activation_hook( $file, $callback ) {} +function register_deactivation_hook( $file, $callback ) {} + function add_image_size( $name, $width = 0, $height = 0, $crop = false ) { return true; } From 4f44531b10d710ff6fb0af019c76225b92a90706 Mon Sep 17 00:00:00 2001 From: Armand Tresova Date: Sun, 16 Aug 2026 18:53:31 -0400 Subject: [PATCH 14/36] Rebuild rewrite rules from whatever is registered this request, not only browse page IDs. - Compare extra_rules_top/extra_rules against the stored rewrite_rules option - Soft-flush late on init so Pro profile, taxonomy, and other add_rewrite_rule mappings recover too - Stop queueing that check from post-type init, which ran before Pro rules existed --- includes/class.cooked-updates.php | 74 +++++++++++++++++------- tests/phpunit/FilterTestCase.php | 1 + tests/phpunit/UpdatesTest.php | 95 +++++++++++++++++++++++++++---- tests/phpunit/bootstrap.php | 11 +++- 4 files changed, 147 insertions(+), 34 deletions(-) diff --git a/includes/class.cooked-updates.php b/includes/class.cooked-updates.php index a655a85..6dcad4c 100644 --- a/includes/class.cooked-updates.php +++ b/includes/class.cooked-updates.php @@ -58,6 +58,7 @@ class Cooked_Updates { public function __construct() { // Add action to check version and update settings at the end of page load. add_action( 'shutdown', [__CLASS__, 'init'] ); + add_action( 'init', [ __CLASS__, 'maybe_heal_rewrite_rules' ], 99 ); } /** @@ -407,25 +408,39 @@ private static function remove_recipes_from_cooked_user_meta() { } /** - * Queue a one-shot rewrite flush when browse-page pretty permalinks are missing + * Soft-flush rewrite rules when any rule registered this request is missing + * from the stored rewrite_rules option. + * + * @since 1.16.0 + * @return void + */ + public static function maybe_heal_rewrite_rules() { + if ( self::registered_rewrite_rules_missing() ) { + self::update_rewrite_rules(); + } + } + + /** + * Queue a one-shot rewrite flush when registered rewrite rules are missing * from the stored rewrite_rules option. * * @since 1.16.0 * @return void */ public static function maybe_queue_rewrite_flush() { - if ( self::browse_rewrite_rules_missing() ) { + if ( self::registered_rewrite_rules_missing() ) { update_option( 'cooked_flush_rewrite_rules', '1' ); } } /** - * Whether stored rewrite rules are missing Cooked browse-page mappings. + * Whether the stored rewrite_rules option is missing any rewrite rule + * registered this request via add_rewrite_rule(). * * @since 1.16.0 * @return bool */ - public static function browse_rewrite_rules_missing() { + public static function registered_rewrite_rules_missing() { if ( function_exists( 'wp_installing' ) && wp_installing() ) { return false; } @@ -434,38 +449,57 @@ public static function browse_rewrite_rules_missing() { return false; } - $browse_pages = Cooked_Multilingual::get_all_browse_pages(); - if ( empty( $browse_pages ) ) { + $registered = self::registered_extra_rewrite_rules(); + if ( empty( $registered ) ) { return false; } - $rules = get_option( 'rewrite_rules' ); - if ( ! is_array( $rules ) || empty( $rules ) ) { + $stored = get_option( 'rewrite_rules' ); + if ( ! is_array( $stored ) || empty( $stored ) ) { return true; } - foreach ( $browse_pages as $page_data ) { - $page_id = isset( $page_data['id'] ) ? (int) $page_data['id'] : 0; - if ( ! $page_id ) { + foreach ( $registered as $regex => $query ) { + if ( array_key_exists( $regex, $stored ) ) { continue; } - $found = false; - foreach ( $rules as $query ) { - if ( is_string( $query ) && preg_match( '/[?&]page_id=' . $page_id . '(?:&|$)/', $query ) ) { - $found = true; - break; - } + if ( is_string( $query ) && in_array( $query, $stored, true ) ) { + continue; } - if ( ! $found ) { - return true; - } + return true; } return false; } + /** + * Rewrite rules added this request with add_rewrite_rule(). + * + * @since 1.16.0 + * @return array + */ + private static function registered_extra_rewrite_rules() { + global $wp_rewrite; + + if ( ! is_object( $wp_rewrite ) ) { + return []; + } + + $rules = []; + + if ( ! empty( $wp_rewrite->extra_rules_top ) && is_array( $wp_rewrite->extra_rules_top ) ) { + $rules = $wp_rewrite->extra_rules_top; + } + + if ( ! empty( $wp_rewrite->extra_rules ) && is_array( $wp_rewrite->extra_rules ) ) { + $rules = array_merge( $rules, $wp_rewrite->extra_rules ); + } + + return $rules; + } + /** * Soft-flush rewrite rules when a previous request queued it. * diff --git a/tests/phpunit/FilterTestCase.php b/tests/phpunit/FilterTestCase.php index ca4b0f0..12a17f1 100644 --- a/tests/phpunit/FilterTestCase.php +++ b/tests/phpunit/FilterTestCase.php @@ -53,6 +53,7 @@ protected function reset_filter_test_state() { unset( $GLOBALS['_cooked_test_is_page'] ); unset( $GLOBALS['_cooked_test_logged_in'] ); unset( $GLOBALS['cooked_modified_where'] ); + unset( $GLOBALS['wp_rewrite'] ); $GLOBALS['_cooked_settings'] = []; $GLOBALS['_cooked_test_flush_rewrite_count'] = 0; $GLOBALS['_cooked_test_flush_rewrite_hard'] = []; diff --git a/tests/phpunit/UpdatesTest.php b/tests/phpunit/UpdatesTest.php index b9a5e95..27a20ab 100644 --- a/tests/phpunit/UpdatesTest.php +++ b/tests/phpunit/UpdatesTest.php @@ -50,14 +50,20 @@ public function test_matching_version_does_not_flush_rewrite_rules() { $this->assertSame( 0, $GLOBALS['_cooked_test_flush_rewrite_count'] ); } - public function test_missing_browse_rewrite_rules_queue_a_flush() { - $GLOBALS['_cooked_settings'] = [ 'browse_page' => 66 ]; + public function test_missing_registered_rewrite_rules_queue_a_flush() { $GLOBALS['_cooked_test_options']['permalink_structure'] = '/%postname%/'; + $GLOBALS['wp_rewrite'] = (object) [ + 'extra_rules_top' => [ + 'recipes/recipe-category/([^/]*)/?' => 'index.php?page_id=66&cp_recipe_category=$matches[1]', + 'profile/([^/]*)/?' => 'index.php?page_id=10&cooked_author=$matches[1]', + ], + 'extra_rules' => [], + ]; $GLOBALS['_cooked_test_options']['rewrite_rules'] = [ - 'foo/([^/]+)/?$' => 'index.php?name=$matches[1]', + 'recipes/recipe-category/([^/]*)/?' => 'index.php?page_id=66&cp_recipe_category=$matches[1]', ]; - $this->assertTrue( Cooked_Updates::browse_rewrite_rules_missing() ); + $this->assertTrue( Cooked_Updates::registered_rewrite_rules_missing() ); Cooked_Updates::maybe_queue_rewrite_flush(); $this->assertSame( '1', get_option( 'cooked_flush_rewrite_rules' ) ); @@ -67,33 +73,86 @@ public function test_missing_browse_rewrite_rules_queue_a_flush() { $this->assertFalse( get_option( 'cooked_flush_rewrite_rules' ) ); } - public function test_present_browse_rewrite_rules_do_not_queue_a_flush() { - $GLOBALS['_cooked_settings'] = [ 'browse_page' => 66 ]; + public function test_present_registered_rewrite_rules_do_not_queue_a_flush() { $GLOBALS['_cooked_test_options']['permalink_structure'] = '/%postname%/'; + $GLOBALS['wp_rewrite'] = (object) [ + 'extra_rules_top' => [ + 'recipes/recipe-category/([^/]*)/?' => 'index.php?page_id=66&cp_recipe_category=$matches[1]', + 'profile/([^/]*)/?' => 'index.php?page_id=10&cooked_author=$matches[1]', + ], + 'extra_rules' => [], + ]; $GLOBALS['_cooked_test_options']['rewrite_rules'] = [ 'recipes/recipe-category/([^/]*)/?' => 'index.php?page_id=66&cp_recipe_category=$matches[1]', + 'profile/([^/]*)/?' => 'index.php?page_id=10&cooked_author=$matches[1]', ]; - $this->assertFalse( Cooked_Updates::browse_rewrite_rules_missing() ); + $this->assertFalse( Cooked_Updates::registered_rewrite_rules_missing() ); Cooked_Updates::maybe_queue_rewrite_flush(); $this->assertFalse( get_option( 'cooked_flush_rewrite_rules' ) ); } public function test_plain_permalinks_do_not_queue_a_flush() { - $GLOBALS['_cooked_settings'] = [ 'browse_page' => 66 ]; $GLOBALS['_cooked_test_options']['permalink_structure'] = ''; + $GLOBALS['wp_rewrite'] = (object) [ + 'extra_rules_top' => [ + 'profile/([^/]*)/?' => 'index.php?page_id=10&cooked_author=$matches[1]', + ], + 'extra_rules' => [], + ]; - $this->assertFalse( Cooked_Updates::browse_rewrite_rules_missing() ); + $this->assertFalse( Cooked_Updates::registered_rewrite_rules_missing() ); Cooked_Updates::maybe_queue_rewrite_flush(); $this->assertFalse( get_option( 'cooked_flush_rewrite_rules' ) ); } - public function test_no_browse_page_does_not_queue_a_flush() { - $GLOBALS['_cooked_settings'] = []; + public function test_no_registered_rewrite_rules_do_not_queue_a_flush() { $GLOBALS['_cooked_test_options']['permalink_structure'] = '/%postname%/'; + $GLOBALS['wp_rewrite'] = (object) [ + 'extra_rules_top' => [], + 'extra_rules' => [], + ]; + + $this->assertFalse( Cooked_Updates::registered_rewrite_rules_missing() ); + } + + public function test_heal_flushes_when_registered_rewrite_rules_are_missing() { + $GLOBALS['_cooked_test_options']['permalink_structure'] = '/%postname%/'; + $GLOBALS['wp_rewrite'] = (object) [ + 'extra_rules_top' => [ + 'profile/([^/]*)/?' => 'index.php?page_id=10&cooked_author=$matches[1]', + ], + 'extra_rules' => [], + ]; + $GLOBALS['_cooked_test_options']['rewrite_rules'] = [ + 'foo/([^/]+)/?$' => 'index.php?name=$matches[1]', + ]; + + Cooked_Updates::maybe_heal_rewrite_rules(); + + $this->assertSame( 1, $GLOBALS['_cooked_test_flush_rewrite_count'] ); + $this->assertContains( false, $GLOBALS['_cooked_test_flush_rewrite_hard'] ); + } + + public function test_updates_heals_rewrite_rules_late_on_init() { + $GLOBALS['_cooked_test_actions'] = []; - $this->assertFalse( Cooked_Updates::browse_rewrite_rules_missing() ); + new Cooked_Updates(); + + $this->assertNotEmpty( $GLOBALS['_cooked_test_actions']['init'][99] ); + } + + public function test_core_activation_flushes_rewrite_rules() { + require_once COOKED_DIR . 'includes/class.cooked-roles.php'; + require_once COOKED_DIR . 'includes/class.cooked-taxonomies.php'; + require_once COOKED_DIR . 'includes/class.cooked-post-types.php'; + + unset( $GLOBALS['wp_roles'] ); + + Cooked_Post_Types::activation(); + + $this->assertSame( 1, $GLOBALS['_cooked_test_flush_rewrite_count'] ); } public function test_core_deactivation_deletes_rewrite_rules() { @@ -104,4 +163,16 @@ public function test_core_deactivation_deletes_rewrite_rules() { $this->assertFalse( get_option( 'rewrite_rules' ) ); } + + public function test_core_registers_activation_and_deactivation_hooks() { + require_once COOKED_DIR . 'includes/class.cooked-post-types.php'; + + $GLOBALS['_cooked_test_activation_hooks'] = []; + $GLOBALS['_cooked_test_deactivation_hooks'] = []; + + new Cooked_Post_Types(); + + $this->assertContains( COOKED_PLUGIN_FILE, $GLOBALS['_cooked_test_activation_hooks'] ); + $this->assertContains( COOKED_PLUGIN_FILE, $GLOBALS['_cooked_test_deactivation_hooks'] ); + } } diff --git a/tests/phpunit/bootstrap.php b/tests/phpunit/bootstrap.php index 3920c9d..7ce3fc9 100644 --- a/tests/phpunit/bootstrap.php +++ b/tests/phpunit/bootstrap.php @@ -474,6 +474,9 @@ function register_uninstall_hook( $file, $callback ) {} */ $GLOBALS['_cooked_test_flush_rewrite_count'] = 0; $GLOBALS['_cooked_test_flush_rewrite_hard'] = []; +$GLOBALS['_cooked_test_activation_hooks'] = []; +$GLOBALS['_cooked_test_deactivation_hooks'] = []; +$GLOBALS['_cooked_test_flush_rewrite_hard'] = []; function flush_rewrite_rules( $hard = true ) { $GLOBALS['_cooked_test_flush_rewrite_count']++; @@ -756,9 +759,13 @@ function remove_role( $role ) { return true; } -function register_activation_hook( $file, $callback ) {} +function register_activation_hook( $file, $callback ) { + $GLOBALS['_cooked_test_activation_hooks'][] = $file; +} -function register_deactivation_hook( $file, $callback ) {} +function register_deactivation_hook( $file, $callback ) { + $GLOBALS['_cooked_test_deactivation_hooks'][] = $file; +} function add_image_size( $name, $width = 0, $height = 0, $crop = false ) { return true; From 927118cac127da270fce7d5ccbcfaa68ea44325d Mon Sep 17 00:00:00 2001 From: Armand Tresova Date: Sun, 16 Aug 2026 19:00:05 -0400 Subject: [PATCH 15/36] Plugin Check (PCP) - Fixes PCP Wave 6: Cooked display GET + recipe-meta --- includes/class.cooked-enqueues.php | 2 ++ includes/class.cooked-multilingual.php | 4 +++- includes/class.cooked-post-types.php | 10 +++++++--- includes/class.cooked-recipe-meta.php | 5 +++-- includes/class.cooked-recipes.php | 14 ++++++++++---- includes/class.cooked-settings.php | 11 +++++++++-- includes/class.cooked-shortcodes.php | 6 ++++-- 7 files changed, 38 insertions(+), 14 deletions(-) diff --git a/includes/class.cooked-enqueues.php b/includes/class.cooked-enqueues.php index 142686d..2f9d67e 100644 --- a/includes/class.cooked-enqueues.php +++ b/includes/class.cooked-enqueues.php @@ -78,6 +78,7 @@ public function enqueues($hook) { } public function css_colors() { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only print view flag. if (!isset($_GET['print'])) { $file = COOKED_DIR . 'assets/css/colors.php'; $css = self::get_dynamic_css($file); @@ -86,6 +87,7 @@ public function css_colors() { } public function css_responsive() { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only print view flag. if (!isset($_GET['print'])) { $file = COOKED_DIR . 'assets/css/responsive.php'; $css = self::get_dynamic_css($file); diff --git a/includes/class.cooked-multilingual.php b/includes/class.cooked-multilingual.php index 7afaa38..3f4cfef 100644 --- a/includes/class.cooked-multilingual.php +++ b/includes/class.cooked-multilingual.php @@ -234,7 +234,9 @@ public static function get_missing_translations() { */ public function translation_notice() { // Only show on Cooked settings page - if ( ! isset( $_GET['page'] ) || $_GET['page'] !== 'cooked_settings' ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only admin page query var. + $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; + if ( $page !== 'cooked_settings' ) { return; } diff --git a/includes/class.cooked-post-types.php b/includes/class.cooked-post-types.php index 168ebb5..c5e8fa5 100644 --- a/includes/class.cooked-post-types.php +++ b/includes/class.cooked-post-types.php @@ -217,8 +217,13 @@ public static function init() { $_cooked_settings = Cooked_Settings::get(); $_cooked_taxonomies = Cooked_Taxonomies::get(); + // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Display-only Settings API query vars. + $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; + $settings_updated = isset( $_GET['settings-updated'] ) ? rest_sanitize_boolean( wp_unslash( $_GET['settings-updated'] ) ) : false; + // phpcs:enable WordPress.Security.NonceVerification.Recommended + // Security check: Only allow settings update from admin area with proper permissions - if (!empty($_GET['settings-updated']) && is_admin() && current_user_can('manage_options') && isset($_GET['page']) && $_GET['page'] === 'cooked_settings') { + if ( $settings_updated && is_admin() && current_user_can('manage_options') && $page === 'cooked_settings' ) { // Recipe Permalink $permalink_parts = explode( '/', $_cooked_settings['recipe_permalink'] ); if ( isset( $permalink_parts[1] ) ): @@ -305,8 +310,6 @@ public static function init() { register_post_type( $slug, $args ); } } - - Cooked_Updates::maybe_queue_rewrite_flush(); } /** @@ -446,6 +449,7 @@ public static function get() { $has_archive_slug = sanitize_title_with_dashes( __('Recipe Archive', 'cooked') ); $exclude_from_search = false; + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only print view flag. if ( !isset($_GET['print']) && isset( $_cooked_settings['advanced'] ) && in_array( 'disable_public_recipes', $_cooked_settings['advanced'] ) ) { $public_recipes = false; $has_archive_slug = false; diff --git a/includes/class.cooked-recipe-meta.php b/includes/class.cooked-recipe-meta.php index 3eba350..a2b5e1a 100644 --- a/includes/class.cooked-recipe-meta.php +++ b/includes/class.cooked-recipe-meta.php @@ -134,8 +134,8 @@ public function save_recipe_meta_box( $post_id ) { // Check if our nonce is set. if ( !isset( $_POST['cooked_recipe_custom_box_nonce'] ) ) return $post_id; - // Verify that the nonce is valid. - if ( ! wp_verify_nonce( $_POST['cooked_recipe_custom_box_nonce'], 'cooked_recipe_custom_box' ) ) return $post_id; + $nonce = wp_unslash( $_POST['cooked_recipe_custom_box_nonce'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verify nonce after unslash; do not sanitize_text_field a nonce. + if ( ! wp_verify_nonce( $nonce, 'cooked_recipe_custom_box' ) ) return $post_id; /* * If this is an autosave, our form has not been submitted, @@ -149,6 +149,7 @@ public function save_recipe_meta_box( $post_id ) { global $recipe_settings; /* OK, it's safe for us to validate/sanitize the data now. */ + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized in meta_cleanup(). $recipe_settings = isset($_POST['_recipe_settings']) ? self::meta_cleanup( wp_unslash( $_POST['_recipe_settings'] ) ) : []; if ( isset( $recipe_settings['content'] ) ) { diff --git a/includes/class.cooked-recipes.php b/includes/class.cooked-recipes.php index 9eae424..1697fd2 100644 --- a/includes/class.cooked-recipes.php +++ b/includes/class.cooked-recipes.php @@ -222,7 +222,9 @@ public function check_recipe_query() { global $_cooked_settings, $recipe_query; if ( !isset($recipe_query['cp_recipe_category']) ): - $recipe_query['cp_recipe_category'] = ( isset($_GET['cp_recipe_category']) && $_GET['cp_recipe_category'] ? intval($_GET['cp_recipe_category']) : ( isset($_cooked_settings['browse_default_cp_recipe_category']) && $_cooked_settings['browse_default_cp_recipe_category'] ? $_cooked_settings['browse_default_cp_recipe_category'] : false ) ); + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only browse category filter. + $requested_category = isset( $_GET['cp_recipe_category'] ) ? absint( wp_unslash( $_GET['cp_recipe_category'] ) ) : 0; + $recipe_query['cp_recipe_category'] = $requested_category ? $requested_category : ( isset($_cooked_settings['browse_default_cp_recipe_category']) && $_cooked_settings['browse_default_cp_recipe_category'] ? $_cooked_settings['browse_default_cp_recipe_category'] : false ); endif; } @@ -373,6 +375,7 @@ public static function card( $rid, $width = false, $hide_image = false, $hide_ti } public function print_recipe_template() { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only print view flag. if ( is_singular('cp_recipe') && isset($_GET['print']) ): load_template( COOKED_DIR . 'templates/front/recipe-print.php', false); exit; @@ -437,7 +440,8 @@ public function filter_recipes_by_taxonomy() { foreach ( $taxonomies as $taxonomy ): if ( is_array($cooked_taxonomies_shown) && !in_array( $taxonomy, $cooked_taxonomies_shown ) || !is_array($cooked_taxonomies_shown) ): $cooked_taxonomies_shown[] = $taxonomy; - $selected = isset($_GET[$taxonomy]) ? sanitize_title($_GET[$taxonomy]) : ''; + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only admin taxonomy filter. + $selected = isset( $_GET[ $taxonomy ] ) ? absint( wp_unslash( $_GET[ $taxonomy ] ) ) : ''; $info_taxonomy = get_taxonomy($taxonomy); $taxonomy_label = $info_taxonomy->label; @@ -927,6 +931,7 @@ public static function gallery_types() { public static function measurement_system_switcher() { global $_cooked_settings, $post; $switcher_enabled = ( isset( $_cooked_settings['advanced'] ) && in_array( 'enable_measurement_switcher', $_cooked_settings['advanced'] ) ? true : false ); + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only print view flag. $printing = ( is_singular('cp_recipe') && isset($_GET['print']) ); if ( !$printing && $switcher_enabled ): @@ -953,6 +958,7 @@ public static function measurement_system_switcher() { public static function serving_size_switcher( $servings ) { global $_cooked_settings, $post; $switcher_disabled = ( isset( $_cooked_settings['advanced'] ) && in_array( 'disable_servings_switcher', $_cooked_settings['advanced'] ) ? true : false ); + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only print view flag. $printing = ( is_singular('cp_recipe') && isset($_GET['print']) ); if ( !$printing && !$switcher_disabled ): @@ -1288,7 +1294,7 @@ public static function recipe_search_box( $options = false ) { $term = get_term( $key ); $term_link = ( !empty($term) ? get_term_link( $term ) : false ); $term_name = apply_filters( 'cooked_term_name', $term->name, $term->ID, $term->taxonomy ); - echo ( $term_link ? ( isset($active_taxonomy) && $active_taxonomy == $val ? '  ' : '' ) . '' . esc_html($term_name) . '' . ( isset($active_taxonomy) && $active_taxonomy == $val ? '' : '' ) : '' ); + echo ( $term_link ? ( isset($active_taxonomy) && $active_taxonomy == $val ? '  ' : '' ) . '' . wp_kses_post($term_name) . '' . ( isset($active_taxonomy) && $active_taxonomy == $val ? '' : '' ) : '' ); $total_taxonomies++; $sub_terms_array = Cooked_Settings::terms_array( 'cp_recipe_category', false, false, true, false, $key ); if ( !empty($sub_terms_array) ): @@ -1297,7 +1303,7 @@ public static function recipe_search_box( $options = false ) { $sub_term = get_term( $sub_key ); $sub_term_link = ( !empty($sub_term) ? get_term_link( $sub_term ) : false ); $sub_term_name = apply_filters( 'cooked_term_name', $sub_term->name, $sub_term->ID, $sub_term->taxonomy ); - echo ( $sub_term_link ? '' . ( isset($active_taxonomy) && $active_taxonomy == $sub_val ? '  ' : '' ) . '' . esc_html($sub_term_name) . '' . ( isset($active_taxonomy) && $active_taxonomy == $sub_val ? '' : '' ) . '' : '' ); + echo ( $sub_term_link ? '' . ( isset($active_taxonomy) && $active_taxonomy == $sub_val ? '  ' : '' ) . '' . wp_kses_post($sub_term_name) . '' . ( isset($active_taxonomy) && $active_taxonomy == $sub_val ? '' : '' ) . '' : '' ); $total_taxonomies++; endif; endforeach; diff --git a/includes/class.cooked-settings.php b/includes/class.cooked-settings.php index e5e4038..f6294b4 100644 --- a/includes/class.cooked-settings.php +++ b/includes/class.cooked-settings.php @@ -107,7 +107,12 @@ public static function sanitize_settings($settings) { } function cooked_settings_saved_admin_notice() { - if (isset($_GET['settings-updated']) && $_GET['settings-updated'] && isset($_GET['page']) && $_GET['page'] === 'cooked_settings') { + // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Display-only Settings API query vars. + $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; + $settings_updated = isset( $_GET['settings-updated'] ) ? rest_sanitize_boolean( wp_unslash( $_GET['settings-updated'] ) ) : false; + // phpcs:enable WordPress.Security.NonceVerification.Recommended + + if ( $settings_updated && $page === 'cooked_settings' ) { add_settings_error( 'cooked_settings_group', 'cooked_settings_updated', @@ -119,7 +124,9 @@ function cooked_settings_saved_admin_notice() { function browse_page_missing_notice() { // Only show on admin pages, not on the Cooked settings page itself - if ( isset($_GET['page']) && $_GET['page'] === 'cooked_settings' ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only admin page query var. + $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; + if ( $page === 'cooked_settings' ) { return; } diff --git a/includes/class.cooked-shortcodes.php b/includes/class.cooked-shortcodes.php index 1fc474b..520ab3d 100644 --- a/includes/class.cooked-shortcodes.php +++ b/includes/class.cooked-shortcodes.php @@ -170,9 +170,11 @@ public function cooked_browse_shortcode( $sc_atts, $content = null ) { if ( is_admin() ) return false; $author_query_var = sanitize_key( get_query_var( 'recipe_author', false ) ); - if ( !$author_query_var && isset( $_GET['recipe_author'] ) ) { - $author_query_var = sanitize_key( $_GET['recipe_author'] ); + // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Display-only browse author filter. + if ( ! $author_query_var && isset( $_GET['recipe_author'] ) ) { + $author_query_var = sanitize_key( wp_unslash( $_GET['recipe_author'] ) ); } + // phpcs:enable WordPress.Security.NonceVerification.Recommended // Shortcode Attributes $atts = shortcode_atts( apply_filters( 'cooked_browse_shortcode_default_attributes', [ From d0d79f05de49f8c5254543caa8e308351570b1f1 Mon Sep 17 00:00:00 2001 From: Armand Tresova Date: Sun, 16 Aug 2026 19:30:29 -0400 Subject: [PATCH 16/36] Added test for Settings --- tests/playwright/tests/admin/settings.spec.ts | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 tests/playwright/tests/admin/settings.spec.ts diff --git a/tests/playwright/tests/admin/settings.spec.ts b/tests/playwright/tests/admin/settings.spec.ts new file mode 100644 index 0000000..e5155af --- /dev/null +++ b/tests/playwright/tests/admin/settings.spec.ts @@ -0,0 +1,109 @@ +import { type Page } from '@playwright/test'; +import { test, expect } from '../../utils/fixtures'; +import { getCookedSettings, setCookedSettings } from '../../utils/wp-cli'; + +test.describe.configure({ mode: 'serial' }); + +let originalSettings: Record = {}; + +async function gotoSettings(page: Page, hash: string): Promise { + await page.goto(`/wp-admin/admin.php?page=cooked_settings#${hash}`, { + waitUntil: 'networkidle', + }); + await page.locator(`#cooked-settings-tab-${hash} a`).click(); + await expect(page.locator(`#cooked-settings-tab-content-${hash}`)).toBeVisible(); +} + +async function saveSettings(page: Page): Promise { + await page.getByRole('button', { name: 'Update Settings' }).first().click(); + await expect(page.getByText('Cooked settings has been updated!')).toBeVisible(); + await expect(page.locator('#cooked-settings-panel .notice-error')).toHaveCount(0); + await expect(page.getByRole('heading', { name: /There has been a critical error/i })).toHaveCount(0); + await expect(page).toHaveURL(/page=cooked_settings/); +} + +async function clickSwitch(page: Page, checkboxId: string): Promise { + await page.locator(`#${checkboxId} + .switchery`).click(); +} + +function settingList(settings: Record, key: string): string[] { + const value = settings[key]; + return Array.isArray(value) ? value.map(String) : []; +} + +test.beforeAll(() => { + originalSettings = getCookedSettings(); +}); + +test.describe('Cooked settings page', () => { + test('saves without changes', async ({ adminContext }) => { + const adminPage = await adminContext.newPage(); + await gotoSettings(adminPage, 'recipe_settings'); + + await saveSettings(adminPage); + }); + + test('persists a select field', async ({ adminContext }) => { + const adminPage = await adminContext.newPage(); + await gotoSettings(adminPage, 'recipe_settings'); + + const select = adminPage.locator('select[name="cooked_settings[carb_format]"]'); + const current = await select.inputValue(); + const next = current === 'net' ? 'total' : 'net'; + + await select.selectOption(next); + await saveSettings(adminPage); + + await gotoSettings(adminPage, 'recipe_settings'); + await expect(adminPage.locator('select[name="cooked_settings[carb_format]"]')).toHaveValue(next); + expect(getCookedSettings().carb_format).toBe(next); + }); + + test('persists a checkbox toggle', async ({ adminContext }) => { + const adminPage = await adminContext.newPage(); + await gotoSettings(adminPage, 'recipe_settings'); + + const checkboxId = 'checkbox-group-print_view_display_options-site_logo'; + const checkbox = adminPage.locator(`#${checkboxId}`); + const wasChecked = await checkbox.isChecked(); + + await clickSwitch(adminPage, checkboxId); + if (wasChecked) { + await expect(checkbox).not.toBeChecked(); + } else { + await expect(checkbox).toBeChecked(); + } + await saveSettings(adminPage); + + await gotoSettings(adminPage, 'recipe_settings'); + const reloaded = adminPage.locator(`#${checkboxId}`); + if (wasChecked) { + await expect(reloaded).not.toBeChecked(); + } else { + await expect(reloaded).toBeChecked(); + } + + const saved = settingList(getCookedSettings(), 'print_view_display_options'); + expect(saved.includes('site_logo')).toBe(!wasChecked); + }); + + test('persists a number field on the Design tab', async ({ adminContext }) => { + const adminPage = await adminContext.newPage(); + await gotoSettings(adminPage, 'design'); + + const input = adminPage.locator('input[name="cooked_settings[responsive_breakpoint_1]"]'); + const current = await input.inputValue(); + const next = current === '1001' ? '1002' : '1001'; + + await input.fill(next); + await saveSettings(adminPage); + + await gotoSettings(adminPage, 'design'); + await expect(adminPage.locator('input[name="cooked_settings[responsive_breakpoint_1]"]')).toHaveValue(next); + expect(String(getCookedSettings().responsive_breakpoint_1)).toBe(next); + }); +}); + +test.afterAll(() => { + setCookedSettings(originalSettings); +}); From 7e53a0e8b5bd909b3c0862e2d8b4da1099b991b4 Mon Sep 17 00:00:00 2001 From: Armand Tresova Date: Mon, 17 Aug 2026 15:00:39 -0400 Subject: [PATCH 17/36] Plugin Check (PCP) - Fixes PCP Wave 7: Cooked AJAX --- assets/admin/css/style.min.css | 2 +- assets/admin/js/cooked-migration.js | 16 +- assets/admin/js/cooked-migration.min.js | 2 +- includes/class.cooked-admin-enqueues.php | 1 + includes/class.cooked-ajax.php | 234 ++++++++++++----------- 5 files changed, 134 insertions(+), 121 deletions(-) diff --git a/assets/admin/css/style.min.css b/assets/admin/css/style.min.css index 11547ac..eb082a5 100644 --- a/assets/admin/css/style.min.css +++ b/assets/admin/css/style.min.css @@ -1 +1 @@ -.cooked-clearfix:after{content:"";display:table;clear:both}#post-body-content #postdivrich{display:none}body.post-type-cp_recipe .wp-list-table tfoot th.check-column,body.post-type-cp_recipe .wp-list-table thead th.check-column{padding:15px 0 0 7px}body.post-type-cp_recipe .wp-list-table tfoot td,body.post-type-cp_recipe .wp-list-table tfoot th,body.post-type-cp_recipe .wp-list-table thead td,body.post-type-cp_recipe .wp-list-table thead th{padding:10px 10px}body.post-type-cp_recipe .wp-list-table tfoot th.sortable a,body.post-type-cp_recipe .wp-list-table tfoot th.sorted a,body.post-type-cp_recipe .wp-list-table thead th.sortable a,body.post-type-cp_recipe .wp-list-table thead th.sorted a{padding-left:0;padding-right:0}body.post-type-cp_recipe .wp-list-table tbody th.check-column{padding:15px 0 0 10px}body.post-type-cp_recipe .wp-list-table tbody td,body.post-type-cp_recipe .wp-list-table tbody th{padding:12px 10px}body.post-type-cp_recipe .wp-list-table tbody td.column-title strong{margin-top:6px}body.post-type-cp_recipe .wp-list-table tbody td.column-title strong .row-title{padding-top:10px;font-size:1rem!important}th.column-featured_image{width:50px;text-align:center}td.column-featured_image{width:50px;text-align:center}.cooked-admin-recipes-list-image img{width:49px;height:auto;border-radius:3px;position:relative;top:3px}body.post-type-cp_recipe #titlediv #title{box-shadow:none;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;padding:0 12px;font-weight:400;font-size:19px;letter-spacing:0;height:44px}body.post-type-cp_recipe #titlediv #title-prompt-text{font-size:15px;color:#888;font-weight:400;letter-spacing:0;padding:12px 15px}body.post-type-cp_recipe .mce-fullscreen{z-index:100100!important}#cooked-settings-prewrap{display:flex;justify-content:center}#cooked-settings-wrap{position:relative;border-radius:10px;margin:40px 40px 40px 20px;max-width:1200px;min-width:1000px;background:#fff;box-shadow:0 4px 4px rgba(0,0,0,.05),0 8px 8px rgba(0,0,0,.05),0 32px 32px rgba(0,0,0,.05),0 64px 64px rgba(0,0,0,.05)}#cooked-settings-wrap.is-stuck{position:static!important}#cooked-settings-wrap .cooked-settings-update-button{position:absolute;top:30px;right:30px}#cooked-settings-wrap .cooked-settings-update-button>.button-primary{box-shadow:none;border:none}#cooked-settings-wrap.is-stuck .cooked-settings-update-button{position:fixed;top:35px;right:11px;z-index:100001}#cooked-recipe-tabs{list-style:none;padding:0;margin:0;position:absolute;top:0;left:0;width:100%}#cooked-recipe-tabs li{font-size:.9rem;line-height:3rem;height:3rem;font-weight:500;display:inline-block;padding:0 1.25rem;margin:0;color:#fff}#cooked-recipe-tabs li:hover{cursor:pointer}#cooked-recipe-tabs li.active,#cooked-recipe-tabs li.active:hover{cursor:default}#cooked-recipe-tabs li:last-child{border-right:none!important}#cooked-recipe-tabs li.cooked-loading{opacity:0;background:0 0;color:#fff;position:absolute;top:0;right:0;cursor:default;font-size:14px;width:40px;height:40px;text-align:center;display:block;line-height:40px;padding:0}#cooked-recipe-tabs li.cooked-loading:hover{background:0 0;color:#fff}#cooked_recipe_settings.cooked-loading #cooked-recipe-tabs li.cooked-loading{opacity:1}#cooked_recipe_settings .inside{padding-top:60px;margin:0}#cooked_recipe_settings.stuck #cooked-recipe-tabs{position:fixed;width:auto;top:32px;left:23px;z-index:100000;margin-left:160px;box-shadow:0 3px 50px rgba(0,0,0,.25)}#cooked_recipe_settings.stuck #cooked-recipe-tabs li.cooked-loading{right:160px}.cooked-recipe-tab-content-wrapper .cooked-recipe-tab-content{display:none}.cooked-recipe-tab-content-wrapper .cooked-recipe-tab-content:first-child{display:block}.cooked-left{float:left;display:inline-block;width:auto}.cooked-right{float:right;display:inline-block;width:auto}#cooked_field--cooked_pro_license_key{font-family:monospace}#cooked_recipe_settings .cooked-bm-5{margin-bottom:5px!important}#cooked_recipe_settings .cooked-bm-10{margin-bottom:10px!important}#cooked_recipe_settings .cooked-tm-10{margin-top:10px!important}#cooked_recipe_settings .cooked-bm-15{margin-bottom:15px!important}#cooked_recipe_settings .cooked-bm-20{margin-bottom:20px!important}#cooked_recipe_settings .cooked-bm-30{margin-bottom:30px!important}#cooked_recipe_settings .cooked-bm-5-up{margin-bottom:-5px!important}#cooked_recipe_settings .cooked-bm-10-up{margin-bottom:-10px!important}#cooked_recipe_settings .cooked-bm-15-up{margin-bottom:-15px!important}#cooked_recipe_settings .cooked-bm-20-up{margin-bottom:-20px!important}#cooked_recipe_settings .cooked-bm-30-up{margin-bottom:-30px!important}#cooked_recipe_settings .cooked-hr{border:none;border-top:2px solid #ddd;margin:10px 0 0 0;padding:15px 0 0 0}#cooked_recipe_settings .cooked-conditional-hidden{display:none}#cooked_recipe_settings .cooked-recipe-tab-content{padding:23px 30px 15px 30px}#cooked_recipe_settings .recipe-setting-block{margin:0 0 20px;width:100%}#cooked_recipe_settings .recipe-setting-block p{font-size:.9rem;line-height:1.5rem;margin:0 0 1rem;padding:0}#cooked_recipe_settings .recipe-setting-block p.cooked-padded{line-height:1.75rem;font-size:.85rem}#cooked_recipe_settings .recipe-setting-block .cooked-conditional-hidden{padding:0}#cooked_recipe_settings textarea{width:100%;height:75px;padding:15px;box-sizing:border-box;position:relative;top:5px}#cooked_recipe_settings .recipe-setting-block>label.cooked-select-label{top:5px}#cooked_recipe_settings select{cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;box-shadow:none;border-radius:3px;padding:0 45px 0 9px;line-height:31px;height:33px;box-sizing:border-box;background:#fff}#cooked_recipe_settings .cooked-select-wrapper{background:#fff;position:relative;width:auto;display:inline-block;border-radius:3px;margin:0 5px 0 0}#cooked_recipe_settings .cooked-select-wrapper select{margin:0;background:0 0;position:relative}#cooked_recipe_settings .cooked-select-wrapper:before{font-family:CookedIcons;display:block;width:15px;height:15px;line-height:14px;color:#000;font-size:14px;content:"\f00b";position:absolute;right:12px;top:11px;color:rgba(0,0,0,.3)}#cooked_recipe_settings .cooked-select-wrapper:hover:before{color:#000}#cooked_recipe_settings .cooked-select-wrapper:hover select{border-color:#ccc}#cooked_recipe_settings .cooked-checkbox-radio-label{position:relative;left:1px}#cooked_recipe_settings .recipe-setting-block input[type=checkbox],#cooked_recipe_settings .recipe-setting-block input[type=radio]{margin-top:0}#cooked_recipe_settings .recipe-setting-block input[type=number]{width:65px}#cooked_recipe_settings .recipe-setting-block input[type=password],#cooked_recipe_settings .recipe-setting-block input[type=text]{width:75%}#cooked_recipe_settings .recipe-setting-block input[type=number],#cooked_recipe_settings .recipe-setting-block input[type=password],#cooked_recipe_settings .recipe-setting-block input[type=text]{margin:0 6px 5px 0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;height:33px;box-shadow:none;position:relative;top:2px;padding:0 10px}#cooked_recipe_settings .recipe-setting-block p.cooked-measurement-inputs .cooked-measurement-column{width:43%;margin-right:3%;display:inline-block}#cooked_recipe_settings .recipe-setting-block p.cooked-measurement-inputs .cooked-select-wrapper,#cooked_recipe_settings .recipe-setting-block p.cooked-measurement-inputs input,#cooked_recipe_settings .recipe-setting-block p.cooked-measurement-inputs label{width:100%;display:block}#cooked_recipe_settings .recipe-setting-block p.cooked-measurement-inputs .cooked-select-wrapper select{width:100%}#cooked_recipe_settings .recipe-setting-block input[type=number],#cooked_recipe_settings .recipe-setting-block input[type=password].cooked-small-textfield,#cooked_recipe_settings .recipe-setting-block input[type=text].cooked-small-textfield{width:150px}#cooked_recipe_settings small{display:block;line-height:1.5;font-size:12px;color:#888;padding:10px 0 0}#cooked_recipe_settings .recipe-setting-block em{color:#aaa}#cooked_recipe_settings .recipe-setting-block,#cooked_recipe_settings .recipe-setting-block .cooked-repositioned{display:block;position:relative;box-sizing:border-box;line-height:1}#cooked_recipe_settings .recipe-setting-block .cooked-repositioned{padding-left:157px}#cooked_recipe_settings .recipe-setting-block h3.cooked-settings-title{width:70%;position:relative;z-index:2;padding:.5rem 0 .5rem;margin:0;font-size:1rem;line-height:1.5rem;font-weight:600}#cooked_recipe_settings .recipe-setting-block strong.cooked-heading{font-size:14px}#cooked_recipe_settings .recipe-setting-block .cooked-repositioned>label{position:absolute;top:1px;left:0;width:147px;cursor:default;font-weight:600}#cooked_recipe_settings .recipe-setting-block .cooked-tooltip-icon{color:#aaa;cursor:help;display:inline-block;margin-left:10px}#cooked_recipe_settings .recipe-setting-block .cooked-tooltip-icon:hover{color:#eee}#cooked_recipe_settings .recipe-setting-block .cooked-repositioned>.cooked-checkbox-radio-label{top:7px}#cooked_recipe_settings .recipe-setting-block .cooked-repositioned>label{top:auto;height:33px;line-height:30px;padding:0;width:130px}#cooked_recipe_settings .recipe-setting-block .cooked-repositioned:first-child label:first-child{padding-top:12px;margin-top:-12px}#cooked_recipe_settings .recipe-setting-block .cooked-repositioned span.cooked-padded{padding:0 0 15px;display:block;line-height:1.9}#cooked_recipe_settings .recipe-setting-block .wp-picker-container .cooked-color-field.wp-color-picker{padding:5px;height:25px;top:0;margin:0;width:74px;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;border:1px solid #ccc}#cooked_recipe_settings .recipe-setting-block input[type=text].cooked-shortcode-field{font-family:monospace;background:#f5f5f5;font-size:12px}#cooked_recipe_settings .cooked-banner-block{margin:30px -30px -17px;display:block;box-sizing:content-box;padding:10px 30px 15px;background:#f9f9f9;width:100%;border-top:1px solid #e5e5e5}#cooked_recipe_settings .cooked-advanced-options-hr{border:none;border-bottom:1px solid #ddd;height:1px;padding:10px 0 0 0;margin:0 0 20px}#cooked_recipe_settings .recipe-setting-block input.cooked-time-picker{top:0;width:100%;padding-right:70%;margin:0 0 3px}#cooked_recipe_settings .recipe-setting-block .cooked-time-picker-text{position:absolute;bottom:13px;right:40px;color:#888}#cooked_recipe_settings .cooked-alert-block{background:#fffbdc;-moz-border-radius:10px;-webkit-border-radius:10px;border-radius:10px;margin:10px 0 15px;padding:10px 22px 2px;border:2px solid #ece8c4}#cooked_recipe_settings .recipe-setting-block.cooked-template{display:none}#cooked_recipe_settings ul.cooked-admin-ul{font-size:.9rem;margin:0 0 1rem 2rem;list-style:disc}#cooked_recipe_settings ul.cooked-admin-ul li{font-size:.9rem;padding:0;margin:0 0 .5rem}#cooked_recipe_settings .cooked-html-block{background:#fff;width:auto;min-width:300px;display:inline-block;padding:.5rem 1.3rem .25rem;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,.15)}#cooked_recipe_settings .recipe-setting-block .cooked-html-block h3.cooked-settings-title{width:100%;color:#333}#cooked_recipe_settings .recipe-setting-block .cooked-html-block.valid{background:#fff;border:2px solid #0aa780;box-shadow:none;border-radius:5px}#cooked_recipe_settings .recipe-setting-block .cooked-html-block.valid>.cooked-settings-title{color:#0aa780}#cooked_recipe_settings .recipe-setting-block .cooked-html-block.expired{border:2px solid #ca4a20}#cooked-directions-builder .cooked-direction-block.cooked-expanded>.cooked-heading-name,#cooked-directions-builder .cooked-direction-block.cooked-has-heading-element>.cooked-heading-name,#cooked-directions-builder .cooked-direction-block:hover>.cooked-heading-name,#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded>.cooked-heading-name,#cooked-ingredients-builder .cooked-ingredient-block.cooked-has-heading-element>.cooked-heading-name,#cooked-ingredients-builder .cooked-ingredient-block:hover>.cooked-heading-name{padding-right:32px}#cooked-directions-builder .cooked-direction-block .cooked-show-heading-element,#cooked-ingredients-builder .cooked-ingredient-block .cooked-show-heading-element{opacity:0;cursor:pointer;font-size:14px;position:absolute;top:14px;right:35px}#cooked-directions-builder .cooked-direction-block .cooked-show-heading-element .cooked-icon,#cooked-ingredients-builder .cooked-ingredient-block .cooked-show-heading-element .cooked-icon{color:#888}#cooked-directions-builder .cooked-direction-block.cooked-expanded .cooked-show-heading-element,#cooked-directions-builder .cooked-direction-block.cooked-has-heading-element .cooked-show-heading-element,#cooked-directions-builder .cooked-direction-block:hover .cooked-show-heading-element,#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded .cooked-show-heading-element,#cooked-ingredients-builder .cooked-ingredient-block.cooked-has-heading-element .cooked-show-heading-element,#cooked-ingredients-builder .cooked-ingredient-block:hover .cooked-show-heading-element{opacity:1}#cooked-directions-builder .cooked-direction-block .cooked-show-heading-element .cooked-icon:hover,#cooked-directions-builder .cooked-direction-block.cooked-expanded .cooked-show-heading-element .cooked-icon,#cooked-directions-builder .cooked-direction-block.cooked-has-heading-element .cooked-show-heading-element .cooked-icon,#cooked-ingredients-builder .cooked-ingredient-block .cooked-show-heading-element .cooked-icon:hover,#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded .cooked-show-heading-element .cooked-icon,#cooked-ingredients-builder .cooked-ingredient-block.cooked-has-heading-element .cooked-show-heading-element .cooked-icon{color:#0685ba}#cooked-directions-builder .cooked-direction-block>.cooked-heading-element,#cooked-ingredients-builder .cooked-ingredient-block>.cooked-heading-element{display:none;width:65%;float:right;margin-top:4px;padding-right:32px}#cooked-directions-builder .cooked-direction-block>.cooked-heading-element select,#cooked-ingredients-builder .cooked-ingredient-block>.cooked-heading-element select{color:#0685ba;width:100%}#cooked-directions-builder .cooked-direction-block>.cooked-heading-element label,#cooked-ingredients-builder .cooked-ingredient-block>.cooked-heading-element label{font-weight:600}#cooked-directions-builder .cooked-direction-block.cooked-expanded>.cooked-heading-element,#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded>.cooked-heading-element{display:block}#cooked-ingredients-builder .cooked-ingredient-block .cooked-show-substitution{opacity:0;cursor:pointer;font-size:14px;position:absolute;top:15px;right:58px}#cooked-ingredients-builder .cooked-ingredient-block .cooked-show-substitution .cooked-icon{color:#888}#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded .cooked-show-substitution,#cooked-ingredients-builder .cooked-ingredient-block.cooked-has-substitution .cooked-show-substitution,#cooked-ingredients-builder .cooked-ingredient-block:hover .cooked-show-substitution{opacity:1;right:50px}#cooked-ingredients-builder .cooked-ingredient-block .cooked-show-substitution .cooked-icon:hover,#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded .cooked-show-substitution .cooked-icon,#cooked-ingredients-builder .cooked-ingredient-block.cooked-has-substitution .cooked-show-substitution .cooked-icon{color:#0685ba}#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded>.cooked-ingredient-name,#cooked-ingredients-builder .cooked-ingredient-block.cooked-has-substitution>.cooked-ingredient-name,#cooked-ingredients-builder .cooked-ingredient-block:hover>.cooked-ingredient-name{padding-right:32px}#cooked_recipe_settings .switchery-small,body.post-type-cp_recipe .switchery-small{height:1rem;width:1.75rem;border-radius:1rem;margin-right:.4rem}#cooked_recipe_settings .switchery-small>small,body.post-type-cp_recipe .switchery-small>small{width:1rem;height:1rem;padding:0}.cooked-tooltip-buttons .cooked-icon-spin{margin-top:10px;font-size:15px}.cooked-tooltip-buttons .cooked-saved-default{height:28px;line-height:28px;font-weight:700;font-size:15px;color:#888}.cooked-progress{display:none;position:relative;background:#eee;width:100%;height:6px;padding:0;border-radius:3px;margin:10px 0 0 0}.cooked-progress-text{display:none;font-size:10px;color:#aaa;padding:2px 0 5px}.cooked-progress-text.cooked-active,.cooked-progress.cooked-active{display:block}.cooked-progress .cooked-progress-bar{display:block;position:absolute;background:#0085ba;width:0%;height:6px;top:0;left:0;border-radius:3px}#cooked-import-progress.cooked-progress,#cooked-migration-progress.cooked-progress{background:#ccc;margin:25px 0 0 0;border-radius:6px;height:12px;max-width:600px}#cooked-import-progress.cooked-progress .cooked-progress-bar,#cooked-migration-progress.cooked-progress .cooked-progress-bar{border-radius:6px;height:12px}#cooked-import-progress-text.cooked-progress-text,#cooked-migration-progress-text.cooked-progress-text{font-size:11px;color:#888;max-width:600px}#cooked-import-completed,#cooked-migration-completed{display:none}#cooked-import-completed.cooked-active,#cooked-migration-completed.cooked-active{display:block}#cooked-csv-import-progress.cooked-progress{background:#ccc;margin:25px 0 0 0;border-radius:6px;height:12px;max-width:600px}#cooked-csv-import-progress.cooked-progress .cooked-progress-bar{border-radius:6px;height:12px}#cooked-csv-import-progress-text.cooked-progress-text{font-size:11px;color:#888;max-width:600px}#cooked-csv-import-completed{display:none}#cooked-csv-import-completed.cooked-active{display:block}#cooked-csv-import-form{max-width:100%;box-sizing:border-box}#cooked-csv-import-form input[type=file]{max-width:100%;box-sizing:border-box;width:100%}#cooked-csv-import-errors{max-width:100%;box-sizing:border-box;word-wrap:break-word}#cooked-import-progress.cooked-progress,#cooked-migration-progress.cooked-progress,#cooked-related-progress.cooked-progress{background:#ccc;margin:25px 0 0 0;border-radius:6px;height:12px;max-width:600px}#cooked-import-progress.cooked-progress .cooked-progress-bar,#cooked-migration-progress.cooked-progress .cooked-progress-bar,#cooked-related-progress.cooked-progress .cooked-progress-bar{border-radius:6px;height:12px}#cooked-import-progress-text.cooked-progress-text,#cooked-migration-progress-text.cooked-progress-text,#cooked-related-progress-text.cooked-progress-text{font-size:11px;color:#888;max-width:600px}#cooked-import-completed,#cooked-migration-completed,#cooked-related-completed{display:none}#cooked-import-completed.cooked-active,#cooked-migration-completed.cooked-active,#cooked-related-completed.cooked-active{display:block}.cooked-related-status{margin:0 0 12px 0;padding:8px 12px;font-size:13px;line-height:1.4;border-radius:4px;border-left:3px solid #00a878;background:#f0f9f6;color:#2d4a42;max-width:480px}#cooked-related-last-done.cooked-related-status{border-left-color:#8c9b99;background:#f6f8f7;color:#5c6b69}#cooked_recipe_settings .cooked-layout-save-default{position:relative;top:-2px;z-index:10;margin:0 0 0 15px;padding:0 8px 1px}#cooked_recipe_settings .cooked-layout-load-default{position:relative;top:-2px;z-index:10;margin:0 0 0 10px;padding:0 8px 1px}body .button.button-cooked-reset{color:#999}body .button.button-cooked-reset:hover{color:#555}#cooked_recipe_settings .cooked-ingredient-headers{display:block;padding:5px 25px 0 35px}#cooked_recipe_settings .cooked-ingredient-headers span{font-size:.7rem;font-weight:700;letter-spacing:.03rem;text-transform:uppercase;color:#0085ba;box-sizing:border-box;display:inline-block;float:left}#cooked_recipe_settings .cooked-ingredient-headers span.cooked-ingredient-header-amount{width:15%}#cooked_recipe_settings .cooked-ingredient-headers span.cooked-ingredient-header-measurement{width:20%}#cooked_recipe_settings .cooked-ingredient-headers span.cooked-ingredient-header-item{width:65%}#cooked-ingredients-builder{margin:0 0 20px}#cooked-ingredients-builder .cooked-ingredient-block{display:block;padding:5px 45px 5px 35px}#cooked-ingredients-builder .cooked-ingredient-block:hover{background:#eee}#cooked-ingredients-builder .cooked-ingredient-block input[type=number],#cooked-ingredients-builder .cooked-ingredient-block input[type=text],#cooked-ingredients-builder .cooked-ingredient-block select{margin:0;display:block;top:0}#cooked-ingredients-builder .cooked-ingredient-block>div{display:inline-block;float:left;box-sizing:border-box;padding-right:10px}#cooked-ingredients-builder .cooked-ingredient-block>div input[type=text]{width:100%}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-ingredient-amount{width:15%}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-ingredient-measurement{width:20%}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-ingredient-measurement .cooked-select-wrapper{width:100%}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-ingredient-measurement .cooked-select-wrapper select{width:100%}#cooked-ingredients-builder .cooked-ingredient-block .cooked-substitution-fields>.cooked-ingredient-measurement .cooked-select-wrapper{width:100%}#cooked-ingredients-builder .cooked-ingredient-block .cooked-substitution-fields>.cooked-ingredient-measurement .cooked-select-wrapper select{width:100%}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-ingredient-name{width:65%}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-heading-name{width:100%}#cooked-ingredients-builder .cooked-ingredient-block>div small.cooked-input-ex{display:block;padding:5px 0 0 2px}#cooked-ingredients-builder .cooked-ingredient-block{margin:0}#cooked-ingredients-builder .cooked-ingredient-block i.cooked-icon-drag{cursor:move;color:#ccc;font-size:32px;position:absolute;top:5px;left:1px;touch-action:none}#cooked-ingredients-builder .cooked-ingredient-block:hover i.cooked-icon-drag{color:#888}#cooked-ingredients-builder .cooked-ingredient-block.ui-sortable-helper{background:#f5f5f5;box-shadow:0 10px 20px rgba(0,0,0,.15);-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}#cooked-ingredients-builder .cooked-ingredient-block.ui-sortable-placeholder{visibility:visible!important;background:#eee;display:block;width:100%;height:43px;border-top:6px solid #fff;border-bottom:6px solid #fff}#cooked-ingredients-builder .cooked-ingredient-block .cooked-delete-ingredient{cursor:pointer;font-size:14px;position:absolute;top:14px;right:11px}#cooked-ingredients-builder .cooked-ingredient-block .cooked-delete-ingredient .cooked-icon{color:#fff}#cooked-ingredients-builder .cooked-ingredient-block:hover .cooked-delete-ingredient .cooked-icon{color:#888}#cooked-ingredients-builder .cooked-ingredient-block .cooked-delete-ingredient .cooked-icon:hover{color:#de2020}#cooked-ingredients-builder .cooked-ingredient-block.cooked-ingredient-heading{margin:10px 0}#cooked-ingredients-builder .cooked-ingredient-block.cooked-ingredient-heading>div input[type=text]{padding:0 7px;font-size:16px;background:#f9f9f9;font-weight:600}#cooked-directions-builder{margin:0 0 20px}#cooked-directions-builder .cooked-direction-block{display:block;padding:15px 25px 15px 35px}#cooked-directions-builder .cooked-direction-block.cooked-direction-heading{padding:5px 25px 5px 35px}#cooked-directions-builder .cooked-direction-block:hover{background:#f5f5f5}#cooked-directions-builder .cooked-direction-block.cooked-direction-heading:hover{background:#eee}#cooked-directions-builder .cooked-direction-block textarea{margin:0;display:block;height:250px;top:0}#cooked-directions-builder .cooked-direction-block>div{display:inline-block;float:left;box-sizing:border-box;padding-right:15px}#cooked-directions-builder .cooked-direction-block>div:last-child{padding:0}#cooked-directions-builder .cooked-direction-block>div input[type=text]{width:100%}#cooked-directions-builder .cooked-direction-block .mce-statusbar{display:none}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image{width:135px;position:absolute;top:15px;left:35px}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image img{cursor:pointer;display:none;border-radius:3px}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image .cooked-direction-img-placeholder{box-sizing:border-box;cursor:pointer;border-radius:3px;width:100%;padding-bottom:100%;background:0 0;border:2px dashed #ddd;border-radius:3px;margin-top:15px}#cooked-directions-builder .cooked-direction-block:hover>.cooked-direction-image .cooked-direction-img-placeholder{background:0 0}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image .cooked-direction-img-placeholder:hover{background:#ddd;border:2px solid #ddd}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image .direction-image-button{width:100%;text-align:center}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image .remove-image-button{display:none;position:absolute;top:43px;right:15px}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image.cooked-has-image .remove-image-button{display:inline-block;padding:10px;background:rgba(0,0,0,.25);color:#fff}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image.cooked-has-image .remove-image-button:hover{background:#ff0100}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image.cooked-has-image img{width:100%;height:auto;display:block;margin:15px 0 0}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image.cooked-has-image .cooked-direction-img-placeholder{display:none}#cooked-directions-builder .cooked-direction-block>.cooked-direction-content{width:100%;padding-left:175px;padding-right:175px}#cooked-directions-builder .cooked-direction-block .cooked-direction-number{opacity:.4;position:absolute;display:inline-block;font-weight:800;top:17px;left:35px;font-size:1.25rem;line-height:1}#cooked-directions-builder .cooked-direction-block.cooked-direction-has-number>.cooked-direction-image{left:65px}#cooked-directions-builder .cooked-direction-block.cooked-direction-has-number>.cooked-direction-content{padding-left:175px;padding-right:175px}#cooked-directions-builder .cooked-direction-block.cooked-direction-has-number-wide>.cooked-direction-content{padding-left:185px;padding-right:175px}#cooked-directions-builder .cooked-direction-block>.cooked-heading-name{width:100%}#cooked-directions-builder .cooked-direction-block{margin:0}#cooked-directions-builder .cooked-direction-block i.cooked-icon-drag{cursor:move;color:#ccc;font-size:32px;position:absolute;top:13px;left:1px;touch-action:none}#cooked-directions-builder .cooked-direction-block.cooked-direction-heading i.cooked-icon-drag{top:5px}#cooked-directions-builder .cooked-direction-block:hover i.cooked-icon-drag{color:#888}#cooked-directions-builder .cooked-direction-block.ui-sortable-helper{background:#f5f5f5;box-shadow:0 10px 20px rgba(0,0,0,.15);-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}#cooked-directions-builder .cooked-direction-block.ui-sortable-placeholder{visibility:visible!important;background:#eee;display:block;width:100%;height:280px;border-top:6px solid #fff;border-bottom:6px solid #fff}#cooked-directions-builder .cooked-direction-block.cooked-direction-heading.ui-sortable-placeholder{height:43px}#cooked-directions-builder .cooked-direction-block .cooked-delete-direction{cursor:pointer;font-size:14px;position:absolute;right:14px}#cooked-directions-builder .cooked-direction-block .cooked-delete-direction .cooked-icon{color:#fff}#cooked-directions-builder .cooked-direction-block:hover .cooked-delete-direction .cooked-icon{color:#888}#cooked-directions-builder .cooked-direction-block .cooked-delete-direction .cooked-icon:hover{color:#de2020}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video{width:135px;position:absolute;top:15px;right:25px}#cooked-directions-builder .cooked-direction-block.cooked-direction-heading{margin:10px 0}#cooked-directions-builder .cooked-direction-block.cooked-direction-heading>div input[type=text]{padding:0 7px;font-size:16px;background:#f9f9f9;font-weight:600;top:0;margin:0}#cooked-directions-builder .cooked-direction-block .cooked-delete-direction{top:16px}#cooked-directions-builder .cooked-direction-block.cooked-direction-heading .cooked-delete-direction{top:14px}.cooked-settings-image-field{position:relative;display:inline-block;max-width:150px}.cooked-settings-image-field .cooked-settings-image-preview-img{display:none;width:100%;height:auto;margin:10px 0 0;border-radius:3px}.cooked-settings-image-field .cooked-settings-image-remove{display:none;position:absolute;top:50px;right:0;padding:10px;background:rgba(0,0,0,.25);color:#fff;border-radius:3px}.cooked-settings-image-field.cooked-has-image .cooked-settings-image-remove{display:inline-block}.cooked-settings-image-field.cooked-has-image .cooked-settings-image-remove:hover{background:#ff0100}.cooked-settings-image-field.cooked-has-image .cooked-settings-image-preview-img{display:block}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video .cooked-direction-video-preview{display:none;width:100%;aspect-ratio:1/1;object-fit:cover;border-radius:3px;cursor:pointer}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video .cooked-direction-video-placeholder{box-sizing:border-box;cursor:pointer;border-radius:3px;width:100%;padding-bottom:100%;background:0 0;border:2px dashed #ddd;margin-top:15px}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video .cooked-direction-video-placeholder:hover{background:#ddd;border:2px solid #ddd}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video .direction-video-button{width:100%;text-align:center}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video.cooked-has-video .cooked-direction-video-placeholder{display:none}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video.cooked-has-video .cooked-direction-video-preview{display:block;width:100%;aspect-ratio:1/1;object-fit:cover;margin-top:15px}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video .remove-video-button{display:none;position:absolute;top:43px;right:15px}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video.cooked-has-video .remove-video-button{display:inline-block;padding:10px;background:rgba(0,0,0,.25);color:#fff}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video.cooked-has-video .remove-video-button:hover{background:#ff0100}@media screen and (max-width:1440px){#cooked-directions-builder .cooked-direction-block:not(.cooked-direction-heading){display:grid;grid-template-columns:1fr 1fr;grid-template-areas:"content content" "image video";gap:10px;padding:15px 0 15px 0}#cooked-directions-builder .cooked-direction-block:not(.cooked-direction-heading)>.cooked-direction-content{grid-area:content;padding:14px 14px 0 14px!important;min-height:80px}#cooked-directions-builder .cooked-direction-block:not(.cooked-direction-heading)>.cooked-direction-image{grid-area:image;position:relative;width:auto;left:auto;top:auto;padding-left:15px;padding-right:0}#cooked-directions-builder .cooked-direction-block:not(.cooked-direction-heading)>.cooked-direction-video{grid-area:video;position:relative;width:auto;right:auto;top:auto}#cooked_recipe_settings .cooked-recipe-tab-content{padding:0 0 15px 0}#cooked-recipe-tabs{white-space:nowrap;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;top:0;width:100%}#cooked-recipe-tabs::-webkit-scrollbar{display:none}#cooked-recipe-tabs li{float:none}#cooked_recipe_settings.stuck #cooked-recipe-tabs{position:fixed;top:32px;left:0;width:100%;margin-left:0}}#cooked-recipe-image-gallery{margin:0 -1% 0}#cooked-recipe-image-gallery .cooked-recipe-gallery-item{position:relative;overflow:hidden;cursor:move;box-sizing:border-box;display:inline-block;width:18%;height:auto;padding:0;margin:0 1% 2% 1%}#cooked-recipe-image-gallery .cooked-recipe-gallery-item:hover{opacity:.9}#cooked-recipe-image-gallery .cooked-recipe-gallery-item img{width:100%;height:auto;border-radius:3px;display:block;margin:0;padding:0;border:none}#cooked-recipe-image-gallery .cooked-recipe-gallery-item.ui-sortable-helper,#cooked-recipe-image-gallery .cooked-recipe-gallery-item.ui-sortable-helper:hover{opacity:.75;box-shadow:0 10px 20px rgba(0,0,0,.15)}#cooked-recipe-image-gallery .cooked-recipe-gallery-item.ui-sortable-placeholder{width:18%;height:auto;visibility:visible!important;border-radius:3px;background:#ddd;display:inline-block}#cooked-recipe-image-gallery .cooked-recipe-gallery-item .remove-image-button{border-radius:0 3px 0 0;display:inline-block;position:absolute;top:0;right:0;padding:10px;background:rgba(0,0,0,.25);color:#fff}#cooked-recipe-image-gallery .cooked-recipe-gallery-item .remove-image-button:hover{background:#ff0100}#cooked-recipe-image-gallery .cooked-recipe-gallery-item .cooked-gallery-edit-button{border-radius:3px 0 0 0;display:inline-block;position:absolute;top:0;left:0;padding:10px;background:rgba(0,0,0,.25);color:#fff}#cooked-recipe-image-gallery .cooked-recipe-gallery-item .cooked-gallery-edit-button:hover{background:#0084bc}#cooked-recipe-image-gallery .cooked-recipe-gallery-item .cooked-gallery-item-title{position:absolute;left:0;bottom:0;box-sizing:border-box;width:100%;border-radius:0 0 3px 3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;padding:10px 10px 11px;text-align:center;background:#000;background:rgba(0,0,0,.3);color:#fff;font-size:12px}#cooked-recipe-image-gallery .cooked-recipe-gallery-item:hover .cooked-gallery-item-title{background:#000;background:rgba(0,0,0,.75)}#cooked-recipe-image-gallery .cooked-recipe-gallery-item.ui-sortable-helper{-webkit-animation:cooked-wiggle .4s infinite;-moz-animation:cooked-wiggle .4s infinite;-o-animation:cooked-wiggle .4s infinite;animation:cooked-wiggle .4s infinite}#cooked_recipe_settings label.cooked-nutrition-label{display:block;font-size:.7rem;letter-spacing:.03rem;font-weight:700;text-transform:uppercase;line-height:1;margin:0 0 7px}#cooked_recipe_settings #cooked-nutrition-label{line-height:1.6;font-size:1.1rem;border:1px solid #aaa;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;padding:5%;margin:0 0 2rem;font-family:Helvetica,Arial,sans-serif;container-name:nutrition-label;container-type:inline-size}#cooked_recipe_settings #cooked-nutrition-label h2{font-size:2.5rem;line-height:1;letter-spacing:0;font-weight:800;padding:0 0 .5rem 0;margin:0 0 .4rem;color:#333;border-bottom:1px solid #aaa}#cooked_recipe_settings #cooked-nutrition-label p.cooked-daily-value-text{padding:.5rem 0 0 0;margin:0;font-size:.9rem}#cooked_recipe_settings #cooked-nutrition-label ul.cooked-nut-servings p{font-size:1.5rem}#cooked_recipe_settings #cooked-nutrition-label ul{list-style:none;margin:0;padding:0}#cooked_recipe_settings #cooked-nutrition-label li{position:relative;margin:0;padding:0 0 3px 0;border-top:1px solid #aaa}#cooked_recipe_settings #cooked-nutrition-label li li{padding:3px 0 0;margin:3px 0 0}#cooked_recipe_settings #cooked-nutrition-label li:after{content:"";display:table;clear:both}#cooked_recipe_settings #cooked-nutrition-label li.no-after:after{content:none}#cooked_recipe_settings #cooked-nutrition-label li.cooked-nut-spacer{border:none;height:.6rem;padding:0;background:#333}#cooked_recipe_settings #cooked-nutrition-label ul li.cooked-nut-no-border,#cooked_recipe_settings #cooked-nutrition-label>ul>li:first-child{border:none}#cooked_recipe_settings #cooked-nutrition-label li ul{padding:0;margin:0 0 0 20px}#cooked_recipe_settings #cooked-nutrition-label li ul.cooked-right{margin:0}#cooked_recipe_settings #cooked-nutrition-label li ul.cooked-right li{border:none;padding:0;margin:0}#cooked_recipe_settings #cooked-nutrition-label li.cooked-calories ul.cooked-right li strong.cooked-nut-label{font-size:2.5rem;line-height:1}#cooked_recipe_settings #cooked-nutrition-label .cooked-calories{border-top:none;display:flex;justify-content:space-between;align-items:baseline;padding:0}#cooked_recipe_settings #cooked-nutrition-label .cooked-calories strong,#cooked_recipe_settings #cooked-nutrition-label .cooked-calories strong.cooked-nut-label{font-size:2rem;font-weight:800}#cooked_recipe_settings #cooked-nutrition-label ul.cooked-nut-servings li{border:none}#cooked_recipe_settings #cooked-nutrition-label ul.cooked-nut-servings{margin:0}#cooked_recipe_settings #cooked-nutrition-label .cooked-nut-hr{border:none;border-top:1.3rem solid #333;margin:.1rem 0 0;padding:0}#cooked_recipe_settings #cooked-nutrition-label strong{font-weight:800}#cooked_recipe_settings #cooked-nutrition-label .cooked-serving-size strong,#cooked_recipe_settings #cooked-nutrition-label .cooked-serving-size strong.cooked-nut-label{font-size:1.5rem;font-weight:800}#cooked_recipe_settings #cooked-nutrition-label strong.cooked-nut-heading{font-size:1rem}#cooked_recipe_settings #cooked-nutrition-label strong.cooked-nut-label{font-weight:400}#cooked_recipe_settings #cooked-nutrition-label .cooked-nut-right{float:right}#cooked_recipe_settings #cooked-nutrition-label ul.cooked-nut-bottom{margin:0 0 .75rem}#cooked_recipe_settings #cooked-nutrition-label ul.cooked-nut-bottom li{border-top:none;border-bottom:1px solid #aaa;float:left;width:100%;box-sizing:border-box}#cooked_recipe_settings .cooked-measurement-inputs .cooked-select-wrapper:before{top:8px}.cooked-auto-nutrition{display:flex;justify-content:end;margin:0 0 1rem}#cooked-tooltip-auto-nutrition-notice{margin:0}.cooked-settings-header{border-radius:10px 10px 0 0;width:auto;height:90px;line-height:85px;padding:0 30px;font-weight:600;box-sizing:border-box;background:#33373c;color:#fff;font-size:29px;letter-spacing:-.03rem}.cooked-settings-header .cooked-icon{color:#00e0a9}.cooked-settings-submit-wrap{padding:0 0 35px 35px;margin:-20px 0 0}#cooked_recipe_settings .recipe-setting-block .cooked-permalink-field-wrapper span{font-size:14px;display:inline-block;line-height:32px;padding:5px 0 0 0}#cooked_recipe_settings .recipe-setting-block input[type=text].cooked-permalink-field{position:relative;top:0;display:inline-block;width:150px;margin:0 10px}#cooked-settings-panel{position:relative;margin:0;padding:0}#cooked-settings-tabs{display:flex;width:auto;padding:0 20px 0 0;margin:0;list-style:none}#cooked-settings-tabs li{font-size:13px;line-height:1;font-weight:400;display:block;padding:0;margin:0;color:#fff}#cooked-settings-tabs li .cooked-icon{font-size:14px}#cooked-settings-tabs li .cooked-icon.cooked-icon-recipe-icon{font-size:20px;top:2px;position:relative}#cooked-settings-tabs li:hover{cursor:pointer}#cooked-settings-tabs li.active,#cooked-settings-tabs li.active:hover{cursor:default}#cooked-settings-tabs a{line-height:41px;font-size:.9rem;letter-spacing:.03rem;padding:2px 15px 4px;display:block;outline:0;box-shadow:none;border:none;white-space:nowrap}#cooked-settings-panel.stuck{padding-top:41px}#cooked-settings-panel.stuck #cooked-settings-tabs{position:fixed;width:calc(100% - 180px);top:32px;left:0;z-index:100000;margin-left:160px}#cooked-settings-panel .cooked-settings-tab-content-wrapper .cooked-settings-tab-content{display:none}#cooked-settings-panel .cooked-settings-tab-content-wrapper .cooked-settings-tab-content:first-child{display:block}#cooked-settings-panel .cooked-settings-tab-content{padding:25px 35px;margin-right:0}#cooked-settings-panel #cooked_recipe_settings .recipe-setting-block h3.cooked-settings-title{padding-bottom:.2rem}#cooked_recipe_settings .cooked-setting-column-14{position:relative;width:25%;float:left;padding-right:20px;box-sizing:border-box}#cooked_recipe_settings .cooked-setting-column-13{position:relative;width:33%;float:left;padding-right:20px;box-sizing:border-box}#cooked_recipe_settings .cooked-setting-column-23{position:relative;width:66%;float:left;padding-right:20px;box-sizing:border-box}#cooked_recipe_settings .cooked-setting-column-12{position:relative;width:50%;float:left;padding-right:20px;box-sizing:border-box}#cooked_recipe_settings .cooked-setting-column-12 input[type=text],#cooked_recipe_settings .cooked-setting-column-13 input[type=text],#cooked_recipe_settings .cooked-setting-column-14 input[type=text],#cooked_recipe_settings .cooked-setting-column-23 input[type=text],#cooked_recipe_settings .recipe-setting-block input[type=text].cooked-shortcode-field{width:95%}#cooked_recipe_settings .cooked-setting-column-12 .cooked-select-wrapper,#cooked_recipe_settings .cooked-setting-column-12 select,#cooked_recipe_settings .cooked-setting-column-13 .cooked-select-wrapper,#cooked_recipe_settings .cooked-setting-column-13 select,#cooked_recipe_settings .cooked-setting-column-14 .cooked-select-wrapper,#cooked_recipe_settings .cooked-setting-column-14 select,#cooked_recipe_settings .cooked-setting-column-23 select{width:100%}#cooked_recipe_settings .cooked-setting-column-14.cooked-tb-col{padding:0}#cooked-welcome-screen{padding:15px 50px 0 30px;position:relative}#cooked-welcome-screen .cooked-badge{position:absolute;top:0;right:0}#cooked-welcome-screen .cooked-badge img{border:none;width:150px;height:150px;display:block;margin:0}#cooked-welcome-screen .about-text{margin-bottom:40px;line-height:1.8;font-size:17px;margin-right:260px}#cooked-welcome-screen .about-wrap{margin:25px auto 0;max-width:1000px}#cooked-welcome-screen .about-wrap h1{font-size:2.25rem}#cooked-welcome-screen .about-wrap p{font-size:1rem;margin:0 0 2rem;line-height:1.7rem}#cooked-welcome-screen #cooked-welcome-panel{background:#fff;padding:30px 30px 15px 30px;-moz-border-radius:15px;-webkit-border-radius:15px;border-radius:15px;border:none;box-shadow:0 4px 4px rgba(0,0,0,.05),0 8px 8px rgba(0,0,0,.05),0 32px 32px rgba(0,0,0,.05),0 64px 64px rgba(0,0,0,.05)}#cooked-welcome-screen #cooked-welcome-panel img.cooked-welcome-banner{border:none;display:block;padding:0;margin:0 0 30px;box-sizing:border-box;border-radius:5px 5px 0 0}#cooked-welcome-screen #cooked-welcome-panel .cooked-welcome-panel-intro{text-align:center;margin:0 50px 2rem;padding:15px 0;border-bottom:1px solid #e5e5e5}#cooked-welcome-screen #cooked-welcome-panel .cooked-welcome-panel-intro h1{font-size:1.75rem;margin:0 0 1rem;padding:0}#cooked-welcome-screen #cooked-welcome-panel .cooked-welcome-panel-intro p{max-width:600px;font-size:1.1rem;line-height:1.8rem;margin:0 auto 2rem;padding:0}#cooked-welcome-screen .cooked-welcome-panel a{text-decoration:none}#cooked-welcome-screen .cooked-welcome-panel-column{display:block!important}#cooked-welcome-screen .cooked-welcome-panel-column>strong{font-size:1rem}#cooked-welcome-screen .cooked-welcome-panel-column h3{font-size:1.75rem;margin:0 0 1rem;position:relative}#cooked-welcome-screen .cooked-welcome-panel-column h3 a{position:absolute;top:0;right:0;font-size:12px;font-weight:400}#cooked-welcome-screen .cooked-welcome-panel-column h3 a i.fa{position:relative;top:1px}#cooked-welcome-screen .cooked-welcome-panel-column h4{font-size:1.25rem;margin:0 0 1rem;position:relative}#cooked-welcome-screen .cooked-welcome-panel-column h4 a{position:absolute;top:0;right:0;font-size:12px;font-weight:400}#cooked-welcome-screen .cooked-welcome-panel-column h4 a i.fa{position:relative;top:1px}#cooked-welcome-screen .cooked-welcome-panel-column.cooked-welcome-panel-full{width:100%;padding-right:0;float:none}#cooked-welcome-screen .cooked-welcome-panel-column.cooked-welcome-panel-last{width:70%;padding-right:0;float:right}#cooked-welcome-screen .cooked-welcome-panel-column .fa-external-link{color:#888}#cooked-welcome-screen .cooked-welcome-panel-content{min-height:1px;padding:10px 50px 20px;margin:0}#cooked-welcome-screen .cooked-welcome-panel .cooked-welcome-panel-column-container{display:flex;justify-content:between!important;padding:0!important;flex-wrap:wrap}#cooked-welcome-screen .cooked-welcome-panel-column{box-sizing:border-box;padding-right:2rem;width:30%}#cooked-welcome-screen .cooked-pro-features{width:85%;margin:0 auto;padding:0 0 10px;display:flex;flex-wrap:wrap}#cooked-welcome-screen ul.cooked-whatsnew-list{margin-right:0}#cooked-welcome-screen ul.cooked-whatsnew-list li{line-height:1.7;position:relative;margin-right:0;border-top:1px solid #e5e5e5;padding:8px 0 4px 0}#cooked-welcome-screen ul.cooked-whatsnew-list li:first-child{border:none;padding-top:0}#cooked-welcome-screen ul.cooked-whatsnew-list em.fix,#cooked-welcome-screen ul.cooked-whatsnew-list strong.new,#cooked-welcome-screen ul.cooked-whatsnew-list strong.tweak{position:absolute;font-style:normal;display:inline-block;background:#aaa;text-transform:uppercase;top:11px;left:0;color:#fff;font-weight:600;-moz-border-radius:3px;-webkit-border-radius:3px;text-align:center;width:38px;border-radius:3px;font-size:10px;line-height:19px;height:19px;padding:0 6px;margin:0 6px 0 0}#cooked-welcome-screen ul.cooked-whatsnew-list li:first-child em.fix,#cooked-welcome-screen ul.cooked-whatsnew-list li:first-child strong.new,#cooked-welcome-screen ul.cooked-whatsnew-list li:first-child strong.tweak{top:3px}#cooked-welcome-screen ul.cooked-whatsnew-list strong.new{background:#56c477}#cooked-welcome-screen ul.cooked-whatsnew-list strong.tweak{background:#0073aa}#cooked-welcome-screen ul.cooked-whatsnew-list em.fix{background:#ffad10}#cooked-welcome-screen ul li.cooked-pro i.cooked-icon{color:#ffad10}#cooked-welcome-screen ul li.cooked-pro a{font-weight:600;color:#ffad10}#cooked-welcome-screen ul li.cooked-pro a:hover{color:#d3910f}#cooked-welcome-screen ul.cooked-whatsnew-pro{margin-bottom:0;flex:1 0 auto;width:33.333%}#cooked-welcome-screen ul.cooked-whatsnew-pro li{padding-left:0;font-size:1.15rem;font-weight:400}#cooked-welcome-screen ul.cooked-whatsnew-pro li i.cooked-icon-star{color:#ffad10;margin:0 7px 0 0;font-size:19px;position:relative;top:1px}#cooked-welcome-screen .cooked-welcome-bottom{text-align:center;background:#f9f9f9;margin:30px -80px -15px;border-top:1px solid #f1f1f1}#cooked-welcome-screen .cooked-pro-button{display:inline-block;clear:both;text-align:center;width:auto;margin:30px auto;padding:20px 25px 21px;font-size:1.25rem;line-height:1rem;font-weight:600;color:#fff;background:#ffad10;border-radius:50px}#cooked-welcome-screen .cooked-pro-button:hover{background:#e08704}#cooked-welcome-screen .cooked-coupon-code{color:#4c5e65;display:inline-block;font-size:17px;padding:0 0 0 1.5rem}@media screen and (max-width:1050px){#cooked-welcome-screen ul.cooked-whatsnew-pro{width:100%;margin-top:0}#cooked-welcome-screen ul.cooked-whatsnew-pro:first-child{margin-top:10px}#cooked-welcome-screen ul.cooked-whatsnew-pro li:first-child{border-top:1px solid #e5e5e5;padding-top:8px}#cooked-welcome-screen ul.cooked-whatsnew-pro:first-child li:first-child{border:none;padding-top:0}}@media screen and (max-width:870px){#cooked-welcome-screen .cooked-pro-features{width:90%}#cooked-welcome-screen .cooked-welcome-panel-column{padding-right:0}#cooked-welcome-screen .cooked-welcome-panel-column.cooked-welcome-panel-last{width:100%;padding-top:20px}#cooked-welcome-screen .about-wrap{margin-top:0}#cooked-welcome-screen .cooked-welcome-panel .cooked-welcome-panel-column li{display:block}}@media screen and (max-width:782px){#cooked-welcome-screen .cooked-pro-features{width:100%}#cooked-welcome-screen{padding:15px 40px 0 30px}#cooked-welcome-screen .cooked-badge{display:none}#cooked-welcome-screen .about-text,#cooked-welcome-screen .about-wrap h1{margin-right:0}#cooked-welcome-screen .cooked-welcome-panel-column h3 a{display:block;position:relative}}@media screen and (max-width:500px){#cooked-welcome-screen #cooked-welcome-panel img.cooked-welcome-banner{display:none}#cooked-welcome-screen .about-wrap h1{font-size:1.8rem}#cooked-welcome-screen .about-text{font-size:15px}}#cooked_recipe_settings #cooked-nutrition-label .cooked-nut-label,#cooked_recipe_settings .recipe-setting-block h3.cooked-settings-title{color:#0173aa}#cooked_recipe_settings .recipe-setting-block.cooked-danger h3.cooked-settings-title{color:#d44d1f}#cooked_recipe_settings .cooked-alert-block h3.cooked-settings-title{color:#c02a2a}#cooked-recipe-tabs{background:#fff}#cooked-recipe-tabs li{background:#454b52;color:#fff;color:rgba(255,255,255,.75);border-right:1px solid rgba(0,0,0,.15)}#cooked-recipe-tabs li:hover{background:#535a62;color:#fff;border-right:1px solid rgba(0,0,0,.15)}#cooked-recipe-tabs li.active{background:#fff;color:#333;border-right:1px solid #fff}#cooked-settings-tabs{background:#454b52}#cooked-settings-tabs a{color:#fff;text-decoration:none}#cooked-settings-tabs li{background:#454b52;color:#fff;color:rgba(255,255,255,.75)}#cooked-settings-tabs li:hover{background:#535a62;color:#fff}#cooked-settings-tabs li.active{background:#fff}#cooked-settings-tabs li.active a{color:#000}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image .cooked-direction-img-placeholder,#cooked-migration-progress.cooked-progress .cooked-progress-bar,#cooked-related-progress.cooked-progress .cooked-progress-bar,#cooked_recipe_settings .cooked-select-wrapper:before,#cooked_recipe_settings .cooked-tooltip-icon,#cooked_recipe_settings select{-webkit-transition:all .15s ease-out;-moz-transition:all .15s ease-out;transition:all .15s ease-out}#cooked-recipe-image-gallery .cooked-recipe-gallery-item{-webkit-transition:transform .15s ease-out;-moz-transition:all .15s ease-out;transition:transform .15s ease-out}#cooked-recipe-tabs li.cooked-loading{-webkit-transition:opacity .15s ease-out;-moz-transition:all .15s ease-out;transition:opacity .15s ease-out}@-webkit-keyframes cooked-wiggle{0%{-webkit-transform:scale(1)}50%{-webkit-transform:scale(1.03)}100%{-webkit-transform:scale(1)}}@-moz-keyframes cooked-wiggle{0%{-moz-transform:scale(1)}50%{-moz-transform:scale(1.03)}100%{-moz-transform:scale(1)}}@-o-keyframes cooked-wiggle{0%{-o-transform:scale(1)}50%{-o-transform:scale(1.03)}100%{-o-transform:scale(1)}}@keyframes cooked-wiggle{0%{transform:scale(1)}50%{transform:scale(1.03)}100%{transform:scale(1)}}@media screen and (max-width:1150px){#cooked-recipe-tabs li{font-size:.8rem;padding:0 1.2rem 0 .7rem}#cooked-recipe-tabs li .cooked-icon{display:none}}@media screen and (max-width:960px){#cooked_recipe_settings.stuck #cooked-recipe-tabs{margin-left:36px}}@media screen and (max-width:768px){#cooked_recipe_settings.stuck #cooked-recipe-tabs{position:absolute;top:0;left:0;width:100%;margin-left:0;box-shadow:0 3px 50px rgba(0,0,0,.25)}#cooked_recipe_settings .cooked-clearfix:has(>.cooked-setting-column-14){display:grid;grid-template-columns:1fr 1fr;gap:10px}#cooked_recipe_settings .cooked-setting-column-14{width:100%;float:none;padding-right:0}#cooked_recipe_settings .recipe-setting-block h3.cooked-settings-title{width:60%}}@media screen and (max-width:600px){#cooked_recipe_settings .recipe-setting-block h3.cooked-settings-title{display:flex;flex-wrap:wrap;align-items:center;row-gap:10px}#cooked_recipe_settings .recipe-setting-block:has(#cooked-nutrition-label) .cooked-setting-column-12{width:100%;float:none;padding-right:0}#cooked_recipe_settings .recipe-setting-block:has(#cooked-nutrition-label) p.cooked-measurement-inputs{display:flex;gap:10px}#cooked_recipe_settings .recipe-setting-block:has(#cooked-nutrition-label) p.cooked-measurement-inputs .cooked-measurement-column{width:50%;margin-right:0;flex:1}#cooked-ingredients-builder .cooked-ingredient-block{display:grid;grid-template-columns:1fr 1fr;column-gap:8px;row-gap:4px;padding-left:30px;padding-right:35px}#cooked-ingredients-builder .cooked-ingredient-block>div{display:block;float:none;padding-right:0}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-ingredient-amount{width:auto;grid-column:1}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-ingredient-measurement{width:auto;grid-column:2}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-ingredient-name{width:auto;grid-column:1/-1;padding-right:40px}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-substitution-fields{width:auto;float:none;clear:none;grid-column:1/-1}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-heading-name{grid-column:1/-1}#cooked_recipe_settings .cooked-ingredient-headers{display:grid;grid-template-columns:1fr 1fr;padding-left:30px;padding-right:35px}#cooked_recipe_settings .cooked-ingredient-headers span{display:block;float:none}#cooked_recipe_settings .cooked-layout-save-default{margin:0}#cooked_recipe_settings .cooked-ingredient-headers span.cooked-ingredient-header-amount{width:auto}#cooked_recipe_settings .cooked-ingredient-headers span.cooked-ingredient-header-measurement{width:auto}#cooked_recipe_settings .cooked-ingredient-headers span.cooked-ingredient-header-item{width:auto;grid-column:1/-1;padding-top:2px}#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded>.cooked-substitution-fields{display:grid;grid-template-columns:1fr 1fr;column-gap:8px;row-gap:4px}#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded>.cooked-substitution-fields>div{display:block;float:none;padding-right:0}#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded>.cooked-substitution-fields .cooked-ingredient-amount,#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded>.cooked-substitution-fields .cooked-ingredient-measurement{width:100%;display:block}#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded>.cooked-substitution-fields .cooked-ingredient-name{width:auto;grid-column:1/-1;padding-top:0}#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded>.cooked-substitution-fields .cooked-sub-label{grid-column:1/-1}#cooked-ingredients-builder .cooked-ingredient-block{position:relative}#cooked-ingredients-builder .cooked-ingredient-block .cooked-show-substitution{top:60px;right:42px}#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded .cooked-show-substitution,#cooked-ingredients-builder .cooked-ingredient-block.cooked-has-substitution .cooked-show-substitution,#cooked-ingredients-builder .cooked-ingredient-block:hover .cooked-show-substitution{right:42px}#cooked-ingredients-builder .cooked-ingredient-block .cooked-delete-ingredient{right:11px}}@media screen and (max-width:500px){#cooked_recipe_settings .cooked-clearfix:has(>.cooked-setting-column-14){grid-template-columns:1fr}#cooked_recipe_settings.stuck #cooked-recipe-tabs{width:100%}}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-substitution-fields{width:100%;float:left;clear:both;margin-top:10px;padding-top:10px;padding-right:0;padding-left:0;margin-left:0;box-sizing:border-box;padding-bottom:5px;position:relative}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-substitution-fields .cooked-ingredient-amount{width:15%}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-substitution-fields .cooked-ingredient-measurement{width:20%}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-substitution-fields .cooked-ingredient-name{width:65%}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-substitution-fields .cooked-sub-label{color:#7c7c7c;font-size:14px;font-weight:300}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-substitution-fields>div{display:inline-block;float:left;box-sizing:border-box;padding-right:10px}.cooked-bulk-add-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.6);z-index:100100;display:flex;align-items:center;justify-content:center}.cooked-bulk-add-modal{background:#fff;border-radius:6px;width:600px;max-width:90vw;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 5px 30px rgba(0,0,0,.3)}.cooked-bulk-add-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #ddd}.cooked-bulk-add-header h2{margin:0;font-size:18px;line-height:1.3}.cooked-bulk-add-close{font-size:24px;text-decoration:none;color:#666;line-height:1;padding:0 4px}.cooked-bulk-add-close:hover{color:#d63638}.cooked-bulk-add-body{padding:20px;overflow-y:auto;flex:1}.cooked-bulk-add-help{margin-top:0;color:#666;font-size:13px}#cooked-bulk-add-textarea{width:100%;min-height:160px;font-size:14px;padding:10px;box-sizing:border-box;resize:vertical}.cooked-bulk-add-preview{margin-top:16px}.cooked-bulk-add-preview-label{margin:0 0 8px;font-size:13px}.cooked-bulk-add-preview-notice-ingredients{display:none;margin:10px 0 0;font-size:12px;line-height:1.45;color:#b45309;font-weight:700;gap:6px;align-items:flex-start}#cooked-bulk-add-preview[data-bulk-type=ingredients] .cooked-bulk-add-preview-notice-ingredients{display:flex}.cooked-bulk-add-preview-notice-mark{flex-shrink:0;font-weight:700;line-height:1.45;color:#b45309}.cooked-bulk-add-preview-notice-text{flex:1;min-width:0;font-weight:700;color:#b45309}.cooked-bulk-add-preview-list{max-height:200px;overflow-y:auto;border:1px solid #ddd;border-radius:4px;padding:4px}.cooked-bulk-add-preview-row{display:flex;align-items:center;gap:8px;padding:4px 6px;border-bottom:1px solid #f0f0f0}.cooked-bulk-add-preview-row:last-child{border-bottom:0}.cooked-bulk-add-heading-toggle{display:flex;align-items:center;gap:4px;white-space:nowrap;font-size:12px;color:#666;cursor:pointer;flex-shrink:0}.cooked-bulk-add-heading-toggle input[type=checkbox]{margin:0}.cooked-bulk-add-preview-text{flex:1;border:1px solid #ddd;border-radius:3px;padding:4px 8px;font-size:13px}.cooked-bulk-add-preview-row:not(.cooked-bulk-add-preview-row-ingredient).cooked-bulk-add-is-heading .cooked-bulk-add-preview-text{font-weight:700}.cooked-bulk-add-preview-row-ingredient .cooked-bulk-add-heading-line-wrap{display:none;flex:1;align-items:center;gap:8px;min-width:0}.cooked-bulk-add-preview-row-ingredient.cooked-bulk-add-is-heading .cooked-bulk-add-heading-line-wrap{display:flex}.cooked-bulk-add-preview-row-ingredient.cooked-bulk-add-is-heading .cooked-bulk-add-parsed-amount,.cooked-bulk-add-preview-row-ingredient.cooked-bulk-add-is-heading .cooked-bulk-add-parsed-name,.cooked-bulk-add-preview-row-ingredient.cooked-bulk-add-is-heading .cooked-bulk-add-parsed-unit{display:none}.cooked-bulk-add-heading-line-label{flex-shrink:0;font-size:12px;font-weight:600;color:#666}.cooked-bulk-add-preview-row-ingredient .cooked-bulk-add-heading-line-wrap .cooked-bulk-add-preview-text{flex:1;min-width:0;border:1px solid #ddd;border-radius:3px;padding:4px 8px;font-size:13px;font-weight:600}.cooked-bulk-add-preview-header{display:flex;align-items:center;gap:8px;padding:4px 6px 6px;border-bottom:1px solid #ddd;font-size:11px;font-weight:600;color:#999;text-transform:uppercase;letter-spacing:.5px}.cooked-bulk-add-col-heading{flex-shrink:0;width:108px}.cooked-bulk-add-col-amount{width:72px;flex-shrink:0}.cooked-bulk-add-col-unit{width:108px;flex-shrink:0}.cooked-bulk-add-col-name{flex:1}.cooked-bulk-add-parsed-amount{width:72px;flex-shrink:0;border:1px solid #ddd;border-radius:3px;padding:4px 6px;font-size:13px;text-align:center}.cooked-bulk-add-parsed-unit{width:108px;flex-shrink:0;border:1px solid #ddd;border-radius:3px;padding:4px 6px;font-size:13px}.cooked-bulk-add-parsed-name{flex:1;border:1px solid #ddd;border-radius:3px;padding:4px 6px;font-size:13px}.cooked-bulk-add-footer{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:14px 20px;border-top:1px solid #ddd}.cooked-bulk-add-footer .button,.cooked-bulk-add-footer .button.button-primary{float:none;margin:0;vertical-align:middle}.cooked-bulk-add-spinner{float:none!important;margin:0!important}#cooked_allergens .cooked-allergens-checkboxes{display:flex;flex-direction:column;gap:8px}#cooked_allergens .cooked-allergen-checkbox{display:flex;align-items:center;gap:8px;cursor:pointer}#cooked_allergens .cooked-allergen-checkbox input[type=checkbox]{margin:0}#cooked_allergens .cooked-allergen{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}#cooked_allergens .cooked-allergen .cooked-icon{font-size:1.5rem;line-height:1}#cooked_allergens .cooked-allergen,#cooked_allergens .cooked-allergen .cooked-icon{color:rgba(0,0,0,.4)}#cooked_allergens .cooked-allergen-label{font-size:13px;color:#333} \ No newline at end of file +.cooked-clearfix:after{content:"";display:table;clear:both}#post-body-content #postdivrich{display:none}body.post-type-cp_recipe .wp-list-table tfoot th.check-column,body.post-type-cp_recipe .wp-list-table thead th.check-column{padding:15px 0 0 7px}body.post-type-cp_recipe .wp-list-table tfoot td,body.post-type-cp_recipe .wp-list-table tfoot th,body.post-type-cp_recipe .wp-list-table thead td,body.post-type-cp_recipe .wp-list-table thead th{padding:10px 10px}body.post-type-cp_recipe .wp-list-table tfoot th.sortable a,body.post-type-cp_recipe .wp-list-table tfoot th.sorted a,body.post-type-cp_recipe .wp-list-table thead th.sortable a,body.post-type-cp_recipe .wp-list-table thead th.sorted a{padding-left:0;padding-right:0}body.post-type-cp_recipe .wp-list-table tbody th.check-column{padding:15px 0 0 10px}body.post-type-cp_recipe .wp-list-table tbody td,body.post-type-cp_recipe .wp-list-table tbody th{padding:12px 10px}body.post-type-cp_recipe .wp-list-table tbody td.column-title strong{margin-top:6px}body.post-type-cp_recipe .wp-list-table tbody td.column-title strong .row-title{padding-top:10px;font-size:1rem!important}th.column-featured_image{width:50px;text-align:center}td.column-featured_image{width:50px;text-align:center}.cooked-admin-recipes-list-image img{width:49px;height:auto;border-radius:3px;position:relative;top:3px}body.post-type-cp_recipe #titlediv #title{box-shadow:none;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;padding:0 12px;font-weight:400;font-size:19px;letter-spacing:0;height:44px}body.post-type-cp_recipe #titlediv #title-prompt-text{font-size:15px;color:#888;font-weight:400;letter-spacing:0;padding:12px 15px}body.post-type-cp_recipe .mce-fullscreen{z-index:100100!important}#cooked-settings-prewrap{display:flex;justify-content:center}#cooked-settings-wrap{position:relative;border-radius:10px;margin:40px 40px 40px 20px;max-width:1200px;min-width:1000px;background:#fff;box-shadow:0 4px 4px rgba(0,0,0,.05),0 8px 8px rgba(0,0,0,.05),0 32px 32px rgba(0,0,0,.05),0 64px 64px rgba(0,0,0,.05)}#cooked-settings-wrap.is-stuck{position:static!important}#cooked-settings-wrap .cooked-settings-update-button{position:absolute;top:30px;right:30px}#cooked-settings-wrap .cooked-settings-update-button>.button-primary{box-shadow:none;border:none}#cooked-settings-wrap.is-stuck .cooked-settings-update-button{position:fixed;top:35px;right:11px;z-index:100001}#cooked-recipe-tabs{list-style:none;padding:0;margin:0;position:absolute;top:0;left:0;width:100%}#cooked-recipe-tabs li{font-size:.9rem;line-height:3rem;height:3rem;font-weight:500;display:inline-block;padding:0 1.25rem;margin:0;color:#fff}#cooked-recipe-tabs li:hover{cursor:pointer}#cooked-recipe-tabs li.active,#cooked-recipe-tabs li.active:hover{cursor:default}#cooked-recipe-tabs li:last-child{border-right:none!important}#cooked-recipe-tabs li.cooked-loading{opacity:0;background:0 0;color:#fff;position:absolute;top:0;right:0;cursor:default;font-size:14px;width:40px;height:40px;text-align:center;display:block;line-height:40px;padding:0}#cooked-recipe-tabs li.cooked-loading:hover{background:0 0;color:#fff}#cooked_recipe_settings.cooked-loading #cooked-recipe-tabs li.cooked-loading{opacity:1}#cooked_recipe_settings .inside{padding-top:60px;margin:0}#cooked_recipe_settings.stuck #cooked-recipe-tabs{position:fixed;width:auto;top:32px;left:23px;z-index:100000;margin-left:160px;box-shadow:0 3px 50px rgba(0,0,0,.25)}#cooked_recipe_settings.stuck #cooked-recipe-tabs li.cooked-loading{right:160px}.cooked-recipe-tab-content-wrapper .cooked-recipe-tab-content{display:none}.cooked-recipe-tab-content-wrapper .cooked-recipe-tab-content:first-child{display:block}.cooked-left{float:left;display:inline-block;width:auto}.cooked-right{float:right;display:inline-block;width:auto}#cooked_field--cooked_pro_license_key{font-family:monospace}#cooked_recipe_settings .cooked-bm-5{margin-bottom:5px!important}#cooked_recipe_settings .cooked-bm-10{margin-bottom:10px!important}#cooked_recipe_settings .cooked-tm-10{margin-top:10px!important}#cooked_recipe_settings .cooked-bm-15{margin-bottom:15px!important}#cooked_recipe_settings .cooked-bm-20{margin-bottom:20px!important}#cooked_recipe_settings .cooked-bm-30{margin-bottom:30px!important}#cooked_recipe_settings .cooked-bm-5-up{margin-bottom:-5px!important}#cooked_recipe_settings .cooked-bm-10-up{margin-bottom:-10px!important}#cooked_recipe_settings .cooked-bm-15-up{margin-bottom:-15px!important}#cooked_recipe_settings .cooked-bm-20-up{margin-bottom:-20px!important}#cooked_recipe_settings .cooked-bm-30-up{margin-bottom:-30px!important}#cooked_recipe_settings .cooked-hr{border:none;border-top:2px solid #ddd;margin:10px 0 0 0;padding:15px 0 0 0}#cooked_recipe_settings .cooked-conditional-hidden{display:none}#cooked_recipe_settings .cooked-recipe-tab-content{padding:23px 30px 15px 30px}#cooked_recipe_settings .recipe-setting-block{margin:0 0 20px;width:100%}#cooked_recipe_settings .recipe-setting-block p{font-size:.9rem;line-height:1.5rem;margin:0 0 1rem;padding:0}#cooked_recipe_settings .recipe-setting-block p.cooked-padded{line-height:1.75rem;font-size:.85rem}#cooked_recipe_settings .recipe-setting-block .cooked-conditional-hidden{padding:0}#cooked_recipe_settings textarea{width:100%;height:75px;padding:15px;box-sizing:border-box;position:relative;top:5px}#cooked_recipe_settings .recipe-setting-block>label.cooked-select-label{top:5px}#cooked_recipe_settings select{cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;box-shadow:none;border-radius:3px;padding:0 45px 0 9px;line-height:31px;height:33px;box-sizing:border-box;background:#fff}#cooked_recipe_settings .cooked-select-wrapper{background:#fff;position:relative;width:auto;display:inline-block;border-radius:3px;margin:0 5px 0 0}#cooked_recipe_settings .cooked-select-wrapper select{margin:0;background:0 0;position:relative}#cooked_recipe_settings .cooked-select-wrapper:before{font-family:CookedIcons;display:block;width:15px;height:15px;line-height:14px;color:#000;font-size:14px;content:"\f00b";position:absolute;right:12px;top:11px;color:rgba(0,0,0,.3)}#cooked_recipe_settings .cooked-select-wrapper:hover:before{color:#000}#cooked_recipe_settings .cooked-select-wrapper:hover select{border-color:#ccc}#cooked_recipe_settings .cooked-checkbox-radio-label{position:relative;left:1px}#cooked_recipe_settings .recipe-setting-block input[type=checkbox],#cooked_recipe_settings .recipe-setting-block input[type=radio]{margin-top:0}#cooked_recipe_settings .recipe-setting-block input[type=number]{width:65px}#cooked_recipe_settings .recipe-setting-block input[type=password],#cooked_recipe_settings .recipe-setting-block input[type=text]{width:75%}#cooked_recipe_settings .recipe-setting-block input[type=number],#cooked_recipe_settings .recipe-setting-block input[type=password],#cooked_recipe_settings .recipe-setting-block input[type=text]{margin:0 6px 5px 0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;height:33px;box-shadow:none;position:relative;top:2px;padding:0 10px}#cooked_recipe_settings .recipe-setting-block p.cooked-measurement-inputs .cooked-measurement-column{width:43%;margin-right:3%;display:inline-block}#cooked_recipe_settings .recipe-setting-block p.cooked-measurement-inputs .cooked-select-wrapper,#cooked_recipe_settings .recipe-setting-block p.cooked-measurement-inputs input,#cooked_recipe_settings .recipe-setting-block p.cooked-measurement-inputs label{width:100%;display:block}#cooked_recipe_settings .recipe-setting-block p.cooked-measurement-inputs .cooked-select-wrapper select{width:100%}#cooked_recipe_settings .recipe-setting-block input[type=number],#cooked_recipe_settings .recipe-setting-block input[type=password].cooked-small-textfield,#cooked_recipe_settings .recipe-setting-block input[type=text].cooked-small-textfield{width:150px}#cooked_recipe_settings small{display:block;line-height:1.5;font-size:12px;color:#888;padding:10px 0 0}#cooked_recipe_settings .recipe-setting-block em{color:#aaa}#cooked_recipe_settings .recipe-setting-block,#cooked_recipe_settings .recipe-setting-block .cooked-repositioned{display:block;position:relative;box-sizing:border-box;line-height:1}#cooked_recipe_settings .recipe-setting-block .cooked-repositioned{padding-left:157px}#cooked_recipe_settings .recipe-setting-block h3.cooked-settings-title{width:70%;position:relative;z-index:2;padding:.5rem 0 .5rem;margin:0;font-size:1rem;line-height:1.5rem;font-weight:600}#cooked_recipe_settings .recipe-setting-block strong.cooked-heading{font-size:14px}#cooked_recipe_settings .recipe-setting-block .cooked-repositioned>label{position:absolute;top:1px;left:0;width:147px;cursor:default;font-weight:600}#cooked_recipe_settings .recipe-setting-block .cooked-tooltip-icon{color:#aaa;cursor:help;display:inline-block;margin-left:10px}#cooked_recipe_settings .recipe-setting-block .cooked-tooltip-icon:hover{color:#eee}#cooked_recipe_settings .recipe-setting-block .cooked-repositioned>.cooked-checkbox-radio-label{top:7px}#cooked_recipe_settings .recipe-setting-block .cooked-repositioned>label{top:auto;height:33px;line-height:30px;padding:0;width:130px}#cooked_recipe_settings .recipe-setting-block .cooked-repositioned:first-child label:first-child{padding-top:12px;margin-top:-12px}#cooked_recipe_settings .recipe-setting-block .cooked-repositioned span.cooked-padded{padding:0 0 15px;display:block;line-height:1.9}#cooked_recipe_settings .recipe-setting-block .wp-picker-container .cooked-color-field.wp-color-picker{padding:5px;height:25px;top:0;margin:0;width:74px;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;border:1px solid #ccc}#cooked_recipe_settings .recipe-setting-block input[type=text].cooked-shortcode-field{font-family:monospace;background:#f5f5f5;font-size:12px}#cooked_recipe_settings .cooked-banner-block{margin:30px -30px -17px;display:block;box-sizing:content-box;padding:10px 30px 15px;background:#f9f9f9;width:100%;border-top:1px solid #e5e5e5}#cooked_recipe_settings .cooked-advanced-options-hr{border:none;border-bottom:1px solid #ddd;height:1px;padding:10px 0 0 0;margin:0 0 20px}#cooked_recipe_settings .recipe-setting-block input.cooked-time-picker{top:0;width:100%;padding-right:70%;margin:0 0 3px}#cooked_recipe_settings .recipe-setting-block .cooked-time-picker-text{position:absolute;bottom:13px;right:40px;color:#888}#cooked_recipe_settings .cooked-alert-block{background:#fffbdc;-moz-border-radius:10px;-webkit-border-radius:10px;border-radius:10px;margin:10px 0 15px;padding:10px 22px 2px;border:2px solid #ece8c4}#cooked_recipe_settings .recipe-setting-block.cooked-template{display:none}#cooked_recipe_settings ul.cooked-admin-ul{font-size:.9rem;margin:0 0 1rem 2rem;list-style:disc}#cooked_recipe_settings ul.cooked-admin-ul li{font-size:.9rem;padding:0;margin:0 0 .5rem}#cooked_recipe_settings .cooked-html-block{background:#fff;width:auto;min-width:300px;display:inline-block;padding:.5rem 1.3rem .25rem;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,.15)}#cooked_recipe_settings .recipe-setting-block .cooked-html-block h3.cooked-settings-title{width:100%;color:#333}#cooked_recipe_settings .recipe-setting-block .cooked-html-block.valid{background:#fff;border:2px solid #0aa780;box-shadow:none;border-radius:5px}#cooked_recipe_settings .recipe-setting-block .cooked-html-block.valid>.cooked-settings-title{color:#0aa780}#cooked_recipe_settings .recipe-setting-block .cooked-html-block.expired,#cooked_recipe_settings .recipe-setting-block .cooked-html-block.invalid{border:2px solid #ca4a20}#cooked_recipe_settings .recipe-setting-block .cooked-html-block.expired>.cooked-settings-title,#cooked_recipe_settings .recipe-setting-block .cooked-html-block.invalid>.cooked-settings-title{color:#ca4a20}#cooked-directions-builder .cooked-direction-block.cooked-expanded>.cooked-heading-name,#cooked-directions-builder .cooked-direction-block.cooked-has-heading-element>.cooked-heading-name,#cooked-directions-builder .cooked-direction-block:hover>.cooked-heading-name,#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded>.cooked-heading-name,#cooked-ingredients-builder .cooked-ingredient-block.cooked-has-heading-element>.cooked-heading-name,#cooked-ingredients-builder .cooked-ingredient-block:hover>.cooked-heading-name{padding-right:32px}#cooked-directions-builder .cooked-direction-block .cooked-show-heading-element,#cooked-ingredients-builder .cooked-ingredient-block .cooked-show-heading-element{opacity:0;cursor:pointer;font-size:14px;position:absolute;top:14px;right:35px}#cooked-directions-builder .cooked-direction-block .cooked-show-heading-element .cooked-icon,#cooked-ingredients-builder .cooked-ingredient-block .cooked-show-heading-element .cooked-icon{color:#888}#cooked-directions-builder .cooked-direction-block.cooked-expanded .cooked-show-heading-element,#cooked-directions-builder .cooked-direction-block.cooked-has-heading-element .cooked-show-heading-element,#cooked-directions-builder .cooked-direction-block:hover .cooked-show-heading-element,#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded .cooked-show-heading-element,#cooked-ingredients-builder .cooked-ingredient-block.cooked-has-heading-element .cooked-show-heading-element,#cooked-ingredients-builder .cooked-ingredient-block:hover .cooked-show-heading-element{opacity:1}#cooked-directions-builder .cooked-direction-block .cooked-show-heading-element .cooked-icon:hover,#cooked-directions-builder .cooked-direction-block.cooked-expanded .cooked-show-heading-element .cooked-icon,#cooked-directions-builder .cooked-direction-block.cooked-has-heading-element .cooked-show-heading-element .cooked-icon,#cooked-ingredients-builder .cooked-ingredient-block .cooked-show-heading-element .cooked-icon:hover,#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded .cooked-show-heading-element .cooked-icon,#cooked-ingredients-builder .cooked-ingredient-block.cooked-has-heading-element .cooked-show-heading-element .cooked-icon{color:#0685ba}#cooked-directions-builder .cooked-direction-block>.cooked-heading-element,#cooked-ingredients-builder .cooked-ingredient-block>.cooked-heading-element{display:none;width:65%;float:right;margin-top:4px;padding-right:32px}#cooked-directions-builder .cooked-direction-block>.cooked-heading-element select,#cooked-ingredients-builder .cooked-ingredient-block>.cooked-heading-element select{color:#0685ba;width:100%}#cooked-directions-builder .cooked-direction-block>.cooked-heading-element label,#cooked-ingredients-builder .cooked-ingredient-block>.cooked-heading-element label{font-weight:600}#cooked-directions-builder .cooked-direction-block.cooked-expanded>.cooked-heading-element,#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded>.cooked-heading-element{display:block}#cooked-ingredients-builder .cooked-ingredient-block .cooked-show-substitution{opacity:0;cursor:pointer;font-size:14px;position:absolute;top:15px;right:58px}#cooked-ingredients-builder .cooked-ingredient-block .cooked-show-substitution .cooked-icon{color:#888}#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded .cooked-show-substitution,#cooked-ingredients-builder .cooked-ingredient-block.cooked-has-substitution .cooked-show-substitution,#cooked-ingredients-builder .cooked-ingredient-block:hover .cooked-show-substitution{opacity:1;right:50px}#cooked-ingredients-builder .cooked-ingredient-block .cooked-show-substitution .cooked-icon:hover,#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded .cooked-show-substitution .cooked-icon,#cooked-ingredients-builder .cooked-ingredient-block.cooked-has-substitution .cooked-show-substitution .cooked-icon{color:#0685ba}#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded>.cooked-ingredient-name,#cooked-ingredients-builder .cooked-ingredient-block.cooked-has-substitution>.cooked-ingredient-name,#cooked-ingredients-builder .cooked-ingredient-block:hover>.cooked-ingredient-name{padding-right:32px}#cooked_recipe_settings .switchery-small,body.post-type-cp_recipe .switchery-small{height:1rem;width:1.75rem;border-radius:1rem;margin-right:.4rem}#cooked_recipe_settings .switchery-small>small,body.post-type-cp_recipe .switchery-small>small{width:1rem;height:1rem;padding:0}.cooked-tooltip-buttons .cooked-icon-spin{margin-top:10px;font-size:15px}.cooked-tooltip-buttons .cooked-saved-default{height:28px;line-height:28px;font-weight:700;font-size:15px;color:#888}.cooked-progress{display:none;position:relative;background:#eee;width:100%;height:6px;padding:0;border-radius:3px;margin:10px 0 0 0}.cooked-progress-text{display:none;font-size:10px;color:#aaa;padding:2px 0 5px}.cooked-progress-text.cooked-active,.cooked-progress.cooked-active{display:block}.cooked-progress .cooked-progress-bar{display:block;position:absolute;background:#0085ba;width:0%;height:6px;top:0;left:0;border-radius:3px}#cooked-import-progress.cooked-progress,#cooked-migration-progress.cooked-progress{background:#ccc;margin:25px 0 0 0;border-radius:6px;height:12px;max-width:600px}#cooked-import-progress.cooked-progress .cooked-progress-bar,#cooked-migration-progress.cooked-progress .cooked-progress-bar{border-radius:6px;height:12px}#cooked-import-progress-text.cooked-progress-text,#cooked-migration-progress-text.cooked-progress-text{font-size:11px;color:#888;max-width:600px}#cooked-import-completed,#cooked-migration-completed{display:none}#cooked-import-completed.cooked-active,#cooked-migration-completed.cooked-active{display:block}#cooked-csv-import-progress.cooked-progress{background:#ccc;margin:25px 0 0 0;border-radius:6px;height:12px;max-width:600px}#cooked-csv-import-progress.cooked-progress .cooked-progress-bar{border-radius:6px;height:12px}#cooked-csv-import-progress-text.cooked-progress-text{font-size:11px;color:#888;max-width:600px}#cooked-csv-import-completed{display:none}#cooked-csv-import-completed.cooked-active{display:block}#cooked-csv-import-form{max-width:100%;box-sizing:border-box}#cooked-csv-import-form input[type=file]{max-width:100%;box-sizing:border-box;width:100%}#cooked-csv-import-errors{max-width:100%;box-sizing:border-box;word-wrap:break-word}#cooked-import-progress.cooked-progress,#cooked-migration-progress.cooked-progress,#cooked-related-progress.cooked-progress{background:#ccc;margin:25px 0 0 0;border-radius:6px;height:12px;max-width:600px}#cooked-import-progress.cooked-progress .cooked-progress-bar,#cooked-migration-progress.cooked-progress .cooked-progress-bar,#cooked-related-progress.cooked-progress .cooked-progress-bar{border-radius:6px;height:12px}#cooked-import-progress-text.cooked-progress-text,#cooked-migration-progress-text.cooked-progress-text,#cooked-related-progress-text.cooked-progress-text{font-size:11px;color:#888;max-width:600px}#cooked-import-completed,#cooked-migration-completed,#cooked-related-completed{display:none}#cooked-import-completed.cooked-active,#cooked-migration-completed.cooked-active,#cooked-related-completed.cooked-active{display:block}.cooked-related-status{margin:0 0 12px 0;padding:8px 12px;font-size:13px;line-height:1.4;border-radius:4px;border-left:3px solid #00a878;background:#f0f9f6;color:#2d4a42;max-width:480px}#cooked-related-last-done.cooked-related-status{border-left-color:#8c9b99;background:#f6f8f7;color:#5c6b69}#cooked_recipe_settings .cooked-layout-save-default{position:relative;top:-2px;z-index:10;margin:0 0 0 15px;padding:0 8px 1px}#cooked_recipe_settings .cooked-layout-load-default{position:relative;top:-2px;z-index:10;margin:0 0 0 10px;padding:0 8px 1px}body .button.button-cooked-reset{color:#999}body .button.button-cooked-reset:hover{color:#555}#cooked_recipe_settings .cooked-ingredient-headers{display:block;padding:5px 25px 0 35px}#cooked_recipe_settings .cooked-ingredient-headers span{font-size:.7rem;font-weight:700;letter-spacing:.03rem;text-transform:uppercase;color:#0085ba;box-sizing:border-box;display:inline-block;float:left}#cooked_recipe_settings .cooked-ingredient-headers span.cooked-ingredient-header-amount{width:15%}#cooked_recipe_settings .cooked-ingredient-headers span.cooked-ingredient-header-measurement{width:20%}#cooked_recipe_settings .cooked-ingredient-headers span.cooked-ingredient-header-item{width:65%}#cooked-ingredients-builder{margin:0 0 20px}#cooked-ingredients-builder .cooked-ingredient-block{display:block;padding:5px 45px 5px 35px}#cooked-ingredients-builder .cooked-ingredient-block:hover{background:#eee}#cooked-ingredients-builder .cooked-ingredient-block input[type=number],#cooked-ingredients-builder .cooked-ingredient-block input[type=text],#cooked-ingredients-builder .cooked-ingredient-block select{margin:0;display:block;top:0}#cooked-ingredients-builder .cooked-ingredient-block>div{display:inline-block;float:left;box-sizing:border-box;padding-right:10px}#cooked-ingredients-builder .cooked-ingredient-block>div input[type=text]{width:100%}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-ingredient-amount{width:15%}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-ingredient-measurement{width:20%}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-ingredient-measurement .cooked-select-wrapper{width:100%}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-ingredient-measurement .cooked-select-wrapper select{width:100%}#cooked-ingredients-builder .cooked-ingredient-block .cooked-substitution-fields>.cooked-ingredient-measurement .cooked-select-wrapper{width:100%}#cooked-ingredients-builder .cooked-ingredient-block .cooked-substitution-fields>.cooked-ingredient-measurement .cooked-select-wrapper select{width:100%}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-ingredient-name{width:65%}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-heading-name{width:100%}#cooked-ingredients-builder .cooked-ingredient-block>div small.cooked-input-ex{display:block;padding:5px 0 0 2px}#cooked-ingredients-builder .cooked-ingredient-block{margin:0}#cooked-ingredients-builder .cooked-ingredient-block i.cooked-icon-drag{cursor:move;color:#ccc;font-size:32px;position:absolute;top:5px;left:1px;touch-action:none}#cooked-ingredients-builder .cooked-ingredient-block:hover i.cooked-icon-drag{color:#888}#cooked-ingredients-builder .cooked-ingredient-block.ui-sortable-helper{background:#f5f5f5;box-shadow:0 10px 20px rgba(0,0,0,.15);-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}#cooked-ingredients-builder .cooked-ingredient-block.ui-sortable-placeholder{visibility:visible!important;background:#eee;display:block;width:100%;height:43px;border-top:6px solid #fff;border-bottom:6px solid #fff}#cooked-ingredients-builder .cooked-ingredient-block .cooked-delete-ingredient{cursor:pointer;font-size:14px;position:absolute;top:14px;right:11px}#cooked-ingredients-builder .cooked-ingredient-block .cooked-delete-ingredient .cooked-icon{color:#fff}#cooked-ingredients-builder .cooked-ingredient-block:hover .cooked-delete-ingredient .cooked-icon{color:#888}#cooked-ingredients-builder .cooked-ingredient-block .cooked-delete-ingredient .cooked-icon:hover{color:#de2020}#cooked-ingredients-builder .cooked-ingredient-block.cooked-ingredient-heading{margin:10px 0}#cooked-ingredients-builder .cooked-ingredient-block.cooked-ingredient-heading>div input[type=text]{padding:0 7px;font-size:16px;background:#f9f9f9;font-weight:600}#cooked-directions-builder{margin:0 0 20px}#cooked-directions-builder .cooked-direction-block{display:block;padding:15px 25px 15px 35px}#cooked-directions-builder .cooked-direction-block.cooked-direction-heading{padding:5px 25px 5px 35px}#cooked-directions-builder .cooked-direction-block:hover{background:#f5f5f5}#cooked-directions-builder .cooked-direction-block.cooked-direction-heading:hover{background:#eee}#cooked-directions-builder .cooked-direction-block textarea{margin:0;display:block;height:250px;top:0}#cooked-directions-builder .cooked-direction-block>div{display:inline-block;float:left;box-sizing:border-box;padding-right:15px}#cooked-directions-builder .cooked-direction-block>div:last-child{padding:0}#cooked-directions-builder .cooked-direction-block>div input[type=text]{width:100%}#cooked-directions-builder .cooked-direction-block .mce-statusbar{display:none}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image{width:135px;position:absolute;top:15px;left:35px}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image img{cursor:pointer;display:none;border-radius:3px}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image .cooked-direction-img-placeholder{box-sizing:border-box;cursor:pointer;border-radius:3px;width:100%;padding-bottom:100%;background:0 0;border:2px dashed #ddd;border-radius:3px;margin-top:15px}#cooked-directions-builder .cooked-direction-block:hover>.cooked-direction-image .cooked-direction-img-placeholder{background:0 0}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image .cooked-direction-img-placeholder:hover{background:#ddd;border:2px solid #ddd}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image .direction-image-button{width:100%;text-align:center}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image .remove-image-button{display:none;position:absolute;top:43px;right:15px}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image.cooked-has-image .remove-image-button{display:inline-block;padding:10px;background:rgba(0,0,0,.25);color:#fff}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image.cooked-has-image .remove-image-button:hover{background:#ff0100}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image.cooked-has-image img{width:100%;height:auto;display:block;margin:15px 0 0}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image.cooked-has-image .cooked-direction-img-placeholder{display:none}#cooked-directions-builder .cooked-direction-block>.cooked-direction-content{width:100%;padding-left:175px;padding-right:175px}#cooked-directions-builder .cooked-direction-block .cooked-direction-number{opacity:.4;position:absolute;display:inline-block;font-weight:800;top:17px;left:35px;font-size:1.25rem;line-height:1}#cooked-directions-builder .cooked-direction-block.cooked-direction-has-number>.cooked-direction-image{left:65px}#cooked-directions-builder .cooked-direction-block.cooked-direction-has-number>.cooked-direction-content{padding-left:175px;padding-right:175px}#cooked-directions-builder .cooked-direction-block.cooked-direction-has-number-wide>.cooked-direction-content{padding-left:185px;padding-right:175px}#cooked-directions-builder .cooked-direction-block>.cooked-heading-name{width:100%}#cooked-directions-builder .cooked-direction-block{margin:0}#cooked-directions-builder .cooked-direction-block i.cooked-icon-drag{cursor:move;color:#ccc;font-size:32px;position:absolute;top:13px;left:1px;touch-action:none}#cooked-directions-builder .cooked-direction-block.cooked-direction-heading i.cooked-icon-drag{top:5px}#cooked-directions-builder .cooked-direction-block:hover i.cooked-icon-drag{color:#888}#cooked-directions-builder .cooked-direction-block.ui-sortable-helper{background:#f5f5f5;box-shadow:0 10px 20px rgba(0,0,0,.15);-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}#cooked-directions-builder .cooked-direction-block.ui-sortable-placeholder{visibility:visible!important;background:#eee;display:block;width:100%;height:280px;border-top:6px solid #fff;border-bottom:6px solid #fff}#cooked-directions-builder .cooked-direction-block.cooked-direction-heading.ui-sortable-placeholder{height:43px}#cooked-directions-builder .cooked-direction-block .cooked-delete-direction{cursor:pointer;font-size:14px;position:absolute;right:14px}#cooked-directions-builder .cooked-direction-block .cooked-delete-direction .cooked-icon{color:#fff}#cooked-directions-builder .cooked-direction-block:hover .cooked-delete-direction .cooked-icon{color:#888}#cooked-directions-builder .cooked-direction-block .cooked-delete-direction .cooked-icon:hover{color:#de2020}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video{width:135px;position:absolute;top:15px;right:25px}#cooked-directions-builder .cooked-direction-block.cooked-direction-heading{margin:10px 0}#cooked-directions-builder .cooked-direction-block.cooked-direction-heading>div input[type=text]{padding:0 7px;font-size:16px;background:#f9f9f9;font-weight:600;top:0;margin:0}#cooked-directions-builder .cooked-direction-block .cooked-delete-direction{top:16px}#cooked-directions-builder .cooked-direction-block.cooked-direction-heading .cooked-delete-direction{top:14px}.cooked-settings-image-field{position:relative;display:inline-block;max-width:150px}.cooked-settings-image-field .cooked-settings-image-preview-img{display:none;width:100%;height:auto;margin:10px 0 0;border-radius:3px}.cooked-settings-image-field .cooked-settings-image-remove{display:none;position:absolute;top:50px;right:0;padding:10px;background:rgba(0,0,0,.25);color:#fff;border-radius:3px}.cooked-settings-image-field.cooked-has-image .cooked-settings-image-remove{display:inline-block}.cooked-settings-image-field.cooked-has-image .cooked-settings-image-remove:hover{background:#ff0100}.cooked-settings-image-field.cooked-has-image .cooked-settings-image-preview-img{display:block}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video .cooked-direction-video-preview{display:none;width:100%;aspect-ratio:1/1;object-fit:cover;border-radius:3px;cursor:pointer}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video .cooked-direction-video-placeholder{box-sizing:border-box;cursor:pointer;border-radius:3px;width:100%;padding-bottom:100%;background:0 0;border:2px dashed #ddd;margin-top:15px}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video .cooked-direction-video-placeholder:hover{background:#ddd;border:2px solid #ddd}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video .direction-video-button{width:100%;text-align:center}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video.cooked-has-video .cooked-direction-video-placeholder{display:none}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video.cooked-has-video .cooked-direction-video-preview{display:block;width:100%;aspect-ratio:1/1;object-fit:cover;margin-top:15px}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video .remove-video-button{display:none;position:absolute;top:43px;right:15px}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video.cooked-has-video .remove-video-button{display:inline-block;padding:10px;background:rgba(0,0,0,.25);color:#fff}#cooked-directions-builder .cooked-direction-block>.cooked-direction-video.cooked-has-video .remove-video-button:hover{background:#ff0100}@media screen and (max-width:1440px){#cooked-directions-builder .cooked-direction-block:not(.cooked-direction-heading){display:grid;grid-template-columns:1fr 1fr;grid-template-areas:"content content" "image video";gap:10px;padding:15px 0 15px 0}#cooked-directions-builder .cooked-direction-block:not(.cooked-direction-heading)>.cooked-direction-content{grid-area:content;padding:14px 14px 0 14px!important;min-height:80px}#cooked-directions-builder .cooked-direction-block:not(.cooked-direction-heading)>.cooked-direction-image{grid-area:image;position:relative;width:auto;left:auto;top:auto;padding-left:15px;padding-right:0}#cooked-directions-builder .cooked-direction-block:not(.cooked-direction-heading)>.cooked-direction-video{grid-area:video;position:relative;width:auto;right:auto;top:auto}#cooked_recipe_settings .cooked-recipe-tab-content{padding:0 0 15px 0}#cooked-recipe-tabs{white-space:nowrap;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;top:0;width:100%}#cooked-recipe-tabs::-webkit-scrollbar{display:none}#cooked-recipe-tabs li{float:none}#cooked_recipe_settings.stuck #cooked-recipe-tabs{position:fixed;top:32px;left:0;width:100%;margin-left:0}}#cooked-recipe-image-gallery{margin:0 -1% 0}#cooked-recipe-image-gallery .cooked-recipe-gallery-item{position:relative;overflow:hidden;cursor:move;box-sizing:border-box;display:inline-block;width:18%;height:auto;padding:0;margin:0 1% 2% 1%}#cooked-recipe-image-gallery .cooked-recipe-gallery-item:hover{opacity:.9}#cooked-recipe-image-gallery .cooked-recipe-gallery-item img{width:100%;height:auto;border-radius:3px;display:block;margin:0;padding:0;border:none}#cooked-recipe-image-gallery .cooked-recipe-gallery-item.ui-sortable-helper,#cooked-recipe-image-gallery .cooked-recipe-gallery-item.ui-sortable-helper:hover{opacity:.75;box-shadow:0 10px 20px rgba(0,0,0,.15)}#cooked-recipe-image-gallery .cooked-recipe-gallery-item.ui-sortable-placeholder{width:18%;height:auto;visibility:visible!important;border-radius:3px;background:#ddd;display:inline-block}#cooked-recipe-image-gallery .cooked-recipe-gallery-item .remove-image-button{border-radius:0 3px 0 0;display:inline-block;position:absolute;top:0;right:0;padding:10px;background:rgba(0,0,0,.25);color:#fff}#cooked-recipe-image-gallery .cooked-recipe-gallery-item .remove-image-button:hover{background:#ff0100}#cooked-recipe-image-gallery .cooked-recipe-gallery-item .cooked-gallery-edit-button{border-radius:3px 0 0 0;display:inline-block;position:absolute;top:0;left:0;padding:10px;background:rgba(0,0,0,.25);color:#fff}#cooked-recipe-image-gallery .cooked-recipe-gallery-item .cooked-gallery-edit-button:hover{background:#0084bc}#cooked-recipe-image-gallery .cooked-recipe-gallery-item .cooked-gallery-item-title{position:absolute;left:0;bottom:0;box-sizing:border-box;width:100%;border-radius:0 0 3px 3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;padding:10px 10px 11px;text-align:center;background:#000;background:rgba(0,0,0,.3);color:#fff;font-size:12px}#cooked-recipe-image-gallery .cooked-recipe-gallery-item:hover .cooked-gallery-item-title{background:#000;background:rgba(0,0,0,.75)}#cooked-recipe-image-gallery .cooked-recipe-gallery-item.ui-sortable-helper{-webkit-animation:cooked-wiggle .4s infinite;-moz-animation:cooked-wiggle .4s infinite;-o-animation:cooked-wiggle .4s infinite;animation:cooked-wiggle .4s infinite}#cooked_recipe_settings label.cooked-nutrition-label{display:block;font-size:.7rem;letter-spacing:.03rem;font-weight:700;text-transform:uppercase;line-height:1;margin:0 0 7px}#cooked_recipe_settings #cooked-nutrition-label{line-height:1.6;font-size:1.1rem;border:1px solid #aaa;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;padding:5%;margin:0 0 2rem;font-family:Helvetica,Arial,sans-serif;container-name:nutrition-label;container-type:inline-size}#cooked_recipe_settings #cooked-nutrition-label h2{font-size:2.5rem;line-height:1;letter-spacing:0;font-weight:800;padding:0 0 .5rem 0;margin:0 0 .4rem;color:#333;border-bottom:1px solid #aaa}#cooked_recipe_settings #cooked-nutrition-label p.cooked-daily-value-text{padding:.5rem 0 0 0;margin:0;font-size:.9rem}#cooked_recipe_settings #cooked-nutrition-label ul.cooked-nut-servings p{font-size:1.5rem}#cooked_recipe_settings #cooked-nutrition-label ul{list-style:none;margin:0;padding:0}#cooked_recipe_settings #cooked-nutrition-label li{position:relative;margin:0;padding:0 0 3px 0;border-top:1px solid #aaa}#cooked_recipe_settings #cooked-nutrition-label li li{padding:3px 0 0;margin:3px 0 0}#cooked_recipe_settings #cooked-nutrition-label li:after{content:"";display:table;clear:both}#cooked_recipe_settings #cooked-nutrition-label li.no-after:after{content:none}#cooked_recipe_settings #cooked-nutrition-label li.cooked-nut-spacer{border:none;height:.6rem;padding:0;background:#333}#cooked_recipe_settings #cooked-nutrition-label ul li.cooked-nut-no-border,#cooked_recipe_settings #cooked-nutrition-label>ul>li:first-child{border:none}#cooked_recipe_settings #cooked-nutrition-label li ul{padding:0;margin:0 0 0 20px}#cooked_recipe_settings #cooked-nutrition-label li ul.cooked-right{margin:0}#cooked_recipe_settings #cooked-nutrition-label li ul.cooked-right li{border:none;padding:0;margin:0}#cooked_recipe_settings #cooked-nutrition-label li.cooked-calories ul.cooked-right li strong.cooked-nut-label{font-size:2.5rem;line-height:1}#cooked_recipe_settings #cooked-nutrition-label .cooked-calories{border-top:none;display:flex;justify-content:space-between;align-items:baseline;padding:0}#cooked_recipe_settings #cooked-nutrition-label .cooked-calories strong,#cooked_recipe_settings #cooked-nutrition-label .cooked-calories strong.cooked-nut-label{font-size:2rem;font-weight:800}#cooked_recipe_settings #cooked-nutrition-label ul.cooked-nut-servings li{border:none}#cooked_recipe_settings #cooked-nutrition-label ul.cooked-nut-servings{margin:0}#cooked_recipe_settings #cooked-nutrition-label .cooked-nut-hr{border:none;border-top:1.3rem solid #333;margin:.1rem 0 0;padding:0}#cooked_recipe_settings #cooked-nutrition-label strong{font-weight:800}#cooked_recipe_settings #cooked-nutrition-label .cooked-serving-size strong,#cooked_recipe_settings #cooked-nutrition-label .cooked-serving-size strong.cooked-nut-label{font-size:1.5rem;font-weight:800}#cooked_recipe_settings #cooked-nutrition-label strong.cooked-nut-heading{font-size:1rem}#cooked_recipe_settings #cooked-nutrition-label strong.cooked-nut-label{font-weight:400}#cooked_recipe_settings #cooked-nutrition-label .cooked-nut-right{float:right}#cooked_recipe_settings #cooked-nutrition-label ul.cooked-nut-bottom{margin:0 0 .75rem}#cooked_recipe_settings #cooked-nutrition-label ul.cooked-nut-bottom li{border-top:none;border-bottom:1px solid #aaa;float:left;width:100%;box-sizing:border-box}#cooked_recipe_settings .cooked-measurement-inputs .cooked-select-wrapper:before{top:8px}.cooked-auto-nutrition{display:flex;justify-content:end;margin:0 0 1rem}#cooked-tooltip-auto-nutrition-notice{margin:0}.cooked-settings-header{border-radius:10px 10px 0 0;width:auto;height:90px;line-height:85px;padding:0 30px;font-weight:600;box-sizing:border-box;background:#33373c;color:#fff;font-size:29px;letter-spacing:-.03rem}.cooked-settings-header .cooked-icon{color:#00e0a9}.cooked-settings-submit-wrap{padding:0 0 35px 35px;margin:-20px 0 0}#cooked_recipe_settings .recipe-setting-block .cooked-permalink-field-wrapper span{font-size:14px;display:inline-block;line-height:32px;padding:5px 0 0 0}#cooked_recipe_settings .recipe-setting-block input[type=text].cooked-permalink-field{position:relative;top:0;display:inline-block;width:150px;margin:0 10px}#cooked-settings-panel{position:relative;margin:0;padding:0}#cooked-settings-tabs{display:flex;width:auto;padding:0 20px 0 0;margin:0;list-style:none}#cooked-settings-tabs li{font-size:13px;line-height:1;font-weight:400;display:block;padding:0;margin:0;color:#fff}#cooked-settings-tabs li .cooked-icon{font-size:14px}#cooked-settings-tabs li .cooked-icon.cooked-icon-recipe-icon{font-size:20px;top:2px;position:relative}#cooked-settings-tabs li:hover{cursor:pointer}#cooked-settings-tabs li.active,#cooked-settings-tabs li.active:hover{cursor:default}#cooked-settings-tabs a{line-height:41px;font-size:.9rem;letter-spacing:.03rem;padding:2px 15px 4px;display:block;outline:0;box-shadow:none;border:none;white-space:nowrap}#cooked-settings-panel.stuck{padding-top:41px}#cooked-settings-panel.stuck #cooked-settings-tabs{position:fixed;width:calc(100% - 180px);top:32px;left:0;z-index:100000;margin-left:160px}#cooked-settings-panel .cooked-settings-tab-content-wrapper .cooked-settings-tab-content{display:none}#cooked-settings-panel .cooked-settings-tab-content-wrapper .cooked-settings-tab-content:first-child{display:block}#cooked-settings-panel .cooked-settings-tab-content{padding:25px 35px;margin-right:0}#cooked-settings-panel #cooked_recipe_settings .recipe-setting-block h3.cooked-settings-title{padding-bottom:.2rem}#cooked_recipe_settings .cooked-setting-column-14{position:relative;width:25%;float:left;padding-right:20px;box-sizing:border-box}#cooked_recipe_settings .cooked-setting-column-13{position:relative;width:33%;float:left;padding-right:20px;box-sizing:border-box}#cooked_recipe_settings .cooked-setting-column-23{position:relative;width:66%;float:left;padding-right:20px;box-sizing:border-box}#cooked_recipe_settings .cooked-setting-column-12{position:relative;width:50%;float:left;padding-right:20px;box-sizing:border-box}#cooked_recipe_settings .cooked-setting-column-12 input[type=text],#cooked_recipe_settings .cooked-setting-column-13 input[type=text],#cooked_recipe_settings .cooked-setting-column-14 input[type=text],#cooked_recipe_settings .cooked-setting-column-23 input[type=text],#cooked_recipe_settings .recipe-setting-block input[type=text].cooked-shortcode-field{width:95%}#cooked_recipe_settings .cooked-setting-column-12 .cooked-select-wrapper,#cooked_recipe_settings .cooked-setting-column-12 select,#cooked_recipe_settings .cooked-setting-column-13 .cooked-select-wrapper,#cooked_recipe_settings .cooked-setting-column-13 select,#cooked_recipe_settings .cooked-setting-column-14 .cooked-select-wrapper,#cooked_recipe_settings .cooked-setting-column-14 select,#cooked_recipe_settings .cooked-setting-column-23 select{width:100%}#cooked_recipe_settings .cooked-setting-column-14.cooked-tb-col{padding:0}#cooked-welcome-screen{padding:15px 50px 0 30px;position:relative}#cooked-welcome-screen .cooked-badge{position:absolute;top:0;right:0}#cooked-welcome-screen .cooked-badge img{border:none;width:150px;height:150px;display:block;margin:0}#cooked-welcome-screen .about-text{margin-bottom:40px;line-height:1.8;font-size:17px;margin-right:260px}#cooked-welcome-screen .about-wrap{margin:25px auto 0;max-width:1000px}#cooked-welcome-screen .about-wrap h1{font-size:2.25rem}#cooked-welcome-screen .about-wrap p{font-size:1rem;margin:0 0 2rem;line-height:1.7rem}#cooked-welcome-screen #cooked-welcome-panel{background:#fff;padding:30px 30px 15px 30px;-moz-border-radius:15px;-webkit-border-radius:15px;border-radius:15px;border:none;box-shadow:0 4px 4px rgba(0,0,0,.05),0 8px 8px rgba(0,0,0,.05),0 32px 32px rgba(0,0,0,.05),0 64px 64px rgba(0,0,0,.05)}#cooked-welcome-screen #cooked-welcome-panel img.cooked-welcome-banner{border:none;display:block;padding:0;margin:0 0 30px;box-sizing:border-box;border-radius:5px 5px 0 0}#cooked-welcome-screen #cooked-welcome-panel .cooked-welcome-panel-intro{text-align:center;margin:0 50px 2rem;padding:15px 0;border-bottom:1px solid #e5e5e5}#cooked-welcome-screen #cooked-welcome-panel .cooked-welcome-panel-intro h1{font-size:1.75rem;margin:0 0 1rem;padding:0}#cooked-welcome-screen #cooked-welcome-panel .cooked-welcome-panel-intro p{max-width:600px;font-size:1.1rem;line-height:1.8rem;margin:0 auto 2rem;padding:0}#cooked-welcome-screen .cooked-welcome-panel a{text-decoration:none}#cooked-welcome-screen .cooked-welcome-panel-column{display:block!important}#cooked-welcome-screen .cooked-welcome-panel-column>strong{font-size:1rem}#cooked-welcome-screen .cooked-welcome-panel-column h3{font-size:1.75rem;margin:0 0 1rem;position:relative}#cooked-welcome-screen .cooked-welcome-panel-column h3 a{position:absolute;top:0;right:0;font-size:12px;font-weight:400}#cooked-welcome-screen .cooked-welcome-panel-column h3 a i.fa{position:relative;top:1px}#cooked-welcome-screen .cooked-welcome-panel-column h4{font-size:1.25rem;margin:0 0 1rem;position:relative}#cooked-welcome-screen .cooked-welcome-panel-column h4 a{position:absolute;top:0;right:0;font-size:12px;font-weight:400}#cooked-welcome-screen .cooked-welcome-panel-column h4 a i.fa{position:relative;top:1px}#cooked-welcome-screen .cooked-welcome-panel-column.cooked-welcome-panel-full{width:100%;padding-right:0;float:none}#cooked-welcome-screen .cooked-welcome-panel-column.cooked-welcome-panel-last{width:70%;padding-right:0;float:right}#cooked-welcome-screen .cooked-welcome-panel-column .fa-external-link{color:#888}#cooked-welcome-screen .cooked-welcome-panel-content{min-height:1px;padding:10px 50px 20px;margin:0}#cooked-welcome-screen .cooked-welcome-panel .cooked-welcome-panel-column-container{display:flex;justify-content:between!important;padding:0!important;flex-wrap:wrap}#cooked-welcome-screen .cooked-welcome-panel-column{box-sizing:border-box;padding-right:2rem;width:30%}#cooked-welcome-screen .cooked-pro-features{width:85%;margin:0 auto;padding:0 0 10px;display:flex;flex-wrap:wrap}#cooked-welcome-screen ul.cooked-whatsnew-list{margin-right:0}#cooked-welcome-screen ul.cooked-whatsnew-list li{line-height:1.7;position:relative;margin-right:0;border-top:1px solid #e5e5e5;padding:8px 0 4px 0}#cooked-welcome-screen ul.cooked-whatsnew-list li:first-child{border:none;padding-top:0}#cooked-welcome-screen ul.cooked-whatsnew-list em.fix,#cooked-welcome-screen ul.cooked-whatsnew-list strong.new,#cooked-welcome-screen ul.cooked-whatsnew-list strong.tweak{position:absolute;font-style:normal;display:inline-block;background:#aaa;text-transform:uppercase;top:11px;left:0;color:#fff;font-weight:600;-moz-border-radius:3px;-webkit-border-radius:3px;text-align:center;width:38px;border-radius:3px;font-size:10px;line-height:19px;height:19px;padding:0 6px;margin:0 6px 0 0}#cooked-welcome-screen ul.cooked-whatsnew-list li:first-child em.fix,#cooked-welcome-screen ul.cooked-whatsnew-list li:first-child strong.new,#cooked-welcome-screen ul.cooked-whatsnew-list li:first-child strong.tweak{top:3px}#cooked-welcome-screen ul.cooked-whatsnew-list strong.new{background:#56c477}#cooked-welcome-screen ul.cooked-whatsnew-list strong.tweak{background:#0073aa}#cooked-welcome-screen ul.cooked-whatsnew-list em.fix{background:#ffad10}#cooked-welcome-screen ul li.cooked-pro i.cooked-icon{color:#ffad10}#cooked-welcome-screen ul li.cooked-pro a{font-weight:600;color:#ffad10}#cooked-welcome-screen ul li.cooked-pro a:hover{color:#d3910f}#cooked-welcome-screen ul.cooked-whatsnew-pro{margin-bottom:0;flex:1 0 auto;width:33.333%}#cooked-welcome-screen ul.cooked-whatsnew-pro li{padding-left:0;font-size:1.15rem;font-weight:400}#cooked-welcome-screen ul.cooked-whatsnew-pro li i.cooked-icon-star{color:#ffad10;margin:0 7px 0 0;font-size:19px;position:relative;top:1px}#cooked-welcome-screen .cooked-welcome-bottom{text-align:center;background:#f9f9f9;margin:30px -80px -15px;border-top:1px solid #f1f1f1}#cooked-welcome-screen .cooked-pro-button{display:inline-block;clear:both;text-align:center;width:auto;margin:30px auto;padding:20px 25px 21px;font-size:1.25rem;line-height:1rem;font-weight:600;color:#fff;background:#ffad10;border-radius:50px}#cooked-welcome-screen .cooked-pro-button:hover{background:#e08704}#cooked-welcome-screen .cooked-coupon-code{color:#4c5e65;display:inline-block;font-size:17px;padding:0 0 0 1.5rem}@media screen and (max-width:1050px){#cooked-welcome-screen ul.cooked-whatsnew-pro{width:100%;margin-top:0}#cooked-welcome-screen ul.cooked-whatsnew-pro:first-child{margin-top:10px}#cooked-welcome-screen ul.cooked-whatsnew-pro li:first-child{border-top:1px solid #e5e5e5;padding-top:8px}#cooked-welcome-screen ul.cooked-whatsnew-pro:first-child li:first-child{border:none;padding-top:0}}@media screen and (max-width:870px){#cooked-welcome-screen .cooked-pro-features{width:90%}#cooked-welcome-screen .cooked-welcome-panel-column{padding-right:0}#cooked-welcome-screen .cooked-welcome-panel-column.cooked-welcome-panel-last{width:100%;padding-top:20px}#cooked-welcome-screen .about-wrap{margin-top:0}#cooked-welcome-screen .cooked-welcome-panel .cooked-welcome-panel-column li{display:block}}@media screen and (max-width:782px){#cooked-welcome-screen .cooked-pro-features{width:100%}#cooked-welcome-screen{padding:15px 40px 0 30px}#cooked-welcome-screen .cooked-badge{display:none}#cooked-welcome-screen .about-text,#cooked-welcome-screen .about-wrap h1{margin-right:0}#cooked-welcome-screen .cooked-welcome-panel-column h3 a{display:block;position:relative}}@media screen and (max-width:500px){#cooked-welcome-screen #cooked-welcome-panel img.cooked-welcome-banner{display:none}#cooked-welcome-screen .about-wrap h1{font-size:1.8rem}#cooked-welcome-screen .about-text{font-size:15px}}#cooked_recipe_settings #cooked-nutrition-label .cooked-nut-label,#cooked_recipe_settings .recipe-setting-block h3.cooked-settings-title{color:#0173aa}#cooked_recipe_settings .recipe-setting-block.cooked-danger h3.cooked-settings-title{color:#d44d1f}#cooked_recipe_settings .cooked-alert-block h3.cooked-settings-title{color:#c02a2a}#cooked-recipe-tabs{background:#fff}#cooked-recipe-tabs li{background:#454b52;color:#fff;color:rgba(255,255,255,.75);border-right:1px solid rgba(0,0,0,.15)}#cooked-recipe-tabs li:hover{background:#535a62;color:#fff;border-right:1px solid rgba(0,0,0,.15)}#cooked-recipe-tabs li.active{background:#fff;color:#333;border-right:1px solid #fff}#cooked-settings-tabs{background:#454b52}#cooked-settings-tabs a{color:#fff;text-decoration:none}#cooked-settings-tabs li{background:#454b52;color:#fff;color:rgba(255,255,255,.75)}#cooked-settings-tabs li:hover{background:#535a62;color:#fff}#cooked-settings-tabs li.active{background:#fff}#cooked-settings-tabs li.active a{color:#000}#cooked-directions-builder .cooked-direction-block>.cooked-direction-image .cooked-direction-img-placeholder,#cooked-migration-progress.cooked-progress .cooked-progress-bar,#cooked-related-progress.cooked-progress .cooked-progress-bar,#cooked_recipe_settings .cooked-select-wrapper:before,#cooked_recipe_settings .cooked-tooltip-icon,#cooked_recipe_settings select{-webkit-transition:all .15s ease-out;-moz-transition:all .15s ease-out;transition:all .15s ease-out}#cooked-recipe-image-gallery .cooked-recipe-gallery-item{-webkit-transition:transform .15s ease-out;-moz-transition:all .15s ease-out;transition:transform .15s ease-out}#cooked-recipe-tabs li.cooked-loading{-webkit-transition:opacity .15s ease-out;-moz-transition:all .15s ease-out;transition:opacity .15s ease-out}@-webkit-keyframes cooked-wiggle{0%{-webkit-transform:scale(1)}50%{-webkit-transform:scale(1.03)}100%{-webkit-transform:scale(1)}}@-moz-keyframes cooked-wiggle{0%{-moz-transform:scale(1)}50%{-moz-transform:scale(1.03)}100%{-moz-transform:scale(1)}}@-o-keyframes cooked-wiggle{0%{-o-transform:scale(1)}50%{-o-transform:scale(1.03)}100%{-o-transform:scale(1)}}@keyframes cooked-wiggle{0%{transform:scale(1)}50%{transform:scale(1.03)}100%{transform:scale(1)}}@media screen and (max-width:1150px){#cooked-recipe-tabs li{font-size:.8rem;padding:0 1.2rem 0 .7rem}#cooked-recipe-tabs li .cooked-icon{display:none}}@media screen and (max-width:960px){#cooked_recipe_settings.stuck #cooked-recipe-tabs{margin-left:36px}}@media screen and (max-width:768px){#cooked_recipe_settings.stuck #cooked-recipe-tabs{position:absolute;top:0;left:0;width:100%;margin-left:0;box-shadow:0 3px 50px rgba(0,0,0,.25)}#cooked_recipe_settings .cooked-clearfix:has(>.cooked-setting-column-14){display:grid;grid-template-columns:1fr 1fr;gap:10px}#cooked_recipe_settings .cooked-setting-column-14{width:100%;float:none;padding-right:0}#cooked_recipe_settings .recipe-setting-block h3.cooked-settings-title{width:60%}}@media screen and (max-width:600px){#cooked_recipe_settings .recipe-setting-block h3.cooked-settings-title{display:flex;flex-wrap:wrap;align-items:center;row-gap:10px}#cooked_recipe_settings .recipe-setting-block:has(#cooked-nutrition-label) .cooked-setting-column-12{width:100%;float:none;padding-right:0}#cooked_recipe_settings .recipe-setting-block:has(#cooked-nutrition-label) p.cooked-measurement-inputs{display:flex;gap:10px}#cooked_recipe_settings .recipe-setting-block:has(#cooked-nutrition-label) p.cooked-measurement-inputs .cooked-measurement-column{width:50%;margin-right:0;flex:1}#cooked-ingredients-builder .cooked-ingredient-block{display:grid;grid-template-columns:1fr 1fr;column-gap:8px;row-gap:4px;padding-left:30px;padding-right:35px}#cooked-ingredients-builder .cooked-ingredient-block>div{display:block;float:none;padding-right:0}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-ingredient-amount{width:auto;grid-column:1}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-ingredient-measurement{width:auto;grid-column:2}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-ingredient-name{width:auto;grid-column:1/-1;padding-right:40px}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-substitution-fields{width:auto;float:none;clear:none;grid-column:1/-1}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-heading-name{grid-column:1/-1}#cooked_recipe_settings .cooked-ingredient-headers{display:grid;grid-template-columns:1fr 1fr;padding-left:30px;padding-right:35px}#cooked_recipe_settings .cooked-ingredient-headers span{display:block;float:none}#cooked_recipe_settings .cooked-layout-save-default{margin:0}#cooked_recipe_settings .cooked-ingredient-headers span.cooked-ingredient-header-amount{width:auto}#cooked_recipe_settings .cooked-ingredient-headers span.cooked-ingredient-header-measurement{width:auto}#cooked_recipe_settings .cooked-ingredient-headers span.cooked-ingredient-header-item{width:auto;grid-column:1/-1;padding-top:2px}#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded>.cooked-substitution-fields{display:grid;grid-template-columns:1fr 1fr;column-gap:8px;row-gap:4px}#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded>.cooked-substitution-fields>div{display:block;float:none;padding-right:0}#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded>.cooked-substitution-fields .cooked-ingredient-amount,#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded>.cooked-substitution-fields .cooked-ingredient-measurement{width:100%;display:block}#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded>.cooked-substitution-fields .cooked-ingredient-name{width:auto;grid-column:1/-1;padding-top:0}#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded>.cooked-substitution-fields .cooked-sub-label{grid-column:1/-1}#cooked-ingredients-builder .cooked-ingredient-block{position:relative}#cooked-ingredients-builder .cooked-ingredient-block .cooked-show-substitution{top:60px;right:42px}#cooked-ingredients-builder .cooked-ingredient-block.cooked-expanded .cooked-show-substitution,#cooked-ingredients-builder .cooked-ingredient-block.cooked-has-substitution .cooked-show-substitution,#cooked-ingredients-builder .cooked-ingredient-block:hover .cooked-show-substitution{right:42px}#cooked-ingredients-builder .cooked-ingredient-block .cooked-delete-ingredient{right:11px}}@media screen and (max-width:500px){#cooked_recipe_settings .cooked-clearfix:has(>.cooked-setting-column-14){grid-template-columns:1fr}#cooked_recipe_settings.stuck #cooked-recipe-tabs{width:100%}}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-substitution-fields{width:100%;float:left;clear:both;margin-top:10px;padding-top:10px;padding-right:0;padding-left:0;margin-left:0;box-sizing:border-box;padding-bottom:5px;position:relative}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-substitution-fields .cooked-ingredient-amount{width:15%}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-substitution-fields .cooked-ingredient-measurement{width:20%}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-substitution-fields .cooked-ingredient-name{width:65%}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-substitution-fields .cooked-sub-label{color:#7c7c7c;font-size:14px;font-weight:300}#cooked-ingredients-builder .cooked-ingredient-block>.cooked-substitution-fields>div{display:inline-block;float:left;box-sizing:border-box;padding-right:10px}.cooked-bulk-add-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.6);z-index:100100;display:flex;align-items:center;justify-content:center}.cooked-bulk-add-modal{background:#fff;border-radius:6px;width:600px;max-width:90vw;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 5px 30px rgba(0,0,0,.3)}.cooked-bulk-add-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #ddd}.cooked-bulk-add-header h2{margin:0;font-size:18px;line-height:1.3}.cooked-bulk-add-close{font-size:24px;text-decoration:none;color:#666;line-height:1;padding:0 4px}.cooked-bulk-add-close:hover{color:#d63638}.cooked-bulk-add-body{padding:20px;overflow-y:auto;flex:1}.cooked-bulk-add-help{margin-top:0;color:#666;font-size:13px}#cooked-bulk-add-textarea{width:100%;min-height:160px;font-size:14px;padding:10px;box-sizing:border-box;resize:vertical}.cooked-bulk-add-preview{margin-top:16px}.cooked-bulk-add-preview-label{margin:0 0 8px;font-size:13px}.cooked-bulk-add-preview-notice-ingredients{display:none;margin:10px 0 0;font-size:12px;line-height:1.45;color:#b45309;font-weight:700;gap:6px;align-items:flex-start}#cooked-bulk-add-preview[data-bulk-type=ingredients] .cooked-bulk-add-preview-notice-ingredients{display:flex}.cooked-bulk-add-preview-notice-mark{flex-shrink:0;font-weight:700;line-height:1.45;color:#b45309}.cooked-bulk-add-preview-notice-text{flex:1;min-width:0;font-weight:700;color:#b45309}.cooked-bulk-add-preview-list{max-height:200px;overflow-y:auto;border:1px solid #ddd;border-radius:4px;padding:4px}.cooked-bulk-add-preview-row{display:flex;align-items:center;gap:8px;padding:4px 6px;border-bottom:1px solid #f0f0f0}.cooked-bulk-add-preview-row:last-child{border-bottom:0}.cooked-bulk-add-heading-toggle{display:flex;align-items:center;gap:4px;white-space:nowrap;font-size:12px;color:#666;cursor:pointer;flex-shrink:0}.cooked-bulk-add-heading-toggle input[type=checkbox]{margin:0}.cooked-bulk-add-preview-text{flex:1;border:1px solid #ddd;border-radius:3px;padding:4px 8px;font-size:13px}.cooked-bulk-add-preview-row:not(.cooked-bulk-add-preview-row-ingredient).cooked-bulk-add-is-heading .cooked-bulk-add-preview-text{font-weight:700}.cooked-bulk-add-preview-row-ingredient .cooked-bulk-add-heading-line-wrap{display:none;flex:1;align-items:center;gap:8px;min-width:0}.cooked-bulk-add-preview-row-ingredient.cooked-bulk-add-is-heading .cooked-bulk-add-heading-line-wrap{display:flex}.cooked-bulk-add-preview-row-ingredient.cooked-bulk-add-is-heading .cooked-bulk-add-parsed-amount,.cooked-bulk-add-preview-row-ingredient.cooked-bulk-add-is-heading .cooked-bulk-add-parsed-name,.cooked-bulk-add-preview-row-ingredient.cooked-bulk-add-is-heading .cooked-bulk-add-parsed-unit{display:none}.cooked-bulk-add-heading-line-label{flex-shrink:0;font-size:12px;font-weight:600;color:#666}.cooked-bulk-add-preview-row-ingredient .cooked-bulk-add-heading-line-wrap .cooked-bulk-add-preview-text{flex:1;min-width:0;border:1px solid #ddd;border-radius:3px;padding:4px 8px;font-size:13px;font-weight:600}.cooked-bulk-add-preview-header{display:flex;align-items:center;gap:8px;padding:4px 6px 6px;border-bottom:1px solid #ddd;font-size:11px;font-weight:600;color:#999;text-transform:uppercase;letter-spacing:.5px}.cooked-bulk-add-col-heading{flex-shrink:0;width:108px}.cooked-bulk-add-col-amount{width:72px;flex-shrink:0}.cooked-bulk-add-col-unit{width:108px;flex-shrink:0}.cooked-bulk-add-col-name{flex:1}.cooked-bulk-add-parsed-amount{width:72px;flex-shrink:0;border:1px solid #ddd;border-radius:3px;padding:4px 6px;font-size:13px;text-align:center}.cooked-bulk-add-parsed-unit{width:108px;flex-shrink:0;border:1px solid #ddd;border-radius:3px;padding:4px 6px;font-size:13px}.cooked-bulk-add-parsed-name{flex:1;border:1px solid #ddd;border-radius:3px;padding:4px 6px;font-size:13px}.cooked-bulk-add-footer{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:14px 20px;border-top:1px solid #ddd}.cooked-bulk-add-footer .button,.cooked-bulk-add-footer .button.button-primary{float:none;margin:0;vertical-align:middle}.cooked-bulk-add-spinner{float:none!important;margin:0!important}#cooked_allergens .cooked-allergens-checkboxes{display:flex;flex-direction:column;gap:8px}#cooked_allergens .cooked-allergen-checkbox{display:flex;align-items:center;gap:8px;cursor:pointer}#cooked_allergens .cooked-allergen-checkbox input[type=checkbox]{margin:0}#cooked_allergens .cooked-allergen{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}#cooked_allergens .cooked-allergen .cooked-icon{font-size:1.5rem;line-height:1}#cooked_allergens .cooked-allergen,#cooked_allergens .cooked-allergen .cooked-icon{color:rgba(0,0,0,.4)}#cooked_allergens .cooked-allergen-label{font-size:13px;color:#333} \ No newline at end of file diff --git a/assets/admin/js/cooked-migration.js b/assets/admin/js/cooked-migration.js index 4aa01ba..c32345a 100644 --- a/assets/admin/js/cooked-migration.js +++ b/assets/admin/js/cooked-migration.js @@ -26,7 +26,8 @@ var ajax__bulk_migrate_recipes = $.post( cooked_migration_js_vars.ajax_url, { - action: 'cooked_get_migrate_ids' + action: 'cooked_get_migrate_ids', + nonce: cooked_migration_js_vars.cooked_import_nonce }, function (json_recipe_ids) { if (json_recipe_ids) { @@ -60,7 +61,8 @@ cooked_migration_js_vars.ajax_url, { action: 'cooked_get_import_ids', - import_type: import_type + import_type: import_type, + nonce: cooked_migration_js_vars.cooked_import_nonce }, function (json_recipe_ids) { if (json_recipe_ids) { @@ -117,6 +119,7 @@ var formData = new FormData(); formData.append('action', 'cooked_upload_csv'); + formData.append('nonce', cooked_migration_js_vars.cooked_import_nonce); formData.append('csv_file', file); // Show progress @@ -144,7 +147,8 @@ cooked_migration_js_vars.ajax_url, { action: 'cooked_process_csv', - transient_key: response.data.transient_key + transient_key: response.data.transient_key, + nonce: cooked_migration_js_vars.cooked_import_nonce }, function(processResponse) { if (processResponse.success) { @@ -267,7 +271,8 @@ function cooked_migrate_recipes(recipe_ids, total_recipes ) { cooked_migration_js_vars.ajax_url, { action: 'cooked_migrate_recipes', - recipe_ids: recipe_ids + recipe_ids: recipe_ids, + nonce: cooked_migration_js_vars.cooked_import_nonce }, function( new_recipe_ids ) { if ( new_recipe_ids && new_recipe_ids != 'false' && new_recipe_ids != false ){ @@ -348,7 +353,8 @@ function cooked_import_recipes(recipe_ids, total_recipes, import_type) { { action: 'cooked_import_recipes', recipe_ids: recipe_ids, - import_type: import_type + import_type: import_type, + nonce: cooked_migration_js_vars.cooked_import_nonce }, function (new_recipe_ids) { if (new_recipe_ids && new_recipe_ids != 'false' && new_recipe_ids != false) { diff --git a/assets/admin/js/cooked-migration.min.js b/assets/admin/js/cooked-migration.min.js index 24373ef..4f9dece 100644 --- a/assets/admin/js/cooked-migration.min.js +++ b/assets/admin/js/cooked-migration.min.js @@ -1 +1 @@ -(d=>{d(document).ready(function(){var e=d("#cooked-migration-button"),o=d("#cooked-import-button"),i=d("#cooked-csv-import-button"),r=(d("#cooked-csv-import-form"),d("#cooked-migration-progress"),d("#cooked-migration-progress-text"),d("#cooked-csv-import-progress")),a=d("#cooked-csv-import-progress-text");e.length&&e.on("click",function(e){e.preventDefault();e=d(this);confirm(cooked_migration_js_vars.i18n_confirm_migrate_recipes)&&!e.hasClass("disabled")&&(e.addClass("disabled").attr("disabled",!0),e.hide(),d.post(cooked_migration_js_vars.ajax_url,{action:"cooked_get_migrate_ids"},function(e){var o;e&&(o=JSON.parse(e),0<(o=Object.keys(o).length))&&cooked_migrate_recipes(e,o)}))}),o.length&&o.on("click",function(e){e.preventDefault();var i=d(this),t=i.data("import-type");confirm(cooked_migration_js_vars.i18n_confirm_import_recipes)&&!i.hasClass("disabled")&&(i.addClass("disabled").attr("disabled",!0),i.hide(),d.post(cooked_migration_js_vars.ajax_url,{action:"cooked_get_import_ids",import_type:t},function(e){var o;e?(o=JSON.parse(e),0<(o=Object.keys(o).length)&&cooked_import_recipes(e,o,t)):(console.log(cooked_migration_js_vars.i18n_something_wrong),i.addClass("disabled").attr("disabled",!1),i.show())}))}),i.length&&i.on("click",function(e){e.preventDefault();var o,i=d(this),t=d("#cooked-csv-file"),e=t[0].files[0],s=d("#cooked-csv-import-errors");s.hide().empty(),e?"text/csv"===e.type||e.name.endsWith(".csv")?i.hasClass("disabled")||confirm(cooked_migration_js_vars.i18n_confirm_csv_import)&&(i.addClass("disabled").attr("disabled",!0),t.attr("disabled",!0),(o=new FormData).append("action","cooked_upload_csv"),o.append("csv_file",e),r.hasClass("cooked-active")||(r.addClass("cooked-active"),a.addClass("cooked-active"),r.find(".cooked-progress-bar").css({width:"0%"}),a.text(cooked_migration_js_vars.i18n_uploading)),d.ajax({url:cooked_migration_js_vars.ajax_url,type:"POST",data:o,processData:!1,contentType:!1,success:function(e){e.success?(a.text(cooked_migration_js_vars.i18n_processing),r.find(".cooked-progress-bar").css({width:"50%"}),d.post(cooked_migration_js_vars.ajax_url,{action:"cooked_process_csv",transient_key:e.data.transient_key},function(e){var o;e.success?(r.find(".cooked-progress-bar").css({width:"100%"}),a.text(e.data.success+" / "+e.data.total+" "+cooked_migration_js_vars.i18n_recipes_imported),e.data.errors&&0"+cooked_migration_js_vars.i18n_errors+"

      ",e.data.errors.forEach(function(e){o+="
    • "+e+"
    • "}),o+="
    ",s.html(o).show()),setTimeout(function(){r.hide(),a.hide(),d("#cooked-csv-import-completed").show(),i.hide(),t.hide()},2e3)):(s.html("

    "+(e.data.message||cooked_migration_js_vars.i18n_import_failed)+"

    ").show(),i.removeClass("disabled").attr("disabled",!1),t.attr("disabled",!1),r.removeClass("cooked-active"),a.removeClass("cooked-active"))},"json").fail(function(){s.html("

    "+cooked_migration_js_vars.i18n_failed_process_csv+"

    ").show(),i.removeClass("disabled").attr("disabled",!1),t.attr("disabled",!1),r.removeClass("cooked-active"),a.removeClass("cooked-active")})):(s.html("

    "+(e.data.message||cooked_migration_js_vars.i18n_file_upload_failed)+"

    ").show(),i.removeClass("disabled").attr("disabled",!1),t.attr("disabled",!1),r.removeClass("cooked-active"),a.removeClass("cooked-active"))},error:function(){s.html("

    "+cooked_migration_js_vars.i18n_failed_upload_csv+"

    ").show(),i.removeClass("disabled").attr("disabled",!1),t.attr("disabled",!1),r.removeClass("cooked-active"),a.removeClass("cooked-active")}})):s.html("

    "+cooked_migration_js_vars.i18n_csv_invalid_file+"

    ").show():s.html("

    "+cooked_migration_js_vars.i18n_csv_no_file+"

    ").show()})})})(jQuery);{function cookedDecimalAdjust(e,o,i){return void 0===i||0==+i?Math[e](o):(i=+i,null===(o=+o)||isNaN(o)||"number"!=typeof i||i%1!=0?NaN:o<0?-cookedDecimalAdjust(e,-o,i):(o=o.toString().split("e"),+((o=(o=Math[e](+(o[0]+"e"+(o[1]?+o[1]-i:-i)))).toString().split("e"))[0]+"e"+(o[1]?+o[1]+i:i))))}}Math.round10||(Math.round10=function(e,o){return cookedDecimalAdjust("round",e,o)});var cooked_recipe_migrate_counter=0,progressIterations=0;function cooked_migrate_recipes(e,r){var a,d,c,o,n=0;0"+t+" "+cooked_migration_js_vars.i18n_hrs+", "+s+" "+cooked_migration_js_vars.i18n_mins+" "+cooked_migration_js_vars.i18n_remaining+""):1<=s?c.html(i+" / "+o+""+s+" "+cooked_migration_js_vars.i18n_mins+" "+cooked_migration_js_vars.i18n_remaining+""):c.text(i+" / "+o)):c.text(i+" / "+o),cooked_migrate_recipes(e,r)):(a.hide(),c.hide(),jQuery(".recipe-setting-block.migrate_button").find("h3").hide(),jQuery(".recipe-setting-block.migrate_button").find("p:nth-child(2)").hide(),jQuery(".recipe-setting-block.migrate_button").find("ul.cooked-admin-ul").hide(),jQuery("#cooked-migration-button").hide(),jQuery("#cooked-migration-completed").addClass("cooked-active"))}))}function cooked_import_recipes(e,r,a){var d,c,n,o,_=0;0"+t+" "+cooked_migration_js_vars.i18n_hrs+", "+s+" "+cooked_migration_js_vars.i18n_mins+" "+cooked_migration_js_vars.i18n_remaining+""):1<=s?n.html(i+" / "+o+""+s+" "+cooked_migration_js_vars.i18n_mins+" "+cooked_migration_js_vars.i18n_remaining+""):n.text(i+" / "+o)):n.text(i+" / "+o),cooked_import_recipes(e,r,a)):(d.hide(),n.hide(),jQuery(".recipe-setting-block.import_button").find("h3").hide(),jQuery(".recipe-setting-block.import_button").find("p:nth-child(2)").hide(),jQuery(".recipe-setting-block.import_button").find(".cooked-import-note").hide(),jQuery(".recipe-setting-block.import_button").find("ul.cooked-admin-ul").hide(),jQuery("#cooked-import-button").hide(),jQuery("#cooked-import-completed").addClass("cooked-active"))}))} \ No newline at end of file +(c=>{c(document).ready(function(){var o=c("#cooked-migration-button"),e=c("#cooked-import-button"),i=c("#cooked-csv-import-button"),s=(c("#cooked-csv-import-form"),c("#cooked-migration-progress"),c("#cooked-migration-progress-text"),c("#cooked-csv-import-progress")),a=c("#cooked-csv-import-progress-text");o.length&&o.on("click",function(o){o.preventDefault();o=c(this);confirm(cooked_migration_js_vars.i18n_confirm_migrate_recipes)&&!o.hasClass("disabled")&&(o.addClass("disabled").attr("disabled",!0),o.hide(),c.post(cooked_migration_js_vars.ajax_url,{action:"cooked_get_migrate_ids",nonce:cooked_migration_js_vars.cooked_import_nonce},function(o){var e;o&&(e=JSON.parse(o),0<(e=Object.keys(e).length))&&cooked_migrate_recipes(o,e)}))}),e.length&&e.on("click",function(o){o.preventDefault();var i=c(this),t=i.data("import-type");confirm(cooked_migration_js_vars.i18n_confirm_import_recipes)&&!i.hasClass("disabled")&&(i.addClass("disabled").attr("disabled",!0),i.hide(),c.post(cooked_migration_js_vars.ajax_url,{action:"cooked_get_import_ids",import_type:t,nonce:cooked_migration_js_vars.cooked_import_nonce},function(o){var e;o?(e=JSON.parse(o),0<(e=Object.keys(e).length)&&cooked_import_recipes(o,e,t)):(console.log(cooked_migration_js_vars.i18n_something_wrong),i.addClass("disabled").attr("disabled",!1),i.show())}))}),i.length&&i.on("click",function(o){o.preventDefault();var e,i=c(this),t=c("#cooked-csv-file"),o=t[0].files[0],r=c("#cooked-csv-import-errors");r.hide().empty(),o?"text/csv"===o.type||o.name.endsWith(".csv")?i.hasClass("disabled")||confirm(cooked_migration_js_vars.i18n_confirm_csv_import)&&(i.addClass("disabled").attr("disabled",!0),t.attr("disabled",!0),(e=new FormData).append("action","cooked_upload_csv"),e.append("nonce",cooked_migration_js_vars.cooked_import_nonce),e.append("csv_file",o),s.hasClass("cooked-active")||(s.addClass("cooked-active"),a.addClass("cooked-active"),s.find(".cooked-progress-bar").css({width:"0%"}),a.text(cooked_migration_js_vars.i18n_uploading)),c.ajax({url:cooked_migration_js_vars.ajax_url,type:"POST",data:e,processData:!1,contentType:!1,success:function(o){o.success?(a.text(cooked_migration_js_vars.i18n_processing),s.find(".cooked-progress-bar").css({width:"50%"}),c.post(cooked_migration_js_vars.ajax_url,{action:"cooked_process_csv",transient_key:o.data.transient_key,nonce:cooked_migration_js_vars.cooked_import_nonce},function(o){var e;o.success?(s.find(".cooked-progress-bar").css({width:"100%"}),a.text(o.data.success+" / "+o.data.total+" "+cooked_migration_js_vars.i18n_recipes_imported),o.data.errors&&0"+cooked_migration_js_vars.i18n_errors+"

      ",o.data.errors.forEach(function(o){e+="
    • "+o+"
    • "}),e+="
    ",r.html(e).show()),setTimeout(function(){s.hide(),a.hide(),c("#cooked-csv-import-completed").show(),i.hide(),t.hide()},2e3)):(r.html("

    "+(o.data.message||cooked_migration_js_vars.i18n_import_failed)+"

    ").show(),i.removeClass("disabled").attr("disabled",!1),t.attr("disabled",!1),s.removeClass("cooked-active"),a.removeClass("cooked-active"))},"json").fail(function(){r.html("

    "+cooked_migration_js_vars.i18n_failed_process_csv+"

    ").show(),i.removeClass("disabled").attr("disabled",!1),t.attr("disabled",!1),s.removeClass("cooked-active"),a.removeClass("cooked-active")})):(r.html("

    "+(o.data.message||cooked_migration_js_vars.i18n_file_upload_failed)+"

    ").show(),i.removeClass("disabled").attr("disabled",!1),t.attr("disabled",!1),s.removeClass("cooked-active"),a.removeClass("cooked-active"))},error:function(){r.html("

    "+cooked_migration_js_vars.i18n_failed_upload_csv+"

    ").show(),i.removeClass("disabled").attr("disabled",!1),t.attr("disabled",!1),s.removeClass("cooked-active"),a.removeClass("cooked-active")}})):r.html("

    "+cooked_migration_js_vars.i18n_csv_invalid_file+"

    ").show():r.html("

    "+cooked_migration_js_vars.i18n_csv_no_file+"

    ").show()})})})(jQuery);{function cookedDecimalAdjust(o,e,i){return void 0===i||0==+i?Math[o](e):(i=+i,null===(e=+e)||isNaN(e)||"number"!=typeof i||i%1!=0?NaN:e<0?-cookedDecimalAdjust(o,-e,i):(e=e.toString().split("e"),+((e=(e=Math[o](+(e[0]+"e"+(e[1]?+e[1]-i:-i)))).toString().split("e"))[0]+"e"+(e[1]?+e[1]+i:i))))}}Math.round10||(Math.round10=function(o,e){return cookedDecimalAdjust("round",o,e)});var cooked_recipe_migrate_counter=0,progressIterations=0;function cooked_migrate_recipes(o,s){var a,c,n,e,d=0;0"+t+" "+cooked_migration_js_vars.i18n_hrs+", "+r+" "+cooked_migration_js_vars.i18n_mins+" "+cooked_migration_js_vars.i18n_remaining+""):1<=r?n.html(i+" / "+e+""+r+" "+cooked_migration_js_vars.i18n_mins+" "+cooked_migration_js_vars.i18n_remaining+""):n.text(i+" / "+e)):n.text(i+" / "+e),cooked_migrate_recipes(o,s)):(a.hide(),n.hide(),jQuery(".recipe-setting-block.migrate_button").find("h3").hide(),jQuery(".recipe-setting-block.migrate_button").find("p:nth-child(2)").hide(),jQuery(".recipe-setting-block.migrate_button").find("ul.cooked-admin-ul").hide(),jQuery("#cooked-migration-button").hide(),jQuery("#cooked-migration-completed").addClass("cooked-active"))}))}function cooked_import_recipes(o,s,a){var c,n,d,e,_=0;0"+t+" "+cooked_migration_js_vars.i18n_hrs+", "+r+" "+cooked_migration_js_vars.i18n_mins+" "+cooked_migration_js_vars.i18n_remaining+""):1<=r?d.html(i+" / "+e+""+r+" "+cooked_migration_js_vars.i18n_mins+" "+cooked_migration_js_vars.i18n_remaining+""):d.text(i+" / "+e)):d.text(i+" / "+e),cooked_import_recipes(o,s,a)):(c.hide(),d.hide(),jQuery(".recipe-setting-block.import_button").find("h3").hide(),jQuery(".recipe-setting-block.import_button").find("p:nth-child(2)").hide(),jQuery(".recipe-setting-block.import_button").find(".cooked-import-note").hide(),jQuery(".recipe-setting-block.import_button").find("ul.cooked-admin-ul").hide(),jQuery("#cooked-import-button").hide(),jQuery("#cooked-import-completed").addClass("cooked-active"))}))} \ No newline at end of file diff --git a/includes/class.cooked-admin-enqueues.php b/includes/class.cooked-admin-enqueues.php index 3bd71cc..dc491eb 100644 --- a/includes/class.cooked-admin-enqueues.php +++ b/includes/class.cooked-admin-enqueues.php @@ -170,6 +170,7 @@ public function admin_enqueues( $hook ) { 'i18n_last_calculated' => __( 'Last: %1$s · %2$s recipes', 'cooked' ), 'wp_editor_roles_allowed' => esc_attr($wp_editor_roles_allowed), 'cooked_bulk_add_nonce' => wp_create_nonce( 'cooked_bulk_add' ), + 'cooked_import_nonce' => wp_create_nonce( 'cooked_admin_import' ), 'i18n_bulk_add_ingredients' => __( 'Bulk Add Ingredients', 'cooked' ), 'i18n_bulk_add_directions' => __( 'Bulk Add Directions', 'cooked' ), 'i18n_bulk_add_placeholder_ingredients' => __( "2 cups flour\n1 tsp salt\n1/2 cup sugar\n3 large eggs", 'cooked' ), diff --git a/includes/class.cooked-ajax.php b/includes/class.cooked-ajax.php index 42b71b5..8d3a085 100644 --- a/includes/class.cooked-ajax.php +++ b/includes/class.cooked-ajax.php @@ -62,10 +62,35 @@ function __construct() { add_action( 'wp_ajax_nopriv_cooked_parse_bulk_ingredients', [&$this, 'parse_bulk_ingredients'] ); } + private static function recipe_ids_from_json( $json ) { + $decoded = json_decode( $json, true ); + if ( ! is_array( $decoded ) || empty( $decoded ) ) { + return []; + } + + $ids = []; + foreach ( $decoded as $rid ) { + $safe_id = absint( $rid ); + if ( $safe_id ) { + $ids[] = $safe_id; + } + } + + return $ids; + } + + private static function sanitize_import_type( $import_type ) { + if ( ! in_array( $import_type, [ 'delicious_recipes', 'wp_recipe_maker' ], true ) ) { + return ''; + } + return $import_type; + } + public function get_migrate_ids() { - if (!current_user_can('edit_cooked_recipes')): + $nonce = isset( $_POST['nonce'] ) ? wp_unslash( $_POST['nonce'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verify nonce after unslash; do not sanitize_text_field a nonce. + if ( ! wp_verify_nonce( $nonce, 'cooked_admin_import' ) || ! current_user_can( 'edit_cooked_recipes' ) ) { wp_die(); - endif; + } $old_recipes = get_transient('cooked_classic_recipes'); if ($old_recipes != 'complete'): @@ -84,15 +109,16 @@ public function get_migrate_ids() { } public function get_import_ids() { - if (!current_user_can('edit_cooked_recipes')): + $nonce = isset( $_POST['nonce'] ) ? wp_unslash( $_POST['nonce'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verify nonce after unslash; do not sanitize_text_field a nonce. + if ( ! wp_verify_nonce( $nonce, 'cooked_admin_import' ) || ! current_user_can( 'edit_cooked_recipes' ) ) { wp_die(); - endif; - - $import_type = $_POST['import_type']; + } + $import_type = isset( $_POST['import_type'] ) ? sanitize_key( wp_unslash( $_POST['import_type'] ) ) : ''; + $import_type = self::sanitize_import_type( $import_type ); $recipes = []; - if ($import_type === 'delicious_recipes') { + if ( $import_type === 'delicious_recipes' ) { $args = [ 'post_type' => 'recipe', 'posts_per_page' => -1, @@ -105,7 +131,7 @@ public function get_import_ids() { ], ], ]; - } elseif ($import_type === 'wp_recipe_maker') { + } elseif ( $import_type === 'wp_recipe_maker' ) { $args = [ 'post_type' => 'wprm_recipe', 'posts_per_page' => -1, @@ -119,6 +145,9 @@ public function get_import_ids() { ], ], ]; + } else { + echo 'false'; + wp_die(); } $_recipes = new WP_Query( $args ); @@ -147,73 +176,53 @@ public function get_import_ids() { public function migrate_recipes() { $bulk_amount = 10; - if (!current_user_can('edit_cooked_recipes')) { + $nonce = isset( $_POST['nonce'] ) ? wp_unslash( $_POST['nonce'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verify nonce after unslash; do not sanitize_text_field a nonce. + if ( ! wp_verify_nonce( $nonce, 'cooked_admin_import' ) || ! current_user_can( 'edit_cooked_recipes' ) ) { wp_die(); } - if ( isset($_POST['recipe_ids']) ) { - // Sanitize Recipe IDs - $recipe_ids = json_decode( $_POST['recipe_ids'], true ); - - if ( is_array( $recipe_ids ) && !empty( $recipe_ids ) ) { - $_recipe_ids = []; - foreach ( $recipe_ids as $_rid ) { - $safe_id = intval( $_rid ); - if ( $safe_id ) { - $_recipe_ids[] = $_rid; - } - } - $recipe_ids = $_recipe_ids; - } else { - return false; - } - - $leftover_recipe_ids = array_slice( $recipe_ids, $bulk_amount ); - $recipe_ids = array_slice( $recipe_ids, 0, $bulk_amount ); - - if ( !empty($recipe_ids) ) { - foreach( $recipe_ids as $rid ) { - - $recipe_settings = Cooked_Recipes::get_settings( $rid ); - - if ( !empty( $recipe_settings ) && !isset( $recipe_settings['cooked_version'] ) || !empty( $recipe_settings ) && isset( $recipe_settings['cooked_version'] ) && !$recipe_settings['cooked_version'] ) { - - $recipe_settings['cooked_version'] = COOKED_VERSION; + $recipe_ids = self::recipe_ids_from_json( isset( $_POST['recipe_ids'] ) ? wp_unslash( $_POST['recipe_ids'] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON decoded then absint in recipe_ids_from_json. + if ( empty( $recipe_ids ) ) { + wp_die(); + } - // Migrate the recipe settings. - update_post_meta( $rid, '_recipe_settings', $recipe_settings ); - $recipe_excerpt = isset($recipe_settings['excerpt']) && $recipe_settings['excerpt'] ? $recipe_settings['excerpt'] : get_the_title( $rid ); + $leftover_recipe_ids = array_slice( $recipe_ids, $bulk_amount ); + $recipe_ids = array_slice( $recipe_ids, 0, $bulk_amount ); - $seo_content = apply_filters( 'cooked_seo_recipe_content', '[cooked-excerpt]

    ' . __('Ingredients','cooked') . '

    [cooked-ingredients checkboxes=false]

    ' . __('Directions','cooked') . '

    [cooked-directions numbers=false]' ); - $seo_content = do_shortcode( $seo_content ); + if ( ! empty( $recipe_ids ) ) { + foreach ( $recipe_ids as $rid ) { + $recipe_settings = Cooked_Recipes::get_settings( $rid ); - wp_update_post([ - 'ID' => $rid, - 'post_excerpt' => $recipe_excerpt, - 'post_content' => $seo_content - ]); + if ( ! empty( $recipe_settings ) && ! isset( $recipe_settings['cooked_version'] ) || ! empty( $recipe_settings ) && isset( $recipe_settings['cooked_version'] ) && ! $recipe_settings['cooked_version'] ) { + $recipe_settings['cooked_version'] = COOKED_VERSION; + update_post_meta( $rid, '_recipe_settings', $recipe_settings ); + $recipe_excerpt = isset( $recipe_settings['excerpt'] ) && $recipe_settings['excerpt'] ? $recipe_settings['excerpt'] : get_the_title( $rid ); - } - } + $seo_content = apply_filters( 'cooked_seo_recipe_content', '[cooked-excerpt]

    ' . __( 'Ingredients', 'cooked' ) . '

    [cooked-ingredients checkboxes=false]

    ' . __( 'Directions', 'cooked' ) . '

    [cooked-directions numbers=false]' ); + $seo_content = do_shortcode( $seo_content ); - if ( !empty( $leftover_recipe_ids ) ) { - echo wp_json_encode( $leftover_recipe_ids ); - wp_die(); + wp_update_post( [ + 'ID' => $rid, + 'post_excerpt' => $recipe_excerpt, + 'post_content' => $seo_content, + ] ); } - } - set_transient( 'cooked_classic_recipes', 'complete', 60 * 60 * 24 * 7 ); - echo 'false'; - wp_die(); - + if ( ! empty( $leftover_recipe_ids ) ) { + echo wp_json_encode( $leftover_recipe_ids ); + wp_die(); + } } + set_transient( 'cooked_classic_recipes', 'complete', 60 * 60 * 24 * 7 ); + echo 'false'; wp_die(); } public function import_recipes() { - if (!current_user_can('edit_cooked_recipes')) { + $nonce = isset( $_POST['nonce'] ) ? wp_unslash( $_POST['nonce'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verify nonce after unslash; do not sanitize_text_field a nonce. + if ( ! wp_verify_nonce( $nonce, 'cooked_admin_import' ) || ! current_user_can( 'edit_cooked_recipes' ) ) { wp_die(); } @@ -221,59 +230,45 @@ public function import_recipes() { require_once COOKED_DIR . 'includes/class.cooked-recipe-maker.php'; $bulk_amount = 10; + $recipe_ids = self::recipe_ids_from_json( isset( $_POST['recipe_ids'] ) ? wp_unslash( $_POST['recipe_ids'] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON decoded then absint in recipe_ids_from_json. + $import_type = isset( $_POST['import_type'] ) ? sanitize_key( wp_unslash( $_POST['import_type'] ) ) : ''; + $import_type = self::sanitize_import_type( $import_type ); - if ( isset($_POST['recipe_ids']) ) { - // Sanitize Recipe IDs - $recipe_ids = json_decode( $_POST['recipe_ids'], true ); - - if ( is_array( $recipe_ids ) && !empty( $recipe_ids ) ) { - $_recipe_ids = []; - foreach ( $recipe_ids as $_rid ) { - $safe_id = intval( $_rid ); - if ( $safe_id ) { - $_recipe_ids[] = $_rid; - } - } - $recipe_ids = $_recipe_ids; - } else { - return false; - } - - $leftover_recipe_ids = array_slice( $recipe_ids, $bulk_amount ); - $recipe_ids = array_slice( $recipe_ids, 0, $bulk_amount ); + if ( empty( $recipe_ids ) || ! $import_type ) { + wp_die(); + } - $import_type = $_POST['import_type']; + $leftover_recipe_ids = array_slice( $recipe_ids, $bulk_amount ); + $recipe_ids = array_slice( $recipe_ids, 0, $bulk_amount ); - if ( !empty($recipe_ids) ) { - foreach ( $recipe_ids as $rid ) { - if ($import_type === 'delicious_recipes') { - Cooked_Delicious_Recipes::import_recipe( $rid ); - } elseif ($import_type === 'wp_recipe_maker') { - Cooked_Recipe_Maker_Recipes::import_recipe( $rid ); - } + if ( ! empty( $recipe_ids ) ) { + foreach ( $recipe_ids as $rid ) { + if ( $import_type === 'delicious_recipes' ) { + Cooked_Delicious_Recipes::import_recipe( $rid ); + } elseif ( $import_type === 'wp_recipe_maker' ) { + Cooked_Recipe_Maker_Recipes::import_recipe( $rid ); } + } - if ( !empty( $leftover_recipe_ids ) ) { - echo wp_json_encode( $leftover_recipe_ids ); - wp_die(); - } else { - if ($import_type === 'delicious_recipes') { - update_option( 'cooked_delicious_recipes_imported', true ); - } elseif ($import_type === 'wp_recipe_maker') { - update_option( 'cooked_wp_recipe_maker_imported', true ); - } - } + if ( ! empty( $leftover_recipe_ids ) ) { + echo wp_json_encode( $leftover_recipe_ids ); + wp_die(); } - echo 'false'; - wp_die(); + if ( $import_type === 'delicious_recipes' ) { + update_option( 'cooked_delicious_recipes_imported', true ); + } elseif ( $import_type === 'wp_recipe_maker' ) { + update_option( 'cooked_wp_recipe_maker_imported', true ); + } } + echo 'false'; wp_die(); } public function get_recipe_ids() { - if (!wp_verify_nonce($_POST['nonce'], 'cooked_save_default_bulk') || !current_user_can('edit_cooked_default_template')) { + $nonce = isset( $_POST['nonce'] ) ? wp_unslash( $_POST['nonce'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verify nonce after unslash; do not sanitize_text_field a nonce. + if ( ! wp_verify_nonce( $nonce, 'cooked_save_default_bulk' ) || ! current_user_can( 'edit_cooked_default_template' ) ) { wp_die(); } @@ -290,7 +285,8 @@ public function get_recipe_ids() { } public function get_recipe_count() { - if (!wp_verify_nonce($_POST['nonce'], 'cooked_save_default_bulk') || !current_user_can('edit_cooked_default_template')) { + $nonce = isset( $_POST['nonce'] ) ? wp_unslash( $_POST['nonce'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verify nonce after unslash; do not sanitize_text_field a nonce. + if ( ! wp_verify_nonce( $nonce, 'cooked_save_default_bulk' ) || ! current_user_can( 'edit_cooked_default_template' ) ) { wp_die(); } @@ -308,16 +304,17 @@ public function get_recipe_count() { public function save_default_bulk() { $per_page = 20; - if (!wp_verify_nonce($_POST['nonce'], 'cooked_save_default_bulk') || !current_user_can('edit_cooked_default_template')) { + $nonce = isset( $_POST['nonce'] ) ? wp_unslash( $_POST['nonce'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verify nonce after unslash; do not sanitize_text_field a nonce. + if ( ! wp_verify_nonce( $nonce, 'cooked_save_default_bulk' ) || ! current_user_can( 'edit_cooked_default_template' ) ) { wp_die(); } - if (!isset($_POST['default_content'])) { + if ( ! isset( $_POST['default_content'] ) ) { wp_send_json_error( [ 'message' => __( 'No default content provided.', 'cooked' ) ] ); } - $page = isset($_POST['page']) ? absint($_POST['page']) : 0; - $content = wp_kses_post($_POST['default_content']); + $page = isset( $_POST['page'] ) ? absint( wp_unslash( $_POST['page'] ) ) : 0; + $content = wp_kses_post( wp_unslash( $_POST['default_content'] ) ); $args = [ 'post_type' => 'cp_recipe', @@ -354,12 +351,13 @@ public function save_default_bulk() { public function save_default() { global $_cooked_settings; - if (!wp_verify_nonce($_POST['nonce'], 'cooked_save_default') || !current_user_can('edit_cooked_default_template')) { + $nonce = isset( $_POST['nonce'] ) ? wp_unslash( $_POST['nonce'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verify nonce after unslash; do not sanitize_text_field a nonce. + if ( ! wp_verify_nonce( $nonce, 'cooked_save_default' ) || ! current_user_can( 'edit_cooked_default_template' ) ) { wp_die(); } - if (isset($_POST['default_content'])) { - $_cooked_settings['default_content'] = wp_kses_post( $_POST['default_content'] ); + if ( isset( $_POST['default_content'] ) ) { + $_cooked_settings['default_content'] = wp_kses_post( wp_unslash( $_POST['default_content'] ) ); update_option('cooked_settings', $_cooked_settings); } else { echo esc_html__( 'No default content provided.', 'cooked' ); @@ -390,16 +388,23 @@ public function load_default() { * Handle CSV file upload */ public function upload_csv() { - if (!current_user_can('edit_cooked_recipes')) { + $nonce = isset( $_POST['nonce'] ) ? wp_unslash( $_POST['nonce'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verify nonce after unslash; do not sanitize_text_field a nonce. + if ( ! wp_verify_nonce( $nonce, 'cooked_admin_import' ) || ! current_user_can( 'edit_cooked_recipes' ) ) { wp_send_json_error(['message' => __('You do not have permission to import recipes.', 'cooked')]); } - if (!isset($_FILES['csv_file']) || $_FILES['csv_file']['error'] !== UPLOAD_ERR_OK) { + if ( ! isset( $_FILES['csv_file'] ) || ! isset( $_FILES['csv_file']['error'] ) || UPLOAD_ERR_OK !== (int) $_FILES['csv_file']['error'] ) { wp_send_json_error(['message' => __('File upload failed.', 'cooked')]); } + $csv_name = isset( $_FILES['csv_file']['name'] ) ? sanitize_file_name( wp_unslash( $_FILES['csv_file']['name'] ) ) : ''; + if ( ! $csv_name ) { + wp_send_json_error(['message' => __('File upload failed.', 'cooked')]); + } + $_FILES['csv_file']['name'] = $csv_name; + // Validate file type - $file_type = wp_check_filetype($_FILES['csv_file']['name']); + $file_type = wp_check_filetype( $csv_name ); if ($file_type['ext'] !== 'csv') { wp_send_json_error(['message' => __('Invalid file type. Please upload a CSV file.', 'cooked')]); } @@ -426,11 +431,12 @@ public function upload_csv() { * Process CSV file and import recipes */ public function process_csv() { - if (!current_user_can('edit_cooked_recipes')) { + $nonce = isset( $_POST['nonce'] ) ? wp_unslash( $_POST['nonce'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verify nonce after unslash; do not sanitize_text_field a nonce. + if ( ! wp_verify_nonce( $nonce, 'cooked_admin_import' ) || ! current_user_can( 'edit_cooked_recipes' ) ) { wp_send_json_error(['message' => __('You do not have permission to import recipes.', 'cooked')]); } - $transient_key = isset($_POST['transient_key']) ? sanitize_text_field($_POST['transient_key']) : ''; + $transient_key = isset( $_POST['transient_key'] ) ? sanitize_text_field( wp_unslash( $_POST['transient_key'] ) ) : ''; $file_path = get_transient($transient_key); if (!$file_path || !file_exists($file_path)) { @@ -467,11 +473,12 @@ public function process_csv() { } public function parse_bulk_ingredients() { - if ( ! check_ajax_referer( 'cooked_bulk_add', 'nonce', false ) ) { + $nonce = isset( $_POST['nonce'] ) ? wp_unslash( $_POST['nonce'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verify nonce after unslash; do not sanitize_text_field a nonce. + if ( ! wp_verify_nonce( $nonce, 'cooked_bulk_add' ) ) { wp_send_json_error( [ 'message' => __( 'Security check failed.', 'cooked' ) ] ); } - $lines = isset( $_POST['lines'] ) ? (array) $_POST['lines'] : []; + $lines = isset( $_POST['lines'] ) ? wp_unslash( (array) $_POST['lines'] ) : []; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Each line is sanitize_text_field'd below. if ( empty( $lines ) ) { wp_send_json_error( [ 'message' => __( 'No ingredients provided.', 'cooked' ) ] ); @@ -506,7 +513,6 @@ public function parse_bulk_ingredients() { // Do not use Cooked_Functions::sanitize_text_field() here — it runs htmlentities() and turns // Unicode like en dash or ½ into – / ½, which breaks parsing and leaks into output. $line = is_string( $line ) ? $line : ''; - $line = wp_unslash( $line ); $line = html_entity_decode( $line, ENT_QUOTES | ENT_HTML5, 'UTF-8' ); $line = trim( sanitize_text_field( $line ) ); From 90ad2c9fd2db67fc84e2d121424b5fb14c7fd297 Mon Sep 17 00:00:00 2001 From: Armand Tresova Date: Mon, 17 Aug 2026 15:01:04 -0400 Subject: [PATCH 18/36] Created .npmrc for DDEV Playwright Addon --- .npmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .npmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..521a9f7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true From 85f83cb0c64965325b40d406ff569d609a7053a6 Mon Sep 17 00:00:00 2001 From: Armand Tresova Date: Mon, 17 Aug 2026 17:22:32 -0400 Subject: [PATCH 19/36] Plugin Check (PCP) - Fixes PCP Wave 10: Pro add-recipe write path --- tests/phpunit/RecipeMetaTest.php | 57 +++++++++++++++ .../playwright/tests/admin/csv-import.spec.ts | 69 ++++++++++++++++--- tests/playwright/utils/wp-cli.ts | 25 +++++++ 3 files changed, 143 insertions(+), 8 deletions(-) diff --git a/tests/phpunit/RecipeMetaTest.php b/tests/phpunit/RecipeMetaTest.php index c87cc0e..77f30a7 100644 --- a/tests/phpunit/RecipeMetaTest.php +++ b/tests/phpunit/RecipeMetaTest.php @@ -4,6 +4,12 @@ class RecipeMetaTest extends TestCase { + protected function tearDown(): void { + unset( $GLOBALS['_cooked_test_logged_in'] ); + unset( $GLOBALS['_cooked_settings'] ); + parent::tearDown(); + } + public function test_meta_cleanup_empty_input() { $result = Cooked_Recipe_Meta::meta_cleanup([]); $this->assertSame([], $result); @@ -65,4 +71,55 @@ public function test_meta_cleanup_sanitizes_direction_video_field() { $result = Cooked_Recipe_Meta::meta_cleanup($input); $this->assertStringNotContainsString('