From e8fcaeb359aa0c410163290fb6460dcc56fd4aac Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 10:32:17 +0400 Subject: [PATCH 01/51] Build/Test Tools: Add Code Reference preview foundations The preview needs one checked-in source of truth for its toolchain, upstreams, runtime, limits, and behavioral smoke targets. Loose refs make the cache lie and make builds drift. Validate official repositories and full commits, resolve beta to a concrete versioned download, bind every material base input into the cache key, and keep arbitrary forks inert. Review follow-ups: move from Node 20 when the harness is exercised on a supported release; make cache-key field naming and ordering explicit when the build caller lands. --- .github/docs-playground-preview/.nvmrc | 1 + .../docs-playground-preview/dependencies.json | 73 + .../docs-playground-preview/package-lock.json | 3024 +++++++++++++++++ .github/docs-playground-preview/package.json | 16 + .../docs-playground-preview/lib/config.mjs | 177 + .../test/config.test.mjs | 158 + 6 files changed, 3449 insertions(+) create mode 100644 .github/docs-playground-preview/.nvmrc create mode 100644 .github/docs-playground-preview/dependencies.json create mode 100644 .github/docs-playground-preview/package-lock.json create mode 100644 .github/docs-playground-preview/package.json create mode 100644 .github/scripts/docs-playground-preview/lib/config.mjs create mode 100644 .github/scripts/docs-playground-preview/test/config.test.mjs diff --git a/.github/docs-playground-preview/.nvmrc b/.github/docs-playground-preview/.nvmrc new file mode 100644 index 0000000000000..ccc4c6c7f818a --- /dev/null +++ b/.github/docs-playground-preview/.nvmrc @@ -0,0 +1 @@ +20.20.2 diff --git a/.github/docs-playground-preview/dependencies.json b/.github/docs-playground-preview/dependencies.json new file mode 100644 index 0000000000000..b6653bfb49a40 --- /dev/null +++ b/.github/docs-playground-preview/dependencies.json @@ -0,0 +1,73 @@ +{ + "schemaVersion": 1, + "cacheSchemaVersion": 1, + "toolchain": { + "nodeVersion": "20.20.2", + "npmVersion": "10.8.2", + "composerVersion": "2.8.12" + }, + "playground": { + "cliVersion": "3.1.48", + "phpVersion": "8.4", + "wordpressChannel": "beta", + "wordpressVersionApi": "https://api.wordpress.org/core/version-check/1.7/?channel=beta", + "blueprintSchema": "https://playground.wordpress.net/blueprint-schema.json" + }, + "repositories": { + "phpdocParser": { + "repository": "WordPress/phpdoc-parser", + "commit": "1156399efccafd344818fa3b566ce5beafdc655e", + "path": "." + }, + "wporgDeveloper": { + "repository": "WordPress/wporg-developer", + "commit": "f2b6b9c1fe788fb4a5441a296b591f6dff540607", + "path": "source/wp-content/themes/wporg-developer-2023" + }, + "wporgParent2021": { + "repository": "WordPress/wporg-parent-2021", + "commit": "3b61f284dcc600dc5ad500e34d56108351909d32", + "path": "source/wp-content/themes/wporg-parent-2021" + }, + "wporgMuPlugins": { + "repository": "WordPress/wporg-mu-plugins", + "commit": "f37f522a0ed78b64748fa0b390e58e9f74de32ea", + "path": "mu-plugins" + }, + "postsToPosts": { + "repository": "scribu/wp-posts-to-posts", + "commit": "d2f297dbe257a09438da6ffe22c9a876acb7b49b", + "path": "." + }, + "codeSyntaxBlock": { + "repository": "mkaz/code-syntax-block", + "commit": "7179d747eef70ca8760cf5e0c04d3877158fa350", + "path": "." + } + }, + "limits": { + "snapshotBytes": 104857600, + "artifactRetentionDays": 1 + }, + "validation": { + "minimumSymbols": { + "classes": 400, + "methods": 3000, + "functions": 1200, + "hooks": 600, + "filters": 1000 + }, + "routes": { + "index": "/reference/", + "class": "/reference/classes/wp_html_tag_processor/", + "method": "/reference/classes/wp_html_tag_processor/next_tag/", + "function": "/reference/functions/wp_parse_args/", + "hook": "/reference/hooks/init/", + "filter": "/reference/hooks/the_content/" + }, + "search": { + "path": "/?s=wp_parse_args&post_type=wp-parser-function", + "expectedText": "wp_parse_args" + } + } +} diff --git a/.github/docs-playground-preview/package-lock.json b/.github/docs-playground-preview/package-lock.json new file mode 100644 index 0000000000000..2e6a587b8088a --- /dev/null +++ b/.github/docs-playground-preview/package-lock.json @@ -0,0 +1,3024 @@ +{ + "name": "wordpress-develop-code-reference-playground-preview", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wordpress-develop-code-reference-playground-preview", + "version": "1.0.0", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wp-playground/cli": "3.1.48" + }, + "engines": { + "node": "20.20.2" + } + }, + "node_modules/@nodable/entities": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.0.0.tgz", + "integrity": "sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@octokit/app": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/app/-/app-14.1.0.tgz", + "integrity": "sha512-g3uEsGOQCBl1+W1rgfwoRFUIR6PtvB2T1E4RpygeUU5LrLvlOqcxrt5lfykIeRpUPpupreGJUYl70fqMDXdTpw==", + "license": "MIT", + "dependencies": { + "@octokit/auth-app": "^6.0.0", + "@octokit/auth-unauthenticated": "^5.0.0", + "@octokit/core": "^5.0.0", + "@octokit/oauth-app": "^6.0.0", + "@octokit/plugin-paginate-rest": "^9.0.0", + "@octokit/types": "^12.0.0", + "@octokit/webhooks": "^12.0.4" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-app": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.1.4.tgz", + "integrity": "sha512-QkXkSOHZK4dA5oUqY5Dk3S+5pN2s1igPjEASNQV8/vgJgW034fQWR16u7VsNOK/EljA00eyjYF5mWNxWKWhHRQ==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^7.1.0", + "@octokit/auth-oauth-user": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.1.0", + "deprecation": "^2.3.1", + "lru-cache": "npm:@wolfy1339/lru-cache@^11.0.2-patch.1", + "universal-github-app-jwt": "^1.1.2", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-app/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/auth-app/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/auth-oauth-app": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-7.1.0.tgz", + "integrity": "sha512-w+SyJN/b0l/HEb4EOPRudo7uUOSW51jcK1jwLa+4r7PA8FPFpoxEnHBHMITqCsc/3Vo2qqFjgQfz/xUUvsSQnA==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^6.1.0", + "@octokit/auth-oauth-user": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", + "@types/btoa-lite": "^1.0.0", + "btoa-lite": "^1.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/auth-oauth-device": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-6.1.0.tgz", + "integrity": "sha512-FNQ7cb8kASufd6Ej4gnJ3f1QB5vJitkoV1O0/g6e6lUsQ7+VsSNRHRmFScN2tV4IgKA12frrr/cegUs0t+0/Lw==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-methods": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/auth-oauth-user": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-4.1.0.tgz", + "integrity": "sha512-FrEp8mtFuS/BrJyjpur+4GARteUCrPeR/tZJzD8YourzoVhRics7u7we/aDcKv+yywRNwNi/P4fRi631rG/OyQ==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^6.1.0", + "@octokit/oauth-methods": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", + "btoa-lite": "^1.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-unauthenticated": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-5.0.1.tgz", + "integrity": "sha512-oxeWzmBFxWd+XolxKTc4zr+h3mt+yofn4r7OfoIkR/Cj/o70eEGmPsFbueyJE2iBAGpjgTnEOKM3pnuEGVmiqg==", + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz", + "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.4.1", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/core/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/endpoint": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", + "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/endpoint/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/graphql": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", + "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^8.4.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/graphql/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/oauth-app": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-6.1.0.tgz", + "integrity": "sha512-nIn/8eUJ/BKUVzxUXd5vpzl1rwaVxMyYbQkNZjHrF7Vk/yu98/YDF/N2KeWO7uZ0g3b5EyiFXFkZI8rJ+DH1/g==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^7.0.0", + "@octokit/auth-oauth-user": "^4.0.0", + "@octokit/auth-unauthenticated": "^5.0.0", + "@octokit/core": "^5.0.0", + "@octokit/oauth-authorization-url": "^6.0.2", + "@octokit/oauth-methods": "^4.0.0", + "@types/aws-lambda": "^8.10.83", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-authorization-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz", + "integrity": "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-methods": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.1.0.tgz", + "integrity": "sha512-4tuKnCRecJ6CG6gr0XcEXdZtkTDbfbnD5oaHBmLERTjTMZNi2CbfEHZxPU41xXLDG4DfKf+sonu00zvKI9NSbw==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-authorization-url": "^6.0.2", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.0.0", + "btoa-lite": "^1.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-graphql": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-4.0.1.tgz", + "integrity": "sha512-R8ZQNmrIKKpHWC6V2gum4x9LG2qF1RxRjo27gjQcG3j+vf2tLsEfE7I/wRWEPzYMaenr1M+qDAtNcwZve1ce1A==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=5" + } + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz", + "integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz", + "integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-retry": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.1.0.tgz", + "integrity": "sha512-WrO3bvq4E1Xh1r2mT9w6SDFg01gFmP81nIG77+p/MqW1JeXXgL++6umim3t6x0Zj5pZm3rXAN+0HEjmmdhIRig==", + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^13.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-retry/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-retry/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/plugin-throttling": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-8.2.0.tgz", + "integrity": "sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.2.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^5.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", + "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^9.0.6", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", + "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/request-error/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/request/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/request/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@octokit/webhooks": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-12.3.2.tgz", + "integrity": "sha512-exj1MzVXoP7xnAcAB3jZ97pTvVPkQF9y6GA/dvYC47HV7vLv+24XRS6b/v/XnyikpEuvMhugEXdGtAlU086WkQ==", + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^5.0.0", + "@octokit/webhooks-methods": "^4.1.0", + "@octokit/webhooks-types": "7.6.1", + "aggregate-error": "^3.1.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/webhooks-methods": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-4.1.0.tgz", + "integrity": "sha512-zoQyKw8h9STNPqtm28UGOYFE7O6D4Il8VJwhAtMHFt2C4L0VQT1qGKLeefUOqHNs1mNRYSadVv7x0z8U2yyeWQ==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/webhooks-types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-7.6.1.tgz", + "integrity": "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw==", + "license": "MIT" + }, + "node_modules/@php-wasm/cli-util": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/cli-util/-/cli-util-3.1.48.tgz", + "integrity": "sha512-zNLQtNYVY9tFQEOQClFS6cNK+ntFq2HX0sPHb7HGRYPFV3vom1/Y42F7HQZBxUP+05N2VX0HL0csKlp41AGbeQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/util": "3.1.48", + "fast-xml-parser": "^5.8.0", + "jsonc-parser": "3.3.1" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/logger": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/logger/-/logger-3.1.48.tgz", + "integrity": "sha512-BZdRRIs+Zc3IY3tme0KKsFOOtg/petabYftlXEHSFll/TKUEfOSV7GvMGKmF3tRkZFzadJzY9FClRV6kSIal4A==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/node/-/node-3.1.48.tgz", + "integrity": "sha512-VGe/ZmOmNJ9WjWOY9uApw7yEr+PS6mI9BVMVbUa/akWhZnloOD/J942izvFcaSUyn+UkYLOuFJkgY3ySulUtgw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/cli-util": "3.1.48", + "@php-wasm/logger": "3.1.48", + "@php-wasm/node-5-2": "3.1.48", + "@php-wasm/node-7-4": "3.1.48", + "@php-wasm/node-8-0": "3.1.48", + "@php-wasm/node-8-1": "3.1.48", + "@php-wasm/node-8-2": "3.1.48", + "@php-wasm/node-8-3": "3.1.48", + "@php-wasm/node-8-4": "3.1.48", + "@php-wasm/node-8-5": "3.1.48", + "@php-wasm/universal": "3.1.48", + "@php-wasm/util": "3.1.48", + "fs-ext-extra-prebuilt": "2.2.7", + "wasm-feature-detect": "1.8.0", + "ws": "8.21.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-5-2": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/node-5-2/-/node-5-2-3.1.48.tgz", + "integrity": "sha512-c3n+9Qs6vQv4haKRmihAmm/90D+r4TzSZcqd6maRXZbpmX2Mw3eymIpkQebRnW/LmOxOO7FC54/jmKHQZSmNQQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.48", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-7-4": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/node-7-4/-/node-7-4-3.1.48.tgz", + "integrity": "sha512-QzasgDrgbov6jkuokqB7yFtuP7SYXLwFZEighgb6HqcppAa3RAKHcsSsArDhhOKbu6NicmkjybdNrOKW9DuP7Q==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.48", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-0": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-0/-/node-8-0-3.1.48.tgz", + "integrity": "sha512-sGTAQdNp4URnBb8/b9IwbgdnC9Vh895JCY0lLS72lkWbrRgwvwsWyAY1iu38LnhHM1D+/xMhZj2wNbMvdPzzkg==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.48", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-1": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-1/-/node-8-1-3.1.48.tgz", + "integrity": "sha512-sruxX5piVy0+9PBibFkgF+DEHGcPzOdCD2e7deLbYXMzLvOLGmqzMb4z7ng88WC6ZDk0Pk2jOTTq+08sG9BuMA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.48", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-2": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-2/-/node-8-2-3.1.48.tgz", + "integrity": "sha512-Dyp0SMyNee8FHFGed3KyyYiAGtHo9c2cFkJ/e3IdtX/+Uqo2pvFM8zcFLuWhwOiMJrEJPYl5FoXuER2H5pkHBw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.48", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-3": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-3/-/node-8-3-3.1.48.tgz", + "integrity": "sha512-vRlGi+Kd8jCfgtqGOZHriIp0ufyIMaVuPMGUbq99CKjvunyXouWE88Zn+lS/LQ7KTEhO86Xq4MEgzK1/huq4LA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.48", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-4": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-4/-/node-8-4-3.1.48.tgz", + "integrity": "sha512-TZfwQqdKYukMU5RXbTHgVx6YeiAlb7bN0NUVY5hDvMfioGNMnpBL0sWWf44ULj5L5TK39MbbYr0SQ7i5Rh2cyw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.48", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-5": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-5/-/node-8-5-3.1.48.tgz", + "integrity": "sha512-I3q1MKXjL2EfvpwC1PT2QiyLcPjOdvUVxP4x+8q94x2JMhtgH19lGMhxX9jnBjOGP8FAQ8cOcQ7JtxXQcWg9GQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.48", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/progress": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/progress/-/progress-3.1.48.tgz", + "integrity": "sha512-KFiI3Q9zDQ9NC7HGl5e/jx0BAvJYpyN1shv8vvJbeicXeuOoJnJS9C3XehRWlT9aRCIZ0qpeRSrmR3rR5ue0aQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/logger": "3.1.48" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/scopes": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/scopes/-/scopes-3.1.48.tgz", + "integrity": "sha512-3ncWvaCPs9gLyvskAplZdIMDyImT2MsJ8aRXrLMunvtjq1dwaPcCmvbzv1Z2czb2m23bxgTqalVn7jEDJbhlGw==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/stream-compression": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/stream-compression/-/stream-compression-3.1.48.tgz", + "integrity": "sha512-bQzUn3gNAHcRUgkks5hrZc7jS+jirow+xORkMPsINobi+Y6j2za/OAaGMd9THo9298ubH6dGeeapZ5q58C0hsg==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/util": "3.1.48" + } + }, + "node_modules/@php-wasm/universal": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/universal/-/universal-3.1.48.tgz", + "integrity": "sha512-CtopMuRcJwo8FVsmrzPhpvs/OsoGXYduHOemI78MSP/wfufosZcAg654J+d8IcF9xq6VTJjir3uxkE3t+bct4g==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/logger": "3.1.48", + "@php-wasm/progress": "3.1.48", + "@php-wasm/stream-compression": "3.1.48", + "@php-wasm/util": "3.1.48", + "ini": "4.1.2" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/util": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/util/-/util-3.1.48.tgz", + "integrity": "sha512-MvHDY9K4ErSAOOAp5BypIX6ITTaYQugtbdcGR76ZCtjGCS91QBOe0Mm+75lfEOKnLOKltRdgUmfhV6Jb0oDZeg==", + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/web-service-worker": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/web-service-worker/-/web-service-worker-3.1.48.tgz", + "integrity": "sha512-fMQLXvJLZxXM4cnPYbsihYVSxrkPhy3bI3gFwO5wJIv4J7WdsRFT5ddQSgpdJQ+u/ndwHZl4rTs0OTuEKYpy7w==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/scopes": "3.1.48", + "@php-wasm/universal": "3.1.48" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/xdebug-bridge": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/xdebug-bridge/-/xdebug-bridge-3.1.48.tgz", + "integrity": "sha512-P2PUgHHAAKs4Bd5EI6arWdojWt+Adw1QBYyotVgRq2z2+ICbUdvSAWiS5NwwFmLJq227vPsNHIgZftuVzhUmBQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/logger": "3.1.48", + "@php-wasm/universal": "3.1.48", + "ws": "8.21.0", + "xml2js": "0.6.2", + "yargs": "17.7.2" + }, + "bin": { + "xdebug-bridge": "xdebug-bridge.js" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@types/aws-lambda": { + "version": "8.10.162", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.162.tgz", + "integrity": "sha512-Fn658grtLOci1oxi1391vvDWJRKNGWRSqfxRkmN/Iy3c0tQH1USMKEXcPYHLvope+ZgTFocx9FRQJx1muBL6qw==", + "license": "MIT" + }, + "node_modules/@types/btoa-lite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/btoa-lite/-/btoa-lite-1.0.2.tgz", + "integrity": "sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg==", + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", + "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@wp-playground/blueprints": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@wp-playground/blueprints/-/blueprints-3.1.48.tgz", + "integrity": "sha512-geJ1EdIWpRFxgnJrJqsYq+YUEJAFenKI6OCRRAfvgRAN+pei7HpCpSEnMcYB2atmpj6DtpqpKGlg3Yf1/EoRBA==", + "dependencies": { + "@php-wasm/logger": "3.1.48", + "@php-wasm/progress": "3.1.48", + "@php-wasm/stream-compression": "3.1.48", + "@php-wasm/universal": "3.1.48", + "@php-wasm/util": "3.1.48", + "@php-wasm/web-service-worker": "3.1.48", + "@wp-playground/common": "3.1.48", + "@wp-playground/storage": "3.1.48", + "@wp-playground/wordpress": "3.1.48", + "ajv": "8.18.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@wp-playground/cli": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@wp-playground/cli/-/cli-3.1.48.tgz", + "integrity": "sha512-24Y640IHzv7t/iEep1cUU5kZwUFTDXqljTRqOtddzQNByZzxL4g0zXSQYVUq6IPCh9rVx3/+elId/Ym6MCzRZA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/cli-util": "3.1.48", + "@php-wasm/logger": "3.1.48", + "@php-wasm/node": "3.1.48", + "@php-wasm/progress": "3.1.48", + "@php-wasm/universal": "3.1.48", + "@php-wasm/util": "3.1.48", + "@php-wasm/xdebug-bridge": "3.1.48", + "@wp-playground/blueprints": "3.1.48", + "@wp-playground/common": "3.1.48", + "@wp-playground/storage": "3.1.48", + "@wp-playground/tools": "3.1.48", + "@wp-playground/wordpress": "3.1.48", + "express": "4.22.2", + "fs-extra": "11.1.1", + "tmp-promise": "3.0.3", + "wasm-feature-detect": "1.8.0", + "yargs": "17.7.2" + }, + "bin": { + "wp-playground-cli": "wp-playground.js" + } + }, + "node_modules/@wp-playground/common": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@wp-playground/common/-/common-3.1.48.tgz", + "integrity": "sha512-+k7Ku33GWROchZmE0mP23aPTdCXxR7uZqsSgfpf+ttjuKB8uOI9TzfcqrRfSoLcqN2Rh7FAvJmtgbcgdOLfl+A==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.48", + "@php-wasm/util": "3.1.48" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@wp-playground/storage": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@wp-playground/storage/-/storage-3.1.48.tgz", + "integrity": "sha512-5WMK4PpL5ETkoMsKPgLCwQ5b8Uz5PgFu0uvG+2tgSu7v8x2/GjuLsLloUl2vrPEu3ZrVDXrMOYnBiX8oEHcAPg==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/stream-compression": "3.1.48", + "@php-wasm/universal": "3.1.48", + "@php-wasm/util": "3.1.48", + "@zip.js/zip.js": "2.7.57", + "isomorphic-git": "1.37.6", + "octokit": "3.1.2", + "pako": "^1.0.10", + "sha.js": "2.4.12" + } + }, + "node_modules/@wp-playground/tools": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@wp-playground/tools/-/tools-3.1.48.tgz", + "integrity": "sha512-HnsBfBkMt7YrhO1d867uQg1OivEoPOKlAuEYwXkabv644O95hO5MrQQWv888qQIPHeKJKzXYjmGEL8KwCy9bGw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/util": "3.1.48", + "@wp-playground/blueprints": "3.1.48" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@wp-playground/wordpress": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@wp-playground/wordpress/-/wordpress-3.1.48.tgz", + "integrity": "sha512-L0TSLNAoCXnHo9Ar4/0oTzM2OmJfhmQbko+ypf7EJhXILN73IsOhbAXp2MWfSUt24+rGGb6Z9BzlXmGPD6Brsw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/logger": "3.1.48", + "@php-wasm/universal": "3.1.48", + "@php-wasm/util": "3.1.48", + "@wp-playground/common": "3.1.48", + "zstddec": "^0.2.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@zip.js/zip.js": { + "version": "2.7.57", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.57.tgz", + "integrity": "sha512-BtonQ1/jDnGiMed6OkV6rZYW78gLmLswkHOzyMrMb+CAR7CZO8phOHO6c2qw6qb1g1betN7kwEHhhZk30dv+NA==", + "license": "BSD-3-Clause", + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=16.5.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anynum": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", + "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/async-lock": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", + "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "license": "Apache-2.0" + }, + "node_modules/body-parser": { + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "license": "MIT" + }, + "node_modules/btoa-lite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", + "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==", + "license": "MIT" + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/clean-git-ref": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/clean-git-ref/-/clean-git-ref-2.0.1.tgz", + "integrity": "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw==", + "license": "Apache-2.0" + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "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==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "license": "ISC" + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/diff3": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz", + "integrity": "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g==", + "license": "MIT" + }, + "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==", + "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/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-builder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.0.tgz", + "integrity": "sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.6.2", + "xml-naming": "^0.3.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.10.1.tgz", + "integrity": "sha512-IEMIf7298kXuZSRFoGfMYrl7is8LpavODgbNz1cwIudv7KwVFnuU+UsMporfq6PD6aXSlawZlARiA3UywCTfMw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^3.0.0", + "fast-xml-builder": "^1.2.0", + "is-unsafe": "^2.0.0", + "path-expression-matcher": "^1.6.2", + "strnum": "^2.4.1", + "xml-naming": "^0.3.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-ext-extra-prebuilt": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/fs-ext-extra-prebuilt/-/fs-ext-extra-prebuilt-2.2.7.tgz", + "integrity": "sha512-Q7rayYRBDIvDF01HWOwSSjoaP+05N1g+o3BXL1Zf8Frw2JkjSmi4EtvCBITuW30l6hB2m2TW1pehdh8wyU/+gw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "nan": "^2.24.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/fs-extra": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "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==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "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==", + "license": "ISC" + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", + "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unsafe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.0.tgz", + "integrity": "sha512-2LdV822R+wmI86unXA93WCFpL6g+av8ynWk0nrHyJqGop5VoocYsSLFgN8jrfalT6iGeLNM4KXuVSsULP53kEA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/isomorphic-git": { + "version": "1.37.6", + "resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.37.6.tgz", + "integrity": "sha512-qr1NFCPsVTZ6YGqTXw0CzamnsHyH9QQ1OTEfeXIweSljRUMzuHFCJdUn0wc6OcjtTDns6knxjPb7N6LmJeftOA==", + "license": "MIT", + "dependencies": { + "async-lock": "^1.4.1", + "clean-git-ref": "^2.0.1", + "crc-32": "^1.2.0", + "diff3": "0.0.3", + "ignore": "^5.1.4", + "minimisted": "^2.0.0", + "pako": "^1.0.10", + "pify": "^4.0.1", + "readable-stream": "^4.0.0", + "sha.js": "^2.4.12", + "simple-get": "^4.0.1" + }, + "bin": { + "isogit": "cli.cjs" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "name": "@wolfy1339/lru-cache", + "version": "11.0.2-patch.1", + "resolved": "https://registry.npmjs.org/@wolfy1339/lru-cache/-/lru-cache-11.0.2-patch.1.tgz", + "integrity": "sha512-BgYZfL2ADCXKOw2wJtkM3slhHotawWkgIRRxq4wEybnZQPjvAp71SPX35xepMykTw8gXlzWcWPTY31hlbnRsDA==", + "license": "ISC", + "engines": { + "node": "18 >=18.20 || 20 || >=22" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "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==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimisted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minimisted/-/minimisted-2.0.1.tgz", + "integrity": "sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/nan": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.28.0.tgz", + "integrity": "sha512-fTsDz99OTq2sVePhGdp4qQhggZFtKr64ZNVyVajRKtMOkJxYekplBh577PiJB12v/D3s2E5cGtOI45LWp6rnLQ==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/octokit": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/octokit/-/octokit-3.1.2.tgz", + "integrity": "sha512-MG5qmrTL5y8KYwFgE1A4JWmgfQBaIETE/lOlfwNYx1QOtCQHGVxkRJmdUJltFc1HVn73d61TlMhMyNTOtMl+ng==", + "license": "MIT", + "dependencies": { + "@octokit/app": "^14.0.2", + "@octokit/core": "^5.0.0", + "@octokit/oauth-app": "^6.0.0", + "@octokit/plugin-paginate-graphql": "^4.0.0", + "@octokit/plugin-paginate-rest": "^9.0.0", + "@octokit/plugin-rest-endpoint-methods": "^10.0.0", + "@octokit/plugin-retry": "^6.0.0", + "@octokit/plugin-throttling": "^8.0.0", + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-expression-matcher": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", + "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.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==", + "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==", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "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==", + "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/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strnum": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.1.tgz", + "integrity": "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "anynum": "^1.0.1" + } + }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "license": "MIT" + }, + "node_modules/universal-github-app-jwt": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.2.0.tgz", + "integrity": "sha512-dncpMpnsKBk0eetwfN8D8OUHGfiDhhJ+mtsbMl+7PfW7mYjiH8LIcqRmYMtzYLgSh47HjfdBtrBwIQ/gizKR3g==", + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "^9.0.0", + "jsonwebtoken": "^9.0.2" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "license": "ISC" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wasm-feature-detect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz", + "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==", + "license": "Apache-2.0" + }, + "node_modules/which-typed-array": { + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-naming": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", + "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "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==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/zstddec": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.2.0.tgz", + "integrity": "sha512-oyPnDa1X5c13+Y7mA/FDMNJrn4S8UNBe0KCqtDmor40Re7ALrPN6npFwyYVRRh+PqozZQdeg23QtbcamZnG5rA==", + "license": "MIT AND BSD-3-Clause" + } + } +} diff --git a/.github/docs-playground-preview/package.json b/.github/docs-playground-preview/package.json new file mode 100644 index 0000000000000..e2e7db936c1ce --- /dev/null +++ b/.github/docs-playground-preview/package.json @@ -0,0 +1,16 @@ +{ + "name": "wordpress-develop-code-reference-playground-preview", + "version": "1.0.0", + "private": true, + "description": "Pinned tooling for the WordPress Core Code Reference Playground preview.", + "license": "GPL-2.0-or-later", + "engines": { + "node": "20.20.2" + }, + "scripts": { + "test": "node --test ../scripts/docs-playground-preview/test/*.test.mjs" + }, + "dependencies": { + "@wp-playground/cli": "3.1.48" + } +} diff --git a/.github/scripts/docs-playground-preview/lib/config.mjs b/.github/scripts/docs-playground-preview/lib/config.mjs new file mode 100644 index 0000000000000..9f4e63ebe31ba --- /dev/null +++ b/.github/scripts/docs-playground-preview/lib/config.mjs @@ -0,0 +1,177 @@ +import { createHash } from 'node:crypto'; +import { readFile } from 'node:fs/promises'; + +const FULL_COMMIT = /^[0-9a-f]{40}$/; +const FULL_DIGEST = /^[0-9a-f]{64}$/; +const VERSION = /^\d+\.\d+\.\d+$/; +const API_BETA_VERSION = /^\d+\.\d+(?:\.\d+)?-(?:beta\d+|RC\d+)$/; +const OFFICIAL_REPOSITORIES = Object.freeze( { + phpdocParser: 'WordPress/phpdoc-parser', + wporgDeveloper: 'WordPress/wporg-developer', + wporgParent2021: 'WordPress/wporg-parent-2021', + wporgMuPlugins: 'WordPress/wporg-mu-plugins', + postsToPosts: 'scribu/wp-posts-to-posts', + codeSyntaxBlock: 'mkaz/code-syntax-block', +} ); +const DEPLOYMENT_REPOSITORIES = new Set( [ + 'WordPress/wordpress-develop', + 'sirreal/wordpress-develop', +] ); +const VERSION_API = + 'https://api.wordpress.org/core/version-check/1.7/?channel=beta'; + +function assertObject( value, label ) { + if ( ! value || typeof value !== 'object' || Array.isArray( value ) ) { + throw new Error( `${ label } must be an object.` ); + } +} + +function assertPositiveInteger( value, label ) { + if ( ! Number.isSafeInteger( value ) || value < 1 ) { + throw new Error( `${ label } must be a positive integer.` ); + } +} + +function assertDigest( value, label ) { + if ( typeof value !== 'string' || ! FULL_DIGEST.test( value ) ) { + throw new Error( `${ label } must be a lowercase SHA-256 digest.` ); + } +} + +export function validateDependencies( dependencies ) { + assertObject( dependencies, 'Dependency manifest' ); + if ( dependencies.schemaVersion !== 1 ) { + throw new Error( `Unsupported dependency schema ${ dependencies.schemaVersion }.` ); + } + assertPositiveInteger( + dependencies.cacheSchemaVersion, + 'cacheSchemaVersion' + ); + assertObject( dependencies.toolchain, 'toolchain' ); + assertObject( dependencies.playground, 'playground' ); + for ( const name of [ 'nodeVersion', 'npmVersion', 'composerVersion' ] ) { + if ( ! VERSION.test( dependencies.toolchain[ name ] ) ) { + throw new Error( `toolchain.${ name } must be an exact version.` ); + } + } + if ( dependencies.playground.cliVersion !== '3.1.48' ) { + throw new Error( 'Unexpected Playground CLI version.' ); + } + if ( dependencies.playground.phpVersion !== '8.4' ) { + throw new Error( 'The Playground PHP version must be 8.4.' ); + } + if ( dependencies.playground.wordpressChannel !== 'beta' ) { + throw new Error( 'The WordPress runtime channel must be beta.' ); + } + if ( dependencies.playground.wordpressVersionApi !== VERSION_API ) { + throw new Error( 'The WordPress beta resolver must use the official API.' ); + } + if ( dependencies.limits?.snapshotBytes !== 104857600 ) { + throw new Error( 'The snapshot limit must be exactly 100 MiB.' ); + } + + assertObject( dependencies.repositories, 'repositories' ); + for ( const [ name, repository ] of Object.entries( + OFFICIAL_REPOSITORIES + ) ) { + const dependency = dependencies.repositories[ name ]; + assertObject( dependency, `repositories.${ name }` ); + if ( dependency.repository !== repository ) { + throw new Error( + `repositories.${ name } must use ${ repository }.` + ); + } + if ( ! FULL_COMMIT.test( dependency.commit ) ) { + throw new Error( + `repositories.${ name }.commit must be a full commit hash.` + ); + } + if ( typeof dependency.path !== 'string' || dependency.path === '' ) { + throw new Error( `repositories.${ name }.path must be nonempty.` ); + } + } + if ( + Object.keys( dependencies.repositories ).length !== + Object.keys( OFFICIAL_REPOSITORIES ).length + ) { + throw new Error( 'The dependency manifest contains an unknown repository.' ); + } + + assertObject( dependencies.validation?.minimumSymbols, 'minimumSymbols' ); + for ( const type of [ + 'classes', + 'methods', + 'functions', + 'hooks', + 'filters', + ] ) { + assertPositiveInteger( + dependencies.validation.minimumSymbols[ type ], + `minimumSymbols.${ type }` + ); + } + return dependencies; +} + +export async function loadDependencies( filename ) { + const bytes = await readFile( filename ); + const dependencies = validateDependencies( JSON.parse( bytes ) ); + return { + dependencies, + digest: createHash( 'sha256' ).update( bytes ).digest( 'hex' ), + }; +} + +export async function resolveWordPressBeta( + fetchImplementation = globalThis.fetch +) { + const response = await fetchImplementation( VERSION_API, { + headers: { Accept: 'application/json' }, + } ); + if ( ! response.ok ) { + throw new Error( `WordPress beta API returned HTTP ${ response.status }.` ); + } + const body = await response.json(); + const offer = body?.offers?.find( + ( candidate ) => + API_BETA_VERSION.test( candidate?.version ) && + candidate.download === + `https://downloads.wordpress.org/release/wordpress-${ candidate.version }.zip` + ); + if ( ! offer ) { + throw new Error( 'The WordPress beta API returned no concrete beta build.' ); + } + return Object.freeze( { + channel: 'beta', + version: offer.version, + downloadUrl: offer.download, + } ); +} + +export function makeBaseCacheKey( inputs ) { + assertObject( inputs, 'Cache inputs' ); + assertPositiveInteger( inputs.schemaVersion, 'Cache schemaVersion' ); + for ( const [ name, value ] of Object.entries( { + platform: inputs.platform, + architecture: inputs.architecture, + phpVersion: inputs.phpVersion, + wordpressVersion: inputs.wordpressVersion, + } ) ) { + if ( typeof value !== 'string' || ! /^[A-Za-z0-9_.-]+$/.test( value ) ) { + throw new Error( `Cache ${ name } is invalid.` ); + } + } + assertDigest( inputs.dependencyDigest, 'Cache dependencyDigest' ); + assertDigest( inputs.harnessDigest, 'Cache harnessDigest' ); + const identity = createHash( 'sha256' ) + .update( JSON.stringify( inputs ) ) + .digest( 'hex' ); + return `docs-preview-base-v${ inputs.schemaVersion }-${ identity }`; +} + +export function isDeploymentEnabled( repository, stagingValue = '' ) { + return ( + DEPLOYMENT_REPOSITORIES.has( repository ) && + ( repository === 'WordPress/wordpress-develop' || stagingValue === 'true' ) + ); +} diff --git a/.github/scripts/docs-playground-preview/test/config.test.mjs b/.github/scripts/docs-playground-preview/test/config.test.mjs new file mode 100644 index 0000000000000..c860c7892ebb6 --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/config.test.mjs @@ -0,0 +1,158 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import { test } from 'node:test'; + +import { + isDeploymentEnabled, + makeBaseCacheKey, + resolveWordPressBeta, + validateDependencies, +} from '../lib/config.mjs'; + +const dependenciesFile = new URL( + '../../../docs-playground-preview/dependencies.json', + import.meta.url +); +const packageFile = new URL( + '../../../docs-playground-preview/package.json', + import.meta.url +); +const nodeVersionFile = new URL( + '../../../docs-playground-preview/.nvmrc', + import.meta.url +); + +async function manifest() { + return JSON.parse( await readFile( dependenciesFile, 'utf8' ) ); +} + +test( 'the checked-in dependency manifest is valid', async () => { + const dependencies = validateDependencies( await manifest() ); + const packageManifest = JSON.parse( await readFile( packageFile, 'utf8' ) ); + assert.equal( dependencies.schemaVersion, 1 ); + assert.equal( + ( await readFile( nodeVersionFile, 'utf8' ) ).trim(), + dependencies.toolchain.nodeVersion + ); + assert.equal( + packageManifest.engines.node, + dependencies.toolchain.nodeVersion + ); + assert.equal( + packageManifest.dependencies[ '@wp-playground/cli' ], + dependencies.playground.cliVersion + ); +} ); + +test( 'dependency repositories and commits are fixed', async () => { + const dependencies = await manifest(); + dependencies.repositories.phpdocParser.repository = 'attacker/parser'; + assert.throws( + () => validateDependencies( dependencies ), + /must use WordPress\/phpdoc-parser/ + ); + + const invalidCommit = await manifest(); + invalidCommit.repositories.wporgDeveloper.commit = 'trunk'; + assert.throws( + () => validateDependencies( invalidCommit ), + /must be a full commit hash/ + ); +} ); + +test( 'toolchain versions are exact and required', async () => { + for ( const name of [ 'nodeVersion', 'npmVersion', 'composerVersion' ] ) { + const dependencies = await manifest(); + delete dependencies.toolchain[ name ]; + assert.throws( + () => validateDependencies( dependencies ), + new RegExp( `toolchain\\.${ name } must be an exact version` ) + ); + } +} ); + +test( 'the beta resolver records a concrete official build', async () => { + const resolved = await resolveWordPressBeta( async ( url ) => { + assert.equal( + url, + 'https://api.wordpress.org/core/version-check/1.7/?channel=beta' + ); + return { + ok: true, + json: async () => ( { + offers: [ + { + response: 'development', + version: '7.2-beta1', + download: + 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', + }, + ], + } ), + }; + } ); + assert.deepEqual( resolved, { + channel: 'beta', + version: '7.2-beta1', + downloadUrl: + 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', + } ); +} ); + +test( 'the beta resolver rejects an unbound download URL', async () => { + await assert.rejects( + resolveWordPressBeta( async () => ( { + ok: true, + json: async () => ( { + offers: [ + { + response: 'autoupdate', + version: '7.2-beta1', + download: 'https://example.com/wordpress.zip', + }, + ], + } ), + } ) ), + /no concrete beta build/ + ); +} ); + +test( 'the cache key changes with every material base input', () => { + const inputs = { + schemaVersion: 1, + platform: 'linux', + architecture: 'x64', + phpVersion: '8.4', + wordpressVersion: '7.2-beta1', + dependencyDigest: 'a'.repeat( 64 ), + harnessDigest: 'b'.repeat( 64 ), + }; + const baseline = makeBaseCacheKey( inputs ); + for ( const [ name, value ] of Object.entries( { + platform: 'darwin', + architecture: 'arm64', + phpVersion: '8.5', + wordpressVersion: '7.2-beta2', + dependencyDigest: 'c'.repeat( 64 ), + harnessDigest: 'd'.repeat( 64 ), + } ) ) { + assert.notEqual( makeBaseCacheKey( { ...inputs, [ name ]: value } ), baseline ); + } +} ); + +test( 'deployment is inert outside the primary and opted-in staging repositories', () => { + assert.equal( isDeploymentEnabled( 'WordPress/wordpress-develop' ), true ); + assert.equal( isDeploymentEnabled( 'sirreal/wordpress-develop' ), false ); + assert.equal( + isDeploymentEnabled( 'sirreal/wordpress-develop', 'true' ), + true + ); + assert.equal( + isDeploymentEnabled( 'somebody/wordpress-develop', 'true' ), + false + ); + assert.equal( + isDeploymentEnabled( 'sirreal/wordpress-develop', 'TRUE' ), + false + ); +} ); From a80f03bbdc0fcf24c2244ac861fd0c3186764358 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 10:39:28 +0400 Subject: [PATCH 02/51] Build/Test Tools: Resolve Code Reference build inputs The base cache is only valid when every material input names the same build. Bind the runner, PHP version, concrete WordPress beta, dependency manifest, and harness into one exact key. Stage only eligible Core PHP as parser input. Fetch the pinned commit before every checkout; otherwise a reused clone breaks as soon as a dependency pin moves. --- .../docs-playground-preview/lib/config.mjs | 8 +- .../docs-playground-preview/lib/files.mjs | 144 ++++++++++++++++ .../docs-playground-preview/lib/inputs.mjs | 77 +++++++++ .../docs-playground-preview/lib/process.mjs | 45 +++++ .../test/config.test.mjs | 5 +- .../test/files.test.mjs | 155 ++++++++++++++++++ 6 files changed, 431 insertions(+), 3 deletions(-) create mode 100644 .github/scripts/docs-playground-preview/lib/files.mjs create mode 100644 .github/scripts/docs-playground-preview/lib/inputs.mjs create mode 100644 .github/scripts/docs-playground-preview/lib/process.mjs create mode 100644 .github/scripts/docs-playground-preview/test/files.test.mjs diff --git a/.github/scripts/docs-playground-preview/lib/config.mjs b/.github/scripts/docs-playground-preview/lib/config.mjs index 9f4e63ebe31ba..72cfd4c5f36e9 100644 --- a/.github/scripts/docs-playground-preview/lib/config.mjs +++ b/.github/scripts/docs-playground-preview/lib/config.mjs @@ -150,10 +150,14 @@ export async function resolveWordPressBeta( export function makeBaseCacheKey( inputs ) { assertObject( inputs, 'Cache inputs' ); - assertPositiveInteger( inputs.schemaVersion, 'Cache schemaVersion' ); + assertPositiveInteger( + inputs.cacheSchemaVersion, + 'Cache cacheSchemaVersion' + ); for ( const [ name, value ] of Object.entries( { platform: inputs.platform, architecture: inputs.architecture, + runnerImage: inputs.runnerImage, phpVersion: inputs.phpVersion, wordpressVersion: inputs.wordpressVersion, } ) ) { @@ -166,7 +170,7 @@ export function makeBaseCacheKey( inputs ) { const identity = createHash( 'sha256' ) .update( JSON.stringify( inputs ) ) .digest( 'hex' ); - return `docs-preview-base-v${ inputs.schemaVersion }-${ identity }`; + return `docs-preview-base-v${ inputs.cacheSchemaVersion }-${ identity }`; } export function isDeploymentEnabled( repository, stagingValue = '' ) { diff --git a/.github/scripts/docs-playground-preview/lib/files.mjs b/.github/scripts/docs-playground-preview/lib/files.mjs new file mode 100644 index 0000000000000..d15ee7f60788e --- /dev/null +++ b/.github/scripts/docs-playground-preview/lib/files.mjs @@ -0,0 +1,144 @@ +import { createHash } from 'node:crypto'; +import { + copyFile, + mkdir, + readdir, + readFile, + rm, + stat, +} from 'node:fs/promises'; +import path from 'node:path'; + +import { run } from './process.mjs'; + +const EXCLUDED_SOURCE_DIRECTORIES = new Set( [ + 'wp-content/plugins', + 'wp-content/themes', +] ); + +export async function exists( filename ) { + try { + await stat( filename ); + return true; + } catch ( error ) { + if ( error.code === 'ENOENT' ) { + return false; + } + throw error; + } +} + +export async function findWordPressSourceRoot( candidate ) { + const root = path.resolve( candidate ); + for ( const directory of [ root, path.join( root, 'src' ) ] ) { + if ( await exists( path.join( directory, 'wp-includes/version.php' ) ) ) { + return directory; + } + } + throw new Error( `No WordPress source tree found beneath ${ root }.` ); +} + +async function listFiles( root, relative = '' ) { + const files = []; + const entries = await readdir( path.join( root, relative ), { + withFileTypes: true, + } ); + for ( const entry of entries.sort( ( a, b ) => a.name.localeCompare( b.name ) ) ) { + const child = relative ? `${ relative }/${ entry.name }` : entry.name; + if ( entry.isDirectory() ) { + files.push( ...( await listFiles( root, child ) ) ); + } else if ( entry.isFile() ) { + files.push( child ); + } + } + return files; +} + +export async function digestTree( root, include = () => true ) { + const hash = createHash( 'sha256' ); + for ( const relative of await listFiles( root ) ) { + if ( ! include( relative ) ) { + continue; + } + const bytes = await readFile( path.join( root, relative ) ); + hash.update( `${ Buffer.byteLength( relative ) }:${ relative }` ); + hash.update( `${ bytes.length }:` ); + hash.update( bytes ); + } + return hash.digest( 'hex' ); +} + +export async function sha256File( filename ) { + return createHash( 'sha256' ) + .update( await readFile( filename ) ) + .digest( 'hex' ); +} + +export async function stageCorePhp( candidate, destination ) { + const source = await findWordPressSourceRoot( candidate ); + await rm( destination, { recursive: true, force: true } ); + let count = 0; + for ( const relative of await listFiles( source ) ) { + if ( + ! relative.endsWith( '.php' ) || + [ ...EXCLUDED_SOURCE_DIRECTORIES ].some( + ( excluded ) => + relative === excluded || relative.startsWith( `${ excluded }/` ) + ) + ) { + continue; + } + const target = path.join( destination, relative ); + await mkdir( path.dirname( target ), { recursive: true } ); + await copyFile( path.join( source, relative ), target ); + count++; + } + if ( count === 0 ) { + throw new Error( 'The WordPress source tree contains no eligible PHP files.' ); + } + return { source, destination, files: count }; +} + +export async function acquireRepositories( + repositories, + destination, + runImplementation = run +) { + await mkdir( destination, { recursive: true } ); + const acquired = {}; + for ( const [ name, dependency ] of Object.entries( repositories ) ) { + const target = path.join( destination, name ); + if ( ! ( await exists( path.join( target, '.git' ) ) ) ) { + await rm( target, { recursive: true, force: true } ); + await runImplementation( + 'git', + [ + 'clone', + '--quiet', + '--no-checkout', + `https://github.com/${ dependency.repository }.git`, + target, + ], + { label: `clone ${ dependency.repository }` } + ); + } + await runImplementation( + 'git', + [ 'fetch', '--quiet', '--depth=1', 'origin', dependency.commit ], + { cwd: target, label: `fetch ${ dependency.repository }` } + ); + await runImplementation( + 'git', + [ + '-c', + 'advice.detachedHead=false', + 'checkout', + '--quiet', + dependency.commit, + ], + { cwd: target, label: `checkout ${ dependency.repository }` } + ); + acquired[ name ] = path.join( target, dependency.path ); + } + return acquired; +} diff --git a/.github/scripts/docs-playground-preview/lib/inputs.mjs b/.github/scripts/docs-playground-preview/lib/inputs.mjs new file mode 100644 index 0000000000000..7247f6f442232 --- /dev/null +++ b/.github/scripts/docs-playground-preview/lib/inputs.mjs @@ -0,0 +1,77 @@ +import { createHash } from 'node:crypto'; +import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import path from 'node:path'; + +import { + loadDependencies, + makeBaseCacheKey, + resolveWordPressBeta, +} from './config.mjs'; +import { digestTree } from './files.mjs'; + +export async function resolveBuildInputs( options ) { + const repositoryRoot = path.resolve( options.repositoryRoot ); + const configRoot = path.join( + repositoryRoot, + '.github/docs-playground-preview' + ); + const scriptsRoot = path.join( + repositoryRoot, + '.github/scripts/docs-playground-preview' + ); + const { dependencies, digest: dependencyDigest } = await loadDependencies( + path.join( configRoot, 'dependencies.json' ) + ); + const wordpress = await resolveWordPressBeta( options.fetchImplementation ); + const configDigest = await digestTree( + configRoot, + ( relative ) => ! relative.startsWith( 'node_modules/' ) + ); + const scriptsDigest = await digestTree( + scriptsRoot, + ( relative ) => ! relative.startsWith( 'test/' ) + ); + const harnessDigest = await digestTreePair( configDigest, scriptsDigest ); + const cacheInputs = { + cacheSchemaVersion: dependencies.cacheSchemaVersion, + platform: options.platform, + architecture: options.architecture, + runnerImage: options.runnerImage, + phpVersion: dependencies.playground.phpVersion, + wordpressVersion: wordpress.version, + dependencyDigest, + harnessDigest, + }; + const cacheKey = makeBaseCacheKey( cacheInputs ); + return { + schemaVersion: 1, + cacheKey, + cacheDirectory: path.join( path.resolve( options.cacheRoot ), cacheKey ), + cacheInputs, + dependencies, + wordpress, + }; +} + +async function digestTreePair( first, second ) { + return createHash( 'sha256' ) + .update( first ) + .update( second ) + .digest( 'hex' ); +} + +export async function writeResolvedInputs( inputs, filename ) { + await mkdir( path.dirname( filename ), { recursive: true } ); + await writeFile( filename, `${ JSON.stringify( inputs, null, 2 ) }\n` ); + if ( process.env.GITHUB_OUTPUT ) { + await writeFile( + process.env.GITHUB_OUTPUT, + `cache-key=${ inputs.cacheKey }\ncache-directory=${ inputs.cacheDirectory }\n`, + { flag: 'a' } + ); + } +} + +export async function readResolvedInputs( filename ) { + return JSON.parse( await readFile( filename, 'utf8' ) ); +} diff --git a/.github/scripts/docs-playground-preview/lib/process.mjs b/.github/scripts/docs-playground-preview/lib/process.mjs new file mode 100644 index 0000000000000..06cf8f6be69e1 --- /dev/null +++ b/.github/scripts/docs-playground-preview/lib/process.mjs @@ -0,0 +1,45 @@ +import { spawn } from 'node:child_process'; +import { writeFile } from 'node:fs/promises'; + +export async function run( command, args, options = {} ) { + const capture = options.capture || Boolean( options.logFile ); + if ( ! options.quiet ) { + console.log( `$ ${ [ command, ...args ].join( ' ' ) }` ); + } + + return new Promise( ( resolve, reject ) => { + const child = spawn( command, args, { + cwd: options.cwd, + env: { ...process.env, ...options.env }, + stdio: capture ? [ 'ignore', 'pipe', 'pipe' ] : 'inherit', + } ); + let stdout = ''; + let stderr = ''; + child.stdout?.on( 'data', ( chunk ) => { + stdout += chunk; + } ); + child.stderr?.on( 'data', ( chunk ) => { + stderr += chunk; + } ); + child.once( 'error', reject ); + child.once( 'close', ( code, signal ) => { + ( async () => { + if ( options.logFile ) { + await writeFile( options.logFile, `${ stdout }${ stderr }` ); + } + const result = { code, signal, stdout, stderr }; + if ( code !== 0 && ! options.allowFailure ) { + const detail = `${ stdout }${ stderr }`.trim(); + throw new Error( + `${ options.label || command } failed${ + signal + ? ` with signal ${ signal }` + : ` with exit code ${ code }` + }${ detail ? `\n${ detail }` : '' }` + ); + } + return result; + } )().then( resolve, reject ); + } ); + } ); +} diff --git a/.github/scripts/docs-playground-preview/test/config.test.mjs b/.github/scripts/docs-playground-preview/test/config.test.mjs index c860c7892ebb6..3eb3461c06c26 100644 --- a/.github/scripts/docs-playground-preview/test/config.test.mjs +++ b/.github/scripts/docs-playground-preview/test/config.test.mjs @@ -119,9 +119,10 @@ test( 'the beta resolver rejects an unbound download URL', async () => { test( 'the cache key changes with every material base input', () => { const inputs = { - schemaVersion: 1, + cacheSchemaVersion: 1, platform: 'linux', architecture: 'x64', + runnerImage: 'ubuntu-24.04', phpVersion: '8.4', wordpressVersion: '7.2-beta1', dependencyDigest: 'a'.repeat( 64 ), @@ -129,8 +130,10 @@ test( 'the cache key changes with every material base input', () => { }; const baseline = makeBaseCacheKey( inputs ); for ( const [ name, value ] of Object.entries( { + cacheSchemaVersion: 2, platform: 'darwin', architecture: 'arm64', + runnerImage: 'macos-15', phpVersion: '8.5', wordpressVersion: '7.2-beta2', dependencyDigest: 'c'.repeat( 64 ), diff --git a/.github/scripts/docs-playground-preview/test/files.test.mjs b/.github/scripts/docs-playground-preview/test/files.test.mjs new file mode 100644 index 0000000000000..8ccb1d2a85bc5 --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/files.test.mjs @@ -0,0 +1,155 @@ +import assert from 'node:assert/strict'; +import { mkdir, mkdtemp, readFile, symlink, writeFile } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { test } from 'node:test'; + +import { + acquireRepositories, + digestTree, + exists, + stageCorePhp, +} from '../lib/files.mjs'; +import { resolveBuildInputs } from '../lib/inputs.mjs'; +import { run } from '../lib/process.mjs'; + +const repositoryRoot = path.resolve( + path.dirname( new URL( import.meta.url ).pathname ), + '../../../..' +); + +test( 'run captures output and reports ordinary child failure', async () => { + const result = await run( + process.execPath, + [ '-e', 'process.stdout.write("ready")' ], + { capture: true, quiet: true } + ); + assert.equal( result.stdout, 'ready' ); + await assert.rejects( + run( process.execPath, [ '-e', 'process.stderr.write("broken");process.exit(2)' ], { + capture: true, + quiet: true, + } ), + /broken/ + ); +} ); + +test( 'stageCorePhp copies only eligible Core PHP source', async () => { + const root = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-source-' ) ); + const source = path.join( root, 'src' ); + for ( const relative of [ + 'wp-includes/version.php', + 'wp-admin/admin.php', + 'wp-content/plugins/hello.php', + 'wp-content/themes/twentytwenty/functions.php', + ] ) { + await mkdir( path.dirname( path.join( source, relative ) ), { + recursive: true, + } ); + await writeFile( path.join( source, relative ), ` { + const root = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-digest-' ) ); + await writeFile( path.join( root, 'a' ), 'one' ); + await writeFile( path.join( root, 'ignored' ), 'first' ); + const first = await digestTree( root, ( relative ) => relative !== 'ignored' ); + await writeFile( path.join( root, 'ignored' ), 'second' ); + assert.equal( + await digestTree( root, ( relative ) => relative !== 'ignored' ), + first + ); + await writeFile( path.join( root, 'a' ), 'two' ); + assert.notEqual( await digestTree( root ), first ); +} ); + +test( 'acquireRepositories uses Git with each validated full commit', async () => { + const root = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-repos-' ) ); + const calls = []; + const fakeRun = async ( command, args, options ) => { + calls.push( { command, args, options } ); + if ( args[ 0 ] === 'clone' ) { + await mkdir( path.join( args.at( -1 ), '.git' ), { recursive: true } ); + } + }; + const repositories = { + parser: { + repository: 'WordPress/phpdoc-parser', + commit: 'a'.repeat( 40 ), + path: '.', + }, + }; + const acquired = await acquireRepositories( repositories, root, fakeRun ); + assert.equal( acquired.parser, path.join( root, 'parser' ) ); + assert.deepEqual( calls[ 0 ].args.slice( 0, 3 ), [ + 'clone', + '--quiet', + '--no-checkout', + ] ); + assert.deepEqual( calls[ 1 ].args, [ + 'fetch', + '--quiet', + '--depth=1', + 'origin', + 'a'.repeat( 40 ), + ] ); + assert.equal( calls[ 2 ].args.at( -1 ), 'a'.repeat( 40 ) ); + + calls.length = 0; + repositories.parser.commit = 'b'.repeat( 40 ); + await acquireRepositories( repositories, root, fakeRun ); + assert.equal( calls.length, 2 ); + assert.equal( calls[ 0 ].args[ 0 ], 'fetch' ); + assert.equal( calls[ 0 ].args.at( -1 ), 'b'.repeat( 40 ) ); + assert.equal( calls[ 1 ].args.at( -1 ), 'b'.repeat( 40 ) ); +} ); + +test( 'resolveBuildInputs binds the concrete beta and runner to an exact cache', async () => { + const cacheRoot = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-cache-' ) ); + const fetchImplementation = async () => ( { + ok: true, + json: async () => ( { + offers: [ + { + response: 'development', + version: '7.2-beta1', + download: + 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', + }, + ], + } ), + } ); + const inputs = await resolveBuildInputs( { + repositoryRoot, + cacheRoot, + platform: 'linux', + architecture: 'x64', + runnerImage: 'ubuntu-24.04', + fetchImplementation, + } ); + assert.equal( inputs.wordpress.version, '7.2-beta1' ); + assert.match( inputs.cacheKey, /^docs-preview-base-v1-[0-9a-f]{64}$/ ); + assert.equal( inputs.cacheDirectory, path.join( cacheRoot, inputs.cacheKey ) ); +} ); From 4e27b0b7a48a0eead277ccaa446ceb5acfaa5e8b Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 10:44:17 +0400 Subject: [PATCH 03/51] Build/Test Tools: Pin Code Reference build tools The DevHub themes do not carry a complete immutable Node toolchain. Letting their ranges resolve at build time makes the invariant cache depend on the day it was built. Pin Yarn and the WordPress build packages in the central manifest and lock their dependency graph. A changed pin now changes the exact base-cache identity. --- .../docs-playground-preview/dependencies.json | 5 +- .../docs-playground-preview/package-lock.json | 24324 ++++++++++++++-- .github/docs-playground-preview/package.json | 5 +- .../docs-playground-preview/lib/config.mjs | 9 +- .../test/config.test.mjs | 21 +- 5 files changed, 22420 insertions(+), 1944 deletions(-) diff --git a/.github/docs-playground-preview/dependencies.json b/.github/docs-playground-preview/dependencies.json index b6653bfb49a40..b8ffebe75e70d 100644 --- a/.github/docs-playground-preview/dependencies.json +++ b/.github/docs-playground-preview/dependencies.json @@ -4,7 +4,10 @@ "toolchain": { "nodeVersion": "20.20.2", "npmVersion": "10.8.2", - "composerVersion": "2.8.12" + "composerVersion": "2.8.12", + "yarnVersion": "1.22.22", + "wpScriptsVersion": "32.6.0", + "wpI18nVersion": "6.24.0" }, "playground": { "cliVersion": "3.1.48", diff --git a/.github/docs-playground-preview/package-lock.json b/.github/docs-playground-preview/package-lock.json index 2e6a587b8088a..087ed80d5d889 100644 --- a/.github/docs-playground-preview/package-lock.json +++ b/.github/docs-playground-preview/package-lock.json @@ -9,2873 +9,23181 @@ "version": "1.0.0", "license": "GPL-2.0-or-later", "dependencies": { - "@wp-playground/cli": "3.1.48" + "@wordpress/i18n": "6.24.0", + "@wordpress/scripts": "32.6.0", + "@wp-playground/cli": "3.1.48", + "yarn": "1.22.22" }, "engines": { "node": "20.20.2" } }, - "node_modules/@nodable/entities": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.0.0.tgz", - "integrity": "sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/nodable" - } - ], - "license": "MIT" - }, - "node_modules/@octokit/app": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@octokit/app/-/app-14.1.0.tgz", - "integrity": "sha512-g3uEsGOQCBl1+W1rgfwoRFUIR6PtvB2T1E4RpygeUU5LrLvlOqcxrt5lfykIeRpUPpupreGJUYl70fqMDXdTpw==", + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", "license": "MIT", "dependencies": { - "@octokit/auth-app": "^6.0.0", - "@octokit/auth-unauthenticated": "^5.0.0", - "@octokit/core": "^5.0.0", - "@octokit/oauth-app": "^6.0.0", - "@octokit/plugin-paginate-rest": "^9.0.0", - "@octokit/types": "^12.0.0", - "@octokit/webhooks": "^12.0.4" - }, - "engines": { - "node": ">= 18" + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" } }, - "node_modules/@octokit/auth-app": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.1.4.tgz", - "integrity": "sha512-QkXkSOHZK4dA5oUqY5Dk3S+5pN2s1igPjEASNQV8/vgJgW034fQWR16u7VsNOK/EljA00eyjYF5mWNxWKWhHRQ==", + "node_modules/@asamuzakjp/css-color/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==", + "license": "ISC" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "license": "MIT", "dependencies": { - "@octokit/auth-oauth-app": "^7.1.0", - "@octokit/auth-oauth-user": "^4.1.0", - "@octokit/request": "^8.3.1", - "@octokit/request-error": "^5.1.0", - "@octokit/types": "^13.1.0", - "deprecation": "^2.3.1", - "lru-cache": "npm:@wolfy1339/lru-cache@^11.0.2-patch.1", - "universal-github-app-jwt": "^1.1.2", - "universal-user-agent": "^6.0.0" + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" } }, - "node_modules/@octokit/auth-app/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@octokit/auth-app/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^24.2.0" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@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/@octokit/auth-oauth-app": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-7.1.0.tgz", - "integrity": "sha512-w+SyJN/b0l/HEb4EOPRudo7uUOSW51jcK1jwLa+4r7PA8FPFpoxEnHBHMITqCsc/3Vo2qqFjgQfz/xUUvsSQnA==", + "node_modules/@babel/core/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { - "@octokit/auth-oauth-device": "^6.1.0", - "@octokit/auth-oauth-user": "^4.1.0", - "@octokit/request": "^8.3.1", - "@octokit/types": "^13.0.0", - "@types/btoa-lite": "^1.0.0", - "btoa-lite": "^1.0.0", - "universal-user-agent": "^6.0.0" + "ms": "^2.1.3" }, "engines": { - "node": ">= 18" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "node_modules/@babel/core/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==", "license": "MIT" }, - "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^24.2.0" + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@octokit/auth-oauth-device": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-6.1.0.tgz", - "integrity": "sha512-FNQ7cb8kASufd6Ej4gnJ3f1QB5vJitkoV1O0/g6e6lUsQ7+VsSNRHRmFScN2tV4IgKA12frrr/cegUs0t+0/Lw==", + "node_modules/@babel/eslint-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.29.7.tgz", + "integrity": "sha512-zxt+UJTOMKvUt3yOg+D58MLuz334pHp93qifMFcjIIO+9hN6t+ufw2gi7vDPMpxvfnHRR+3VVXvIjineCcgyXw==", "license": "MIT", "dependencies": { - "@octokit/oauth-methods": "^4.1.0", - "@octokit/request": "^8.3.1", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^6.0.0" + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" }, "engines": { - "node": ">= 18" + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" } }, - "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" + "node_modules/@babel/eslint-parser/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } }, - "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^24.2.0" + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@octokit/auth-oauth-user": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-4.1.0.tgz", - "integrity": "sha512-FrEp8mtFuS/BrJyjpur+4GARteUCrPeR/tZJzD8YourzoVhRics7u7we/aDcKv+yywRNwNi/P4fRi631rG/OyQ==", + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", "license": "MIT", "dependencies": { - "@octokit/auth-oauth-device": "^6.1.0", - "@octokit/oauth-methods": "^4.1.0", - "@octokit/request": "^8.3.1", - "@octokit/types": "^13.0.0", - "btoa-lite": "^1.0.0", - "universal-user-agent": "^6.0.0" + "@babel/types": "^7.29.7" }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" } }, - "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" - }, - "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^24.2.0" + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@octokit/auth-token": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", - "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", - "license": "MIT", - "engines": { - "node": ">= 18" + "node_modules/@babel/helper-compilation-targets/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==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" } }, - "node_modules/@octokit/auth-unauthenticated": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-5.0.1.tgz", - "integrity": "sha512-oxeWzmBFxWd+XolxKTc4zr+h3mt+yofn4r7OfoIkR/Cj/o70eEGmPsFbueyJE2iBAGpjgTnEOKM3pnuEGVmiqg==", + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", "license": "MIT", "dependencies": { - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^12.0.0" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@octokit/core": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz", - "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.7.tgz", + "integrity": "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==", "license": "MIT", "dependencies": { - "@octokit/auth-token": "^4.0.0", - "@octokit/graphql": "^7.1.0", - "@octokit/request": "^8.4.1", - "@octokit/request-error": "^5.1.1", - "@octokit/types": "^13.0.0", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" + "@babel/helper-annotate-as-pure": "^7.29.7", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@octokit/core/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } }, - "node_modules/@octokit/core/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^24.2.0" + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@octokit/endpoint": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", - "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", + "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { - "@octokit/types": "^13.1.0", - "universal-user-agent": "^6.0.0" + "ms": "^2.1.3" }, "engines": { - "node": ">= 18" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "node_modules/@babel/helper-define-polyfill-provider/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==", "license": "MIT" }, - "node_modules/@octokit/endpoint/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^24.2.0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@octokit/graphql": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", - "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", "license": "MIT", "dependencies": { - "@octokit/request": "^8.4.1", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^6.0.0" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" } }, - "node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" - }, - "node_modules/@octokit/graphql/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^24.2.0" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@octokit/oauth-app": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-6.1.0.tgz", - "integrity": "sha512-nIn/8eUJ/BKUVzxUXd5vpzl1rwaVxMyYbQkNZjHrF7Vk/yu98/YDF/N2KeWO7uZ0g3b5EyiFXFkZI8rJ+DH1/g==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "license": "MIT", "dependencies": { - "@octokit/auth-oauth-app": "^7.0.0", - "@octokit/auth-oauth-user": "^4.0.0", - "@octokit/auth-unauthenticated": "^5.0.0", - "@octokit/core": "^5.0.0", - "@octokit/oauth-authorization-url": "^6.0.2", - "@octokit/oauth-methods": "^4.0.0", - "@types/aws-lambda": "^8.10.83", - "universal-user-agent": "^6.0.0" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@octokit/oauth-authorization-url": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz", - "integrity": "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==", + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" } }, - "node_modules/@octokit/oauth-methods": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.1.0.tgz", - "integrity": "sha512-4tuKnCRecJ6CG6gr0XcEXdZtkTDbfbnD5oaHBmLERTjTMZNi2CbfEHZxPU41xXLDG4DfKf+sonu00zvKI9NSbw==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz", + "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", "license": "MIT", "dependencies": { - "@octokit/oauth-authorization-url": "^6.0.2", - "@octokit/request": "^8.3.1", - "@octokit/request-error": "^5.1.0", - "@octokit/types": "^13.0.0", - "btoa-lite": "^1.0.0" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-wrap-function": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } }, - "node_modules/@octokit/oauth-methods/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^24.2.0" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@octokit/openapi-types": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", - "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", - "license": "MIT" + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@octokit/plugin-paginate-graphql": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-4.0.1.tgz", - "integrity": "sha512-R8ZQNmrIKKpHWC6V2gum4x9LG2qF1RxRjo27gjQcG3j+vf2tLsEfE7I/wRWEPzYMaenr1M+qDAtNcwZve1ce1A==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", "engines": { - "node": ">= 18" + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz", + "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, - "peerDependencies": { - "@octokit/core": ">=5" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz", - "integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==", + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "license": "MIT", "dependencies": { - "@octokit/types": "^12.6.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" }, - "peerDependencies": { - "@octokit/core": "5" + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz", - "integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==", + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.29.7.tgz", + "integrity": "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==", "license": "MIT", "dependencies": { - "@octokit/types": "^12.6.0" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" }, "peerDependencies": { - "@octokit/core": "5" + "@babel/core": "^7.0.0" } }, - "node_modules/@octokit/plugin-retry": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.1.0.tgz", - "integrity": "sha512-WrO3bvq4E1Xh1r2mT9w6SDFg01gFmP81nIG77+p/MqW1JeXXgL++6umim3t6x0Zj5pZm3rXAN+0HEjmmdhIRig==", + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.29.7.tgz", + "integrity": "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==", "license": "MIT", "dependencies": { - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^13.0.0", - "bottleneck": "^2.15.3" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" }, "peerDependencies": { - "@octokit/core": "5" + "@babel/core": "^7.0.0" } }, - "node_modules/@octokit/plugin-retry/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" - }, - "node_modules/@octokit/plugin-retry/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.29.7.tgz", + "integrity": "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==", "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^24.2.0" + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@octokit/plugin-throttling": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-8.2.0.tgz", - "integrity": "sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ==", + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.7.tgz", + "integrity": "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==", "license": "MIT", "dependencies": { - "@octokit/types": "^12.2.0", - "bottleneck": "^2.15.3" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" }, "peerDependencies": { - "@octokit/core": "^5.0.0" + "@babel/core": "^7.0.0" } }, - "node_modules/@octokit/request": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", - "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.29.7.tgz", + "integrity": "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==", "license": "MIT", "dependencies": { - "@octokit/endpoint": "^9.0.6", - "@octokit/request-error": "^5.1.1", - "@octokit/types": "^13.1.0", - "universal-user-agent": "^6.0.0" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7" }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" } }, - "node_modules/@octokit/request-error": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", - "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.29.7.tgz", + "integrity": "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==", "license": "MIT", "dependencies": { - "@octokit/types": "^13.1.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@octokit/request-error/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "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==", "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^24.2.0" + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@octokit/request/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" + "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==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@octokit/request/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "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==", "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^24.2.0" + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@octokit/types": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", - "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "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==", "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^20.0.0" + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@octokit/webhooks": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-12.3.2.tgz", - "integrity": "sha512-exj1MzVXoP7xnAcAB3jZ97pTvVPkQF9y6GA/dvYC47HV7vLv+24XRS6b/v/XnyikpEuvMhugEXdGtAlU086WkQ==", + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.29.7.tgz", + "integrity": "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==", "license": "MIT", "dependencies": { - "@octokit/request-error": "^5.0.0", - "@octokit/webhooks-methods": "^4.1.0", - "@octokit/webhooks-types": "7.6.1", - "aggregate-error": "^3.1.0" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@octokit/webhooks-methods": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-4.1.0.tgz", - "integrity": "sha512-zoQyKw8h9STNPqtm28UGOYFE7O6D4Il8VJwhAtMHFt2C4L0VQT1qGKLeefUOqHNs1mNRYSadVv7x0z8U2yyeWQ==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": ">= 18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@octokit/webhooks-types": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-7.6.1.tgz", - "integrity": "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw==", - "license": "MIT" + "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==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@php-wasm/cli-util": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@php-wasm/cli-util/-/cli-util-3.1.48.tgz", - "integrity": "sha512-zNLQtNYVY9tFQEOQClFS6cNK+ntFq2HX0sPHb7HGRYPFV3vom1/Y42F7HQZBxUP+05N2VX0HL0csKlp41AGbeQ==", - "license": "GPL-2.0-or-later", + "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==", + "license": "MIT", "dependencies": { - "@php-wasm/util": "3.1.48", - "fast-xml-parser": "^5.8.0", - "jsonc-parser": "3.3.1" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@php-wasm/logger": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@php-wasm/logger/-/logger-3.1.48.tgz", - "integrity": "sha512-BZdRRIs+Zc3IY3tme0KKsFOOtg/petabYftlXEHSFll/TKUEfOSV7GvMGKmF3tRkZFzadJzY9FClRV6kSIal4A==", - "license": "GPL-2.0-or-later", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@php-wasm/node": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@php-wasm/node/-/node-3.1.48.tgz", - "integrity": "sha512-VGe/ZmOmNJ9WjWOY9uApw7yEr+PS6mI9BVMVbUa/akWhZnloOD/J942izvFcaSUyn+UkYLOuFJkgY3ySulUtgw==", - "license": "GPL-2.0-or-later", + "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==", + "license": "MIT", "dependencies": { - "@php-wasm/cli-util": "3.1.48", - "@php-wasm/logger": "3.1.48", - "@php-wasm/node-5-2": "3.1.48", - "@php-wasm/node-7-4": "3.1.48", - "@php-wasm/node-8-0": "3.1.48", - "@php-wasm/node-8-1": "3.1.48", - "@php-wasm/node-8-2": "3.1.48", - "@php-wasm/node-8-3": "3.1.48", - "@php-wasm/node-8-4": "3.1.48", - "@php-wasm/node-8-5": "3.1.48", - "@php-wasm/universal": "3.1.48", - "@php-wasm/util": "3.1.48", - "fs-ext-extra-prebuilt": "2.2.7", - "wasm-feature-detect": "1.8.0", - "ws": "8.21.0" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@php-wasm/node-5-2": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@php-wasm/node-5-2/-/node-5-2-3.1.48.tgz", - "integrity": "sha512-c3n+9Qs6vQv4haKRmihAmm/90D+r4TzSZcqd6maRXZbpmX2Mw3eymIpkQebRnW/LmOxOO7FC54/jmKHQZSmNQQ==", - "license": "GPL-2.0-or-later", + "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==", + "license": "MIT", "dependencies": { - "@php-wasm/universal": "3.1.48", - "wasm-feature-detect": "1.8.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@php-wasm/node-7-4": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@php-wasm/node-7-4/-/node-7-4-3.1.48.tgz", - "integrity": "sha512-QzasgDrgbov6jkuokqB7yFtuP7SYXLwFZEighgb6HqcppAa3RAKHcsSsArDhhOKbu6NicmkjybdNrOKW9DuP7Q==", - "license": "GPL-2.0-or-later", + "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==", + "license": "MIT", "dependencies": { - "@php-wasm/universal": "3.1.48", - "wasm-feature-detect": "1.8.0" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@php-wasm/node-8-0": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@php-wasm/node-8-0/-/node-8-0-3.1.48.tgz", - "integrity": "sha512-sGTAQdNp4URnBb8/b9IwbgdnC9Vh895JCY0lLS72lkWbrRgwvwsWyAY1iu38LnhHM1D+/xMhZj2wNbMvdPzzkg==", - "license": "GPL-2.0-or-later", + "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==", + "license": "MIT", "dependencies": { - "@php-wasm/universal": "3.1.48", - "wasm-feature-detect": "1.8.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@php-wasm/node-8-1": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@php-wasm/node-8-1/-/node-8-1-3.1.48.tgz", - "integrity": "sha512-sruxX5piVy0+9PBibFkgF+DEHGcPzOdCD2e7deLbYXMzLvOLGmqzMb4z7ng88WC6ZDk0Pk2jOTTq+08sG9BuMA==", - "license": "GPL-2.0-or-later", + "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==", + "license": "MIT", "dependencies": { - "@php-wasm/universal": "3.1.48", - "wasm-feature-detect": "1.8.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@php-wasm/node-8-2": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@php-wasm/node-8-2/-/node-8-2-3.1.48.tgz", - "integrity": "sha512-Dyp0SMyNee8FHFGed3KyyYiAGtHo9c2cFkJ/e3IdtX/+Uqo2pvFM8zcFLuWhwOiMJrEJPYl5FoXuER2H5pkHBw==", - "license": "GPL-2.0-or-later", + "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==", + "license": "MIT", "dependencies": { - "@php-wasm/universal": "3.1.48", - "wasm-feature-detect": "1.8.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@php-wasm/node-8-3": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@php-wasm/node-8-3/-/node-8-3-3.1.48.tgz", - "integrity": "sha512-vRlGi+Kd8jCfgtqGOZHriIp0ufyIMaVuPMGUbq99CKjvunyXouWE88Zn+lS/LQ7KTEhO86Xq4MEgzK1/huq4LA==", - "license": "GPL-2.0-or-later", + "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==", + "license": "MIT", "dependencies": { - "@php-wasm/universal": "3.1.48", - "wasm-feature-detect": "1.8.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@php-wasm/node-8-4": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@php-wasm/node-8-4/-/node-8-4-3.1.48.tgz", - "integrity": "sha512-TZfwQqdKYukMU5RXbTHgVx6YeiAlb7bN0NUVY5hDvMfioGNMnpBL0sWWf44ULj5L5TK39MbbYr0SQ7i5Rh2cyw==", - "license": "GPL-2.0-or-later", + "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==", + "license": "MIT", "dependencies": { - "@php-wasm/universal": "3.1.48", - "wasm-feature-detect": "1.8.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@php-wasm/node-8-5": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@php-wasm/node-8-5/-/node-8-5-3.1.48.tgz", - "integrity": "sha512-I3q1MKXjL2EfvpwC1PT2QiyLcPjOdvUVxP4x+8q94x2JMhtgH19lGMhxX9jnBjOGP8FAQ8cOcQ7JtxXQcWg9GQ==", - "license": "GPL-2.0-or-later", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "license": "MIT", "dependencies": { - "@php-wasm/universal": "3.1.48", - "wasm-feature-detect": "1.8.0" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@php-wasm/progress": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@php-wasm/progress/-/progress-3.1.48.tgz", - "integrity": "sha512-KFiI3Q9zDQ9NC7HGl5e/jx0BAvJYpyN1shv8vvJbeicXeuOoJnJS9C3XehRWlT9aRCIZ0qpeRSrmR3rR5ue0aQ==", - "license": "GPL-2.0-or-later", + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "license": "MIT", "dependencies": { - "@php-wasm/logger": "3.1.48" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@php-wasm/scopes": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@php-wasm/scopes/-/scopes-3.1.48.tgz", - "integrity": "sha512-3ncWvaCPs9gLyvskAplZdIMDyImT2MsJ8aRXrLMunvtjq1dwaPcCmvbzv1Z2czb2m23bxgTqalVn7jEDJbhlGw==", - "license": "GPL-2.0-or-later", + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", + "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@php-wasm/stream-compression": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@php-wasm/stream-compression/-/stream-compression-3.1.48.tgz", - "integrity": "sha512-bQzUn3gNAHcRUgkks5hrZc7jS+jirow+xORkMPsINobi+Y6j2za/OAaGMd9THo9298ubH6dGeeapZ5q58C0hsg==", - "license": "GPL-2.0-or-later", + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", + "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==", + "license": "MIT", "dependencies": { - "@php-wasm/util": "3.1.48" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@php-wasm/universal": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@php-wasm/universal/-/universal-3.1.48.tgz", - "integrity": "sha512-CtopMuRcJwo8FVsmrzPhpvs/OsoGXYduHOemI78MSP/wfufosZcAg654J+d8IcF9xq6VTJjir3uxkE3t+bct4g==", - "license": "GPL-2.0-or-later", + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz", + "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==", + "license": "MIT", "dependencies": { - "@php-wasm/logger": "3.1.48", - "@php-wasm/progress": "3.1.48", - "@php-wasm/stream-compression": "3.1.48", - "@php-wasm/util": "3.1.48", - "ini": "4.1.2" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7" }, "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@php-wasm/util": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@php-wasm/util/-/util-3.1.48.tgz", - "integrity": "sha512-MvHDY9K4ErSAOOAp5BypIX6ITTaYQugtbdcGR76ZCtjGCS91QBOe0Mm+75lfEOKnLOKltRdgUmfhV6Jb0oDZeg==", + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.29.7.tgz", + "integrity": "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@php-wasm/web-service-worker": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@php-wasm/web-service-worker/-/web-service-worker-3.1.48.tgz", - "integrity": "sha512-fMQLXvJLZxXM4cnPYbsihYVSxrkPhy3bI3gFwO5wJIv4J7WdsRFT5ddQSgpdJQ+u/ndwHZl4rTs0OTuEKYpy7w==", - "license": "GPL-2.0-or-later", + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz", + "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", + "license": "MIT", "dependencies": { - "@php-wasm/scopes": "3.1.48", - "@php-wasm/universal": "3.1.48" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@php-wasm/xdebug-bridge": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@php-wasm/xdebug-bridge/-/xdebug-bridge-3.1.48.tgz", - "integrity": "sha512-P2PUgHHAAKs4Bd5EI6arWdojWt+Adw1QBYyotVgRq2z2+ICbUdvSAWiS5NwwFmLJq227vPsNHIgZftuVzhUmBQ==", - "license": "GPL-2.0-or-later", + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", + "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", + "license": "MIT", "dependencies": { - "@php-wasm/logger": "3.1.48", - "@php-wasm/universal": "3.1.48", - "ws": "8.21.0", - "xml2js": "0.6.2", - "yargs": "17.7.2" - }, - "bin": { - "xdebug-bridge": "xdebug-bridge.js" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@types/aws-lambda": { - "version": "8.10.162", - "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.162.tgz", - "integrity": "sha512-Fn658grtLOci1oxi1391vvDWJRKNGWRSqfxRkmN/Iy3c0tQH1USMKEXcPYHLvope+ZgTFocx9FRQJx1muBL6qw==", - "license": "MIT" - }, - "node_modules/@types/btoa-lite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/btoa-lite/-/btoa-lite-1.0.2.tgz", - "integrity": "sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg==", - "license": "MIT" - }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.10", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", - "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.29.7.tgz", + "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==", "license": "MIT", "dependencies": { - "@types/ms": "*", - "@types/node": "*" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" } }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", - "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", + "node_modules/@babel/plugin-transform-classes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz", + "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", "license": "MIT", "dependencies": { - "undici-types": "~8.3.0" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@wp-playground/blueprints": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@wp-playground/blueprints/-/blueprints-3.1.48.tgz", - "integrity": "sha512-geJ1EdIWpRFxgnJrJqsYq+YUEJAFenKI6OCRRAfvgRAN+pei7HpCpSEnMcYB2atmpj6DtpqpKGlg3Yf1/EoRBA==", + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.29.7.tgz", + "integrity": "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==", + "license": "MIT", "dependencies": { - "@php-wasm/logger": "3.1.48", - "@php-wasm/progress": "3.1.48", - "@php-wasm/stream-compression": "3.1.48", - "@php-wasm/universal": "3.1.48", - "@php-wasm/util": "3.1.48", - "@php-wasm/web-service-worker": "3.1.48", - "@wp-playground/common": "3.1.48", - "@wp-playground/storage": "3.1.48", - "@wp-playground/wordpress": "3.1.48", - "ajv": "8.18.0" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/template": "^7.29.7" }, "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@wp-playground/cli": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@wp-playground/cli/-/cli-3.1.48.tgz", - "integrity": "sha512-24Y640IHzv7t/iEep1cUU5kZwUFTDXqljTRqOtddzQNByZzxL4g0zXSQYVUq6IPCh9rVx3/+elId/Ym6MCzRZA==", - "license": "GPL-2.0-or-later", + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", + "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", + "license": "MIT", "dependencies": { - "@php-wasm/cli-util": "3.1.48", - "@php-wasm/logger": "3.1.48", - "@php-wasm/node": "3.1.48", - "@php-wasm/progress": "3.1.48", - "@php-wasm/universal": "3.1.48", - "@php-wasm/util": "3.1.48", - "@php-wasm/xdebug-bridge": "3.1.48", - "@wp-playground/blueprints": "3.1.48", - "@wp-playground/common": "3.1.48", - "@wp-playground/storage": "3.1.48", - "@wp-playground/tools": "3.1.48", - "@wp-playground/wordpress": "3.1.48", - "express": "4.22.2", - "fs-extra": "11.1.1", - "tmp-promise": "3.0.3", - "wasm-feature-detect": "1.8.0", - "yargs": "17.7.2" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, - "bin": { - "wp-playground-cli": "wp-playground.js" + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@wp-playground/common": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@wp-playground/common/-/common-3.1.48.tgz", - "integrity": "sha512-+k7Ku33GWROchZmE0mP23aPTdCXxR7uZqsSgfpf+ttjuKB8uOI9TzfcqrRfSoLcqN2Rh7FAvJmtgbcgdOLfl+A==", - "license": "GPL-2.0-or-later", + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.29.7.tgz", + "integrity": "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==", + "license": "MIT", "dependencies": { - "@php-wasm/universal": "3.1.48", - "@php-wasm/util": "3.1.48" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@wp-playground/storage": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@wp-playground/storage/-/storage-3.1.48.tgz", - "integrity": "sha512-5WMK4PpL5ETkoMsKPgLCwQ5b8Uz5PgFu0uvG+2tgSu7v8x2/GjuLsLloUl2vrPEu3ZrVDXrMOYnBiX8oEHcAPg==", - "license": "GPL-2.0-or-later", + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.29.7.tgz", + "integrity": "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==", + "license": "MIT", "dependencies": { - "@php-wasm/stream-compression": "3.1.48", - "@php-wasm/universal": "3.1.48", - "@php-wasm/util": "3.1.48", - "@zip.js/zip.js": "2.7.57", - "isomorphic-git": "1.37.6", - "octokit": "3.1.2", - "pako": "^1.0.10", - "sha.js": "2.4.12" + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@wp-playground/tools": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@wp-playground/tools/-/tools-3.1.48.tgz", - "integrity": "sha512-HnsBfBkMt7YrhO1d867uQg1OivEoPOKlAuEYwXkabv644O95hO5MrQQWv888qQIPHeKJKzXYjmGEL8KwCy9bGw==", - "license": "GPL-2.0-or-later", + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==", + "license": "MIT", "dependencies": { - "@php-wasm/util": "3.1.48", - "@wp-playground/blueprints": "3.1.48" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@wp-playground/wordpress": { - "version": "3.1.48", - "resolved": "https://registry.npmjs.org/@wp-playground/wordpress/-/wordpress-3.1.48.tgz", - "integrity": "sha512-L0TSLNAoCXnHo9Ar4/0oTzM2OmJfhmQbko+ypf7EJhXILN73IsOhbAXp2MWfSUt24+rGGb6Z9BzlXmGPD6Brsw==", - "license": "GPL-2.0-or-later", + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.29.7.tgz", + "integrity": "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==", + "license": "MIT", "dependencies": { - "@php-wasm/logger": "3.1.48", - "@php-wasm/universal": "3.1.48", - "@php-wasm/util": "3.1.48", - "@wp-playground/common": "3.1.48", - "zstddec": "^0.2.0" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@zip.js/zip.js": { - "version": "2.7.57", - "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.57.tgz", - "integrity": "sha512-BtonQ1/jDnGiMed6OkV6rZYW78gLmLswkHOzyMrMb+CAR7CZO8phOHO6c2qw6qb1g1betN7kwEHhhZk30dv+NA==", - "license": "BSD-3-Clause", + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.29.7.tgz", + "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7" + }, "engines": { - "bun": ">=0.7.0", - "deno": ">=1.0.0", - "node": ">=16.5.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.29.7.tgz", + "integrity": "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==", "license": "MIT", "dependencies": { - "event-target-shim": "^5.0.0" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=6.5" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.29.7.tgz", + "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==", "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">= 0.6" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz", + "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", "license": "MIT", "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { - "node": ">=8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.29.7.tgz", + "integrity": "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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==", + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.29.7.tgz", + "integrity": "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==", "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": ">=8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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==", + "node_modules/@babel/plugin-transform-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.29.7.tgz", + "integrity": "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==", "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=8" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/anynum": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", - "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/async-lock": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", - "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==", - "license": "MIT" + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.29.7.tgz", + "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.29.7.tgz", + "integrity": "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==", "license": "MIT", "dependencies": { - "possible-typed-array-names": "^1.0.0" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">= 0.4" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "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/@babel/plugin-transform-modules-amd": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.29.7.tgz", + "integrity": "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/before-after-hook": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", - "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", - "license": "Apache-2.0" + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/body-parser": { - "version": "1.20.6", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", - "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.8.tgz", + "integrity": "sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA==", "license": "MIT", "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.15.1", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.8" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/bottleneck": { - "version": "2.19.5", - "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", - "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", - "license": "MIT" - }, - "node_modules/btoa-lite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", - "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==", - "license": "MIT" - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.29.7.tgz", + "integrity": "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==", "license": "MIT", "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": ">= 0.8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/call-bind": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", - "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.29.7.tgz", + "integrity": "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "get-intrinsic": "^1.3.0", - "set-function-length": "^1.2.2" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">= 0.4" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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==", + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", + "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">= 0.4" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.29.7.tgz", + "integrity": "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">= 0.4" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/clean-git-ref": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/clean-git-ref/-/clean-git-ref-2.0.1.tgz", - "integrity": "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw==", - "license": "Apache-2.0" - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.29.7.tgz", + "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==", "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, "engines": { - "node": ">=6" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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==", - "license": "ISC", + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.29.7.tgz", + "integrity": "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==", + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7" }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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==", + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz", + "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=7.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.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==", - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", + "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", "license": "MIT", "dependencies": { - "safe-buffer": "5.2.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { - "node": ">= 0.6" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.29.7.tgz", + "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==", "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": ">= 0.6" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz", + "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": ">= 0.6" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", - "license": "MIT" - }, - "node_modules/crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", - "license": "Apache-2.0", - "bin": { - "crc32": "bin/crc32.njs" + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz", + "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=0.8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.29.7.tgz", + "integrity": "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==", "license": "MIT", "dependencies": { - "ms": "2.0.0" + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "node_modules/@babel/plugin-transform-react-constant-elements": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.29.7.tgz", + "integrity": "sha512-J0wGhKan+rIiE2OhfhRptySLrJ6SjQYM6b6N1FMlhyhCcw1Mig8vQjWchyB+bgHGDvaWo6Diu6CLRMra2uMtmg==", "license": "MIT", "dependencies": { - "mimic-response": "^3.1.0" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=10" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.29.7.tgz", + "integrity": "sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==", "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">= 0.4" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.29.7.tgz", + "integrity": "sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==", "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { - "node": ">= 0.8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/deprecation": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", - "license": "ISC" + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.29.7.tgz", + "integrity": "sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.29.7.tgz", + "integrity": "sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA==", "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/diff3": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz", - "integrity": "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g==", - "license": "MIT" + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.8.tgz", + "integrity": "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.29.7.tgz", + "integrity": "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">= 0.4" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.29.7.tgz", + "integrity": "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==", + "license": "MIT", "dependencies": { - "safe-buffer": "^5.0.1" + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "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==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.7.tgz", + "integrity": "sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q==", "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" + }, "engines": { - "node": ">= 0.8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "node_modules/@babel/plugin-transform-runtime/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "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==", + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz", + "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==", "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": ">= 0.4" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "node_modules/@babel/plugin-transform-spread": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.29.8.tgz", + "integrity": "sha512-4S9ksMGVWUshvgK0mKfvZky7leuG5/uoFVwMpAomJ8bMoDJiNHRVmc1EglwW/CmGVSqqWpEbXm9FmbRit22qoA==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { - "node": ">= 0.4" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.29.7.tgz", + "integrity": "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==", "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": ">=6" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz", + "integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==", "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": ">= 0.6" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.29.7.tgz", + "integrity": "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==", "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": ">=6" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" + }, "engines": { - "node": ">=0.8.x" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/express": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", - "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.29.7.tgz", + "integrity": "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==", "license": "MIT", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.5", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", - "proxy-addr": "~2.0.7", - "qs": "~6.15.1", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">= 0.10.0" + "node": ">=6.9.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", - "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fast-xml-builder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.0.tgz", - "integrity": "sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.29.7.tgz", + "integrity": "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==", "license": "MIT", "dependencies": { - "path-expression-matcher": "^1.6.2", - "xml-naming": "^0.3.0" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/fast-xml-parser": { - "version": "5.10.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.10.1.tgz", - "integrity": "sha512-IEMIf7298kXuZSRFoGfMYrl7is8LpavODgbNz1cwIudv7KwVFnuU+UsMporfq6PD6aXSlawZlARiA3UywCTfMw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz", + "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==", "license": "MIT", "dependencies": { - "@nodable/entities": "^3.0.0", - "fast-xml-builder": "^1.2.0", - "is-unsafe": "^2.0.0", - "path-expression-matcher": "^1.6.2", - "strnum": "^2.4.1", - "xml-naming": "^0.3.0" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, - "bin": { - "fxparser": "src/cli/cli.js" + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/finalhandler": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.29.7.tgz", + "integrity": "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "statuses": "~2.0.2", - "unpipe": "~1.0.0" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">= 0.8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "node_modules/@babel/preset-env": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.7.tgz", + "integrity": "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==", "license": "MIT", "dependencies": { - "is-callable": "^1.2.7" + "@babel/compat-data": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7", + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.29.7", + "@babel/plugin-syntax-import-attributes": "^7.29.7", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.29.7", + "@babel/plugin-transform-async-generator-functions": "^7.29.7", + "@babel/plugin-transform-async-to-generator": "^7.29.7", + "@babel/plugin-transform-block-scoped-functions": "^7.29.7", + "@babel/plugin-transform-block-scoping": "^7.29.7", + "@babel/plugin-transform-class-properties": "^7.29.7", + "@babel/plugin-transform-class-static-block": "^7.29.7", + "@babel/plugin-transform-classes": "^7.29.7", + "@babel/plugin-transform-computed-properties": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-dotall-regex": "^7.29.7", + "@babel/plugin-transform-duplicate-keys": "^7.29.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-dynamic-import": "^7.29.7", + "@babel/plugin-transform-explicit-resource-management": "^7.29.7", + "@babel/plugin-transform-exponentiation-operator": "^7.29.7", + "@babel/plugin-transform-export-namespace-from": "^7.29.7", + "@babel/plugin-transform-for-of": "^7.29.7", + "@babel/plugin-transform-function-name": "^7.29.7", + "@babel/plugin-transform-json-strings": "^7.29.7", + "@babel/plugin-transform-literals": "^7.29.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.29.7", + "@babel/plugin-transform-member-expression-literals": "^7.29.7", + "@babel/plugin-transform-modules-amd": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-modules-systemjs": "^7.29.7", + "@babel/plugin-transform-modules-umd": "^7.29.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-new-target": "^7.29.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7", + "@babel/plugin-transform-numeric-separator": "^7.29.7", + "@babel/plugin-transform-object-rest-spread": "^7.29.7", + "@babel/plugin-transform-object-super": "^7.29.7", + "@babel/plugin-transform-optional-catch-binding": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/plugin-transform-private-methods": "^7.29.7", + "@babel/plugin-transform-private-property-in-object": "^7.29.7", + "@babel/plugin-transform-property-literals": "^7.29.7", + "@babel/plugin-transform-regenerator": "^7.29.7", + "@babel/plugin-transform-regexp-modifiers": "^7.29.7", + "@babel/plugin-transform-reserved-words": "^7.29.7", + "@babel/plugin-transform-shorthand-properties": "^7.29.7", + "@babel/plugin-transform-spread": "^7.29.7", + "@babel/plugin-transform-sticky-regex": "^7.29.7", + "@babel/plugin-transform-template-literals": "^7.29.7", + "@babel/plugin-transform-typeof-symbol": "^7.29.7", + "@babel/plugin-transform-unicode-escapes": "^7.29.7", + "@babel/plugin-transform-unicode-property-regex": "^7.29.7", + "@babel/plugin-transform-unicode-regex": "^7.29.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.29.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", + "semver": "^6.3.1" }, "engines": { - "node": ">= 0.4" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "license": "MIT", - "engines": { - "node": ">= 0.6" + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "node_modules/@babel/preset-react": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.29.7.tgz", + "integrity": "sha512-C+PV1TFUPTmBQGoPBL8j2QmLpZ117YTCwxIZeJOM96GbYMFSc7/pOXU5lVykwnZxyTqQxRsvoRk6f2FktZgGHA==", "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-transform-react-display-name": "^7.29.7", + "@babel/plugin-transform-react-jsx": "^7.29.7", + "@babel/plugin-transform-react-jsx-development": "^7.29.7", + "@babel/plugin-transform-react-pure-annotations": "^7.29.7" + }, "engines": { - "node": ">= 0.6" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/fs-ext-extra-prebuilt": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/fs-ext-extra-prebuilt/-/fs-ext-extra-prebuilt-2.2.7.tgz", - "integrity": "sha512-Q7rayYRBDIvDF01HWOwSSjoaP+05N1g+o3BXL1Zf8Frw2JkjSmi4EtvCBITuW30l6hB2m2TW1pehdh8wyU/+gw==", - "hasInstallScript": true, + "node_modules/@babel/preset-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", + "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", "license": "MIT", "dependencies": { - "nan": "^2.24.0" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7" }, "engines": { - "node": ">= 8.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/fs-extra": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", - "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": ">=14.14" + "node": ">=6.9.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==", + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=6.9.0" } }, - "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==", + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "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" + "ms": "^2.1.3" }, "engines": { - "node": ">= 0.4" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "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==", + "node_modules/@babel/traverse/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==", + "license": "MIT" + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "license": "MIT", "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { - "node": ">= 0.4" + "node": ">=6.9.0" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "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==", + "license": "MIT" + }, + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" } }, - "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==", - "license": "ISC" - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "node_modules/@cacheable/memory/node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0" + "hashery": "^1.4.0", + "hookified": "^1.15.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" } }, - "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==", + "node_modules/@cacheable/memory/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "@keyv/serialize": "^1.1.1" } }, - "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==", + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", "license": "MIT", "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "hashery": "^1.5.1", + "keyv": "^5.6.0" } }, - "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==", + "node_modules/@cacheable/utils/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", "license": "MIT", "dependencies": { - "function-bind": "^1.1.2" - }, + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "engines": { - "node": ">= 0.4" + "node": ">=18" } }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, "engines": { - "node": ">= 0.8" + "node": ">=18" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" }, "engines": { - "node": ">=0.10.0" + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", "funding": [ { "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "url": "https://github.com/sponsors/csstools" }, { - "type": "consulting", - "url": "https://feross.org/support" + "type": "opencollective", + "url": "https://opencollective.com/csstools" } ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "license": "MIT", "engines": { - "node": ">= 4" + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz", + "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", - "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", - "license": "ISC", + "node_modules/@csstools/media-query-list-parser": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-3.0.1.tgz", + "integrity": "sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1" } }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", "license": "MIT", "engines": { - "node": ">= 0.10" + "node": ">=10.0.0" } }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "node_modules/@dual-bundle/import-meta-resolve": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.2.1.tgz", + "integrity": "sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==", "license": "MIT", - "engines": { - "node": ">= 0.4" - }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "github", + "url": "https://github.com/sponsors/JounQin" } }, - "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==", + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "license": "MIT", - "engines": { - "node": ">=8" + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" } }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "license": "MIT", + "optional": true, "dependencies": { - "which-typed-array": "^1.1.16" + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.50.2", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.50.2.tgz", + "integrity": "sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6", + "@typescript-eslint/types": "^8.11.0", + "comment-parser": "1.4.1", + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~4.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-plugin-eslint-comments": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-4.7.2.tgz", + "integrity": "sha512-LF03qURSwEWm2dz5wtdDCzNk+7Opl0X7q6I3undsaIuNsEiNvRV3BCtqu14Q/6Pzg1tBj44LcxpW2EpSLZStZw==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^4.0.0", + "ignore": "^7.0.5" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/@eslint-community/eslint-plugin-eslint-comments/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/compat": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.1.0.tgz", + "integrity": "sha512-LgaSCymEpw7tF53xvDw9SNsraPb1IBHxpdABIOM0hW8UAlP8znrjYtuxfR58FSJ3L9BhwD+FaPRFQpZq84Nh6g==", + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "peerDependencies": { + "eslint": "^8.40 || 9 || 10" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array/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==", + "license": "MIT", + "peer": true + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.0", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0", + "peer": true + }, + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT", + "peer": true + }, + "node_modules/@eslint/eslintrc/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==", + "license": "MIT", + "peer": true + }, + "node_modules/@eslint/js": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.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==", + "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==", + "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==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@hapi/address": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-5.1.1.tgz", + "integrity": "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@hapi/formula": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-3.0.2.tgz", + "integrity": "sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/hoek": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz", + "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/pinpoint": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.1.tgz", + "integrity": "sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/tlds": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.7.tgz", + "integrity": "sha512-MgNjRwy9Ti92yVAixLmDc8dd1bJIKwO9qlWCfFQRwRmUEDPQHYn4G6hwPFvFGUTzAa0FsS+inMjLin7GnyBRhA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@hapi/topo": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", + "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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==", + "license": "ISC", + "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/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.4.1.tgz", + "integrity": "sha512-dSlKrqug3siYNHVnjwIldShY12wAH3spwRltO/+8VOjg0X+xEq7vOs3DbBs4LRKsu7OH+NUb9kuZUNBF9Ho3TA==", + "license": "MIT", + "dependencies": { + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", + "@types/jsdom": "^21.1.7", + "@types/node": "*", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/@jest/environment": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz", + "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==", + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "jest-mock": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/@jest/fake-timers": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz", + "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==", + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@sinonjs/fake-timers": "^15.4.0", + "@types/node": "*", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/@jest/schemas": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/@jest/types": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", + "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.4.0", + "@jest/schemas": "30.4.1", + "@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/@jest/environment-jsdom-abstract/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "license": "MIT" + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/@sinonjs/fake-timers": { + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", + "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@jest/environment-jsdom-abstract/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==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/environment-jsdom-abstract/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==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/jest-message-util": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", + "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.4.1", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-util": "30.4.1", + "picomatch": "^4.0.3", + "pretty-format": "30.4.1", + "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/environment-jsdom-abstract/node_modules/jest-mock": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", + "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/jest-util": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", + "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@jest/environment-jsdom-abstract/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", + "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern/node_modules/jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/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==", + "license": "BSD-3-Clause", + "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/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "license": "MIT" + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "license": "MIT" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "license": "MIT", + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nodable/entities": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.0.0.tgz", + "integrity": "sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@octokit/app": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/app/-/app-14.1.0.tgz", + "integrity": "sha512-g3uEsGOQCBl1+W1rgfwoRFUIR6PtvB2T1E4RpygeUU5LrLvlOqcxrt5lfykIeRpUPpupreGJUYl70fqMDXdTpw==", + "license": "MIT", + "dependencies": { + "@octokit/auth-app": "^6.0.0", + "@octokit/auth-unauthenticated": "^5.0.0", + "@octokit/core": "^5.0.0", + "@octokit/oauth-app": "^6.0.0", + "@octokit/plugin-paginate-rest": "^9.0.0", + "@octokit/types": "^12.0.0", + "@octokit/webhooks": "^12.0.4" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-app": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.1.4.tgz", + "integrity": "sha512-QkXkSOHZK4dA5oUqY5Dk3S+5pN2s1igPjEASNQV8/vgJgW034fQWR16u7VsNOK/EljA00eyjYF5mWNxWKWhHRQ==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^7.1.0", + "@octokit/auth-oauth-user": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.1.0", + "deprecation": "^2.3.1", + "lru-cache": "npm:@wolfy1339/lru-cache@^11.0.2-patch.1", + "universal-github-app-jwt": "^1.1.2", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-app/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/auth-app/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/auth-oauth-app": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-7.1.0.tgz", + "integrity": "sha512-w+SyJN/b0l/HEb4EOPRudo7uUOSW51jcK1jwLa+4r7PA8FPFpoxEnHBHMITqCsc/3Vo2qqFjgQfz/xUUvsSQnA==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^6.1.0", + "@octokit/auth-oauth-user": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", + "@types/btoa-lite": "^1.0.0", + "btoa-lite": "^1.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/auth-oauth-device": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-6.1.0.tgz", + "integrity": "sha512-FNQ7cb8kASufd6Ej4gnJ3f1QB5vJitkoV1O0/g6e6lUsQ7+VsSNRHRmFScN2tV4IgKA12frrr/cegUs0t+0/Lw==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-methods": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/auth-oauth-user": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-4.1.0.tgz", + "integrity": "sha512-FrEp8mtFuS/BrJyjpur+4GARteUCrPeR/tZJzD8YourzoVhRics7u7we/aDcKv+yywRNwNi/P4fRi631rG/OyQ==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^6.1.0", + "@octokit/oauth-methods": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", + "btoa-lite": "^1.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-unauthenticated": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-5.0.1.tgz", + "integrity": "sha512-oxeWzmBFxWd+XolxKTc4zr+h3mt+yofn4r7OfoIkR/Cj/o70eEGmPsFbueyJE2iBAGpjgTnEOKM3pnuEGVmiqg==", + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz", + "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.4.1", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/core/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/endpoint": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", + "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/endpoint/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/graphql": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", + "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^8.4.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/graphql/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/oauth-app": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-6.1.0.tgz", + "integrity": "sha512-nIn/8eUJ/BKUVzxUXd5vpzl1rwaVxMyYbQkNZjHrF7Vk/yu98/YDF/N2KeWO7uZ0g3b5EyiFXFkZI8rJ+DH1/g==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^7.0.0", + "@octokit/auth-oauth-user": "^4.0.0", + "@octokit/auth-unauthenticated": "^5.0.0", + "@octokit/core": "^5.0.0", + "@octokit/oauth-authorization-url": "^6.0.2", + "@octokit/oauth-methods": "^4.0.0", + "@types/aws-lambda": "^8.10.83", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-authorization-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz", + "integrity": "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-methods": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.1.0.tgz", + "integrity": "sha512-4tuKnCRecJ6CG6gr0XcEXdZtkTDbfbnD5oaHBmLERTjTMZNi2CbfEHZxPU41xXLDG4DfKf+sonu00zvKI9NSbw==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-authorization-url": "^6.0.2", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.0.0", + "btoa-lite": "^1.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-graphql": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-4.0.1.tgz", + "integrity": "sha512-R8ZQNmrIKKpHWC6V2gum4x9LG2qF1RxRjo27gjQcG3j+vf2tLsEfE7I/wRWEPzYMaenr1M+qDAtNcwZve1ce1A==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=5" + } + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz", + "integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz", + "integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-retry": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.1.0.tgz", + "integrity": "sha512-WrO3bvq4E1Xh1r2mT9w6SDFg01gFmP81nIG77+p/MqW1JeXXgL++6umim3t6x0Zj5pZm3rXAN+0HEjmmdhIRig==", + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^13.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-retry/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-retry/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/plugin-throttling": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-8.2.0.tgz", + "integrity": "sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.2.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^5.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", + "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^9.0.6", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", + "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/request-error/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/request/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/request/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@octokit/webhooks": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-12.3.2.tgz", + "integrity": "sha512-exj1MzVXoP7xnAcAB3jZ97pTvVPkQF9y6GA/dvYC47HV7vLv+24XRS6b/v/XnyikpEuvMhugEXdGtAlU086WkQ==", + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^5.0.0", + "@octokit/webhooks-methods": "^4.1.0", + "@octokit/webhooks-types": "7.6.1", + "aggregate-error": "^3.1.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/webhooks-methods": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-4.1.0.tgz", + "integrity": "sha512-zoQyKw8h9STNPqtm28UGOYFE7O6D4Il8VJwhAtMHFt2C4L0VQT1qGKLeefUOqHNs1mNRYSadVv7x0z8U2yyeWQ==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/webhooks-types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-7.6.1.tgz", + "integrity": "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw==", + "license": "MIT" + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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/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==", + "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==", + "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==", + "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==", + "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==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", + "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==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.1.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz", + "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.6.0", + "@parcel/watcher-darwin-arm64": "2.6.0", + "@parcel/watcher-darwin-x64": "2.6.0", + "@parcel/watcher-freebsd-x64": "2.6.0", + "@parcel/watcher-linux-arm-glibc": "2.6.0", + "@parcel/watcher-linux-arm-musl": "2.6.0", + "@parcel/watcher-linux-arm64-glibc": "2.6.0", + "@parcel/watcher-linux-arm64-musl": "2.6.0", + "@parcel/watcher-linux-x64-glibc": "2.6.0", + "@parcel/watcher-linux-x64-musl": "2.6.0", + "@parcel/watcher-win32-arm64": "2.6.0", + "@parcel/watcher-win32-x64": "2.6.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz", + "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz", + "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz", + "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz", + "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz", + "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz", + "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", + "cpu": [ + "arm" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz", + "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz", + "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz", + "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz", + "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz", + "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz", + "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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==", + "license": "BSD-3-Clause", + "dependencies": { + "legacy-javascript": "latest", + "third-party-web": "latest" + } + }, + "node_modules/@php-wasm/cli-util": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/cli-util/-/cli-util-3.1.48.tgz", + "integrity": "sha512-zNLQtNYVY9tFQEOQClFS6cNK+ntFq2HX0sPHb7HGRYPFV3vom1/Y42F7HQZBxUP+05N2VX0HL0csKlp41AGbeQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/util": "3.1.48", + "fast-xml-parser": "^5.8.0", + "jsonc-parser": "3.3.1" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/logger": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/logger/-/logger-3.1.48.tgz", + "integrity": "sha512-BZdRRIs+Zc3IY3tme0KKsFOOtg/petabYftlXEHSFll/TKUEfOSV7GvMGKmF3tRkZFzadJzY9FClRV6kSIal4A==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/node/-/node-3.1.48.tgz", + "integrity": "sha512-VGe/ZmOmNJ9WjWOY9uApw7yEr+PS6mI9BVMVbUa/akWhZnloOD/J942izvFcaSUyn+UkYLOuFJkgY3ySulUtgw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/cli-util": "3.1.48", + "@php-wasm/logger": "3.1.48", + "@php-wasm/node-5-2": "3.1.48", + "@php-wasm/node-7-4": "3.1.48", + "@php-wasm/node-8-0": "3.1.48", + "@php-wasm/node-8-1": "3.1.48", + "@php-wasm/node-8-2": "3.1.48", + "@php-wasm/node-8-3": "3.1.48", + "@php-wasm/node-8-4": "3.1.48", + "@php-wasm/node-8-5": "3.1.48", + "@php-wasm/universal": "3.1.48", + "@php-wasm/util": "3.1.48", + "fs-ext-extra-prebuilt": "2.2.7", + "wasm-feature-detect": "1.8.0", + "ws": "8.21.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-5-2": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/node-5-2/-/node-5-2-3.1.48.tgz", + "integrity": "sha512-c3n+9Qs6vQv4haKRmihAmm/90D+r4TzSZcqd6maRXZbpmX2Mw3eymIpkQebRnW/LmOxOO7FC54/jmKHQZSmNQQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.48", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-7-4": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/node-7-4/-/node-7-4-3.1.48.tgz", + "integrity": "sha512-QzasgDrgbov6jkuokqB7yFtuP7SYXLwFZEighgb6HqcppAa3RAKHcsSsArDhhOKbu6NicmkjybdNrOKW9DuP7Q==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.48", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-0": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-0/-/node-8-0-3.1.48.tgz", + "integrity": "sha512-sGTAQdNp4URnBb8/b9IwbgdnC9Vh895JCY0lLS72lkWbrRgwvwsWyAY1iu38LnhHM1D+/xMhZj2wNbMvdPzzkg==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.48", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-1": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-1/-/node-8-1-3.1.48.tgz", + "integrity": "sha512-sruxX5piVy0+9PBibFkgF+DEHGcPzOdCD2e7deLbYXMzLvOLGmqzMb4z7ng88WC6ZDk0Pk2jOTTq+08sG9BuMA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.48", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-2": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-2/-/node-8-2-3.1.48.tgz", + "integrity": "sha512-Dyp0SMyNee8FHFGed3KyyYiAGtHo9c2cFkJ/e3IdtX/+Uqo2pvFM8zcFLuWhwOiMJrEJPYl5FoXuER2H5pkHBw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.48", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-3": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-3/-/node-8-3-3.1.48.tgz", + "integrity": "sha512-vRlGi+Kd8jCfgtqGOZHriIp0ufyIMaVuPMGUbq99CKjvunyXouWE88Zn+lS/LQ7KTEhO86Xq4MEgzK1/huq4LA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.48", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-4": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-4/-/node-8-4-3.1.48.tgz", + "integrity": "sha512-TZfwQqdKYukMU5RXbTHgVx6YeiAlb7bN0NUVY5hDvMfioGNMnpBL0sWWf44ULj5L5TK39MbbYr0SQ7i5Rh2cyw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.48", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-5": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-5/-/node-8-5-3.1.48.tgz", + "integrity": "sha512-I3q1MKXjL2EfvpwC1PT2QiyLcPjOdvUVxP4x+8q94x2JMhtgH19lGMhxX9jnBjOGP8FAQ8cOcQ7JtxXQcWg9GQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.48", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/progress": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/progress/-/progress-3.1.48.tgz", + "integrity": "sha512-KFiI3Q9zDQ9NC7HGl5e/jx0BAvJYpyN1shv8vvJbeicXeuOoJnJS9C3XehRWlT9aRCIZ0qpeRSrmR3rR5ue0aQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/logger": "3.1.48" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/scopes": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/scopes/-/scopes-3.1.48.tgz", + "integrity": "sha512-3ncWvaCPs9gLyvskAplZdIMDyImT2MsJ8aRXrLMunvtjq1dwaPcCmvbzv1Z2czb2m23bxgTqalVn7jEDJbhlGw==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/stream-compression": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/stream-compression/-/stream-compression-3.1.48.tgz", + "integrity": "sha512-bQzUn3gNAHcRUgkks5hrZc7jS+jirow+xORkMPsINobi+Y6j2za/OAaGMd9THo9298ubH6dGeeapZ5q58C0hsg==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/util": "3.1.48" + } + }, + "node_modules/@php-wasm/universal": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/universal/-/universal-3.1.48.tgz", + "integrity": "sha512-CtopMuRcJwo8FVsmrzPhpvs/OsoGXYduHOemI78MSP/wfufosZcAg654J+d8IcF9xq6VTJjir3uxkE3t+bct4g==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/logger": "3.1.48", + "@php-wasm/progress": "3.1.48", + "@php-wasm/stream-compression": "3.1.48", + "@php-wasm/util": "3.1.48", + "ini": "4.1.2" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/util": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/util/-/util-3.1.48.tgz", + "integrity": "sha512-MvHDY9K4ErSAOOAp5BypIX6ITTaYQugtbdcGR76ZCtjGCS91QBOe0Mm+75lfEOKnLOKltRdgUmfhV6Jb0oDZeg==", + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/web-service-worker": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/web-service-worker/-/web-service-worker-3.1.48.tgz", + "integrity": "sha512-fMQLXvJLZxXM4cnPYbsihYVSxrkPhy3bI3gFwO5wJIv4J7WdsRFT5ddQSgpdJQ+u/ndwHZl4rTs0OTuEKYpy7w==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/scopes": "3.1.48", + "@php-wasm/universal": "3.1.48" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/xdebug-bridge": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@php-wasm/xdebug-bridge/-/xdebug-bridge-3.1.48.tgz", + "integrity": "sha512-P2PUgHHAAKs4Bd5EI6arWdojWt+Adw1QBYyotVgRq2z2+ICbUdvSAWiS5NwwFmLJq227vPsNHIgZftuVzhUmBQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/logger": "3.1.48", + "@php-wasm/universal": "3.1.48", + "ws": "8.21.0", + "xml2js": "0.6.2", + "yargs": "17.7.2" + }, + "bin": { + "xdebug-bridge": "xdebug-bridge.js" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@pkgr/core": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", + "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@playwright/test": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz", + "integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "playwright": "1.62.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.17.tgz", + "integrity": "sha512-tXDyE1/jzFsHXjhRZQ3hMl0IVhYe5qula43LDWIhVfjp9G/nT5OQY5AORVOrkEGAUltBJOfOWeETbmhm6kHhuQ==", + "license": "MIT", + "dependencies": { + "ansi-html": "^0.0.9", + "core-js-pure": "^3.23.3", + "error-stack-parser": "^2.0.6", + "html-entities": "^2.1.0", + "loader-utils": "^2.0.4", + "schema-utils": "^4.2.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "@types/webpack": "4.x || 5.x", + "react-refresh": ">=0.10.0 <1.0.0", + "sockjs-client": "^1.4.0", + "type-fest": ">=0.17.0 <5.0.0", + "webpack": ">=4.43.0 <6.0.0", + "webpack-dev-server": "3.x || 4.x || 5.x", + "webpack-hot-middleware": "2.x", + "webpack-plugin-serve": "0.x || 1.x" + }, + "peerDependenciesMeta": { + "@types/webpack": { + "optional": true + }, + "sockjs-client": { + "optional": true + }, + "type-fest": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + }, + "webpack-hot-middleware": { + "optional": true + }, + "webpack-plugin-serve": { + "optional": true + } + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "license": "MIT" + }, + "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==", + "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.6.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.6.1.tgz", + "integrity": "sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.0", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@puppeteer/browsers/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==", + "license": "MIT" + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "license": "MIT" + }, + "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==", + "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==", + "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==", + "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/node/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==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@sentry/node/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@sentry/opentelemetry": { + "version": "9.47.1", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-9.47.1.tgz", + "integrity": "sha512-STtFpjF7lwzeoedDJV+5XA6P89BfmFwFftmHSGSe3UTI8z8IoiR5yB6X2vCjSPvXlfeOs13qCNNCEZyznxM8Xw==", + "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.27.12", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", + "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@stylistic/stylelint-plugin": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-3.1.3.tgz", + "integrity": "sha512-85fsmzgsIVmyG3/GFrjuYj6Cz8rAM7IZiPiXCMiSMfoDOC1lOrzrXPDk24WqviAghnPqGpx8b0caK2PuewWGFg==", + "license": "MIT", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", + "@csstools/media-query-list-parser": "^3.0.1", + "is-plain-object": "^5.0.0", + "postcss": "^8.4.41", + "postcss-selector-parser": "^6.1.2", + "postcss-value-parser": "^4.2.0", + "style-search": "^0.1.0" + }, + "engines": { + "node": "^18.12 || >=20.9" + }, + "peerDependencies": { + "stylelint": "^16.8.0" + } + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", + "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", + "license": "MIT", + "dependencies": { + "cosmiconfig": "^8.1.3", + "deepmerge": "^4.3.1", + "svgo": "^3.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/webpack": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", + "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@babel/plugin-transform-react-constant-elements": "^7.21.3", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.0", + "@svgr/core": "8.1.0", + "@svgr/plugin-jsx": "8.1.0", + "@svgr/plugin-svgo": "8.1.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/aws-lambda": { + "version": "8.10.162", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.162.tgz", + "integrity": "sha512-Fn658grtLOci1oxi1391vvDWJRKNGWRSqfxRkmN/Iy3c0tQH1USMKEXcPYHLvope+ZgTFocx9FRQJx1muBL6qw==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/btoa-lite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/btoa-lite/-/btoa-lite-1.0.2.tgz", + "integrity": "sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg==", + "license": "MIT" + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz", + "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jsdom": { + "version": "21.1.7", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", + "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", + "license": "MIT" + }, + "node_modules/@types/mousetrap": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/@types/mousetrap/-/mousetrap-1.6.15.tgz", + "integrity": "sha512-qL0hyIMNPow317QWW/63RvL1x5MVMV+Ru3NaY9f/CuEpCqrmb7WeuK2071ZY5hczOnm38qExWM2i2WtkXLSqFw==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "license": "MIT" + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/pg": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", + "integrity": "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==", + "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==", + "license": "MIT", + "dependencies": { + "@types/pg": "*" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "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==", + "license": "MIT", + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.66.0.tgz", + "integrity": "sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A==", + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/type-utils": "8.66.0", + "@typescript-eslint/utils": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.66.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.66.0.tgz", + "integrity": "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/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==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.66.0.tgz", + "integrity": "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.66.0", + "@typescript-eslint/types": "^8.66.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/project-service/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==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.66.0.tgz", + "integrity": "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.66.0.tgz", + "integrity": "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.66.0.tgz", + "integrity": "sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0", + "@typescript-eslint/utils": "8.66.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/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==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/types": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.66.0.tgz", + "integrity": "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.66.0.tgz", + "integrity": "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.66.0", + "@typescript-eslint/tsconfig-utils": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/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==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.66.0.tgz", + "integrity": "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA==", + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz", + "integrity": "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.66.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@wordpress/babel-preset-default": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-8.52.0.tgz", + "integrity": "sha512-0beMLrvoMAHH3Q4916FEftMCkn0/lx+wg8JoxX80BjOTrCzZMQaArTgvmH2F0G9jvuhzqJebsNoqdrNvd6nTVQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/core": "^7.25.7", + "@babel/plugin-syntax-import-attributes": "^7.26.0", + "@babel/plugin-transform-react-jsx": "^7.25.7", + "@babel/plugin-transform-runtime": "^7.25.7", + "@babel/preset-env": "^7.25.7", + "@babel/preset-typescript": "^7.25.7", + "@wordpress/browserslist-config": "^6.52.0", + "@wordpress/warning": "^3.52.0", + "browserslist": "^4.28.4", + "core-js": "^3.31.0", + "react": "^18.3.1" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/base-styles": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-12.0.0.tgz", + "integrity": "sha512-BqSA9qZrBM9FBXFmQYvYCigA8ytkmlNFZ8+H0FscEnLWXkyCRwvHm4dE6LL53lmclKUGRMKlTZOUe1l8hfBQ5g==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/browserslist-config": { + "version": "6.52.0", + "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-6.52.0.tgz", + "integrity": "sha512-+4oOFS7wOuCc8ZRRT8rjoQbJCXX0li8tWN++1szH0qsRzSNVvZSu4gR7bIAK1GrQKhIjAUZItrT02iocBq7lIA==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/compose": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-8.5.0.tgz", + "integrity": "sha512-giYS22Tbhtr3Lj4lK6lxzGqV6EkM2QeVBiP7+c9r2F9rnQN3F6A8cN4gQM5sVGdgIOlps9tw+dsn9OHzIFVwIg==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@types/mousetrap": "^1.6.8", + "@wordpress/deprecated": "^4.52.0", + "@wordpress/dom": "^4.52.0", + "@wordpress/element": "^8.4.0", + "@wordpress/is-shallow-equal": "^5.52.0", + "@wordpress/keycodes": "^4.52.0", + "@wordpress/priority-queue": "^3.52.0", + "@wordpress/private-apis": "^1.52.0", + "@wordpress/undo-manager": "^1.52.0", + "change-case": "^4.1.2", + "mousetrap": "^1.6.5", + "use-memo-one": "^1.1.1" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "@types/react": "^18 || ^19", + "react": "^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@wordpress/dependency-extraction-webpack-plugin": { + "version": "6.52.0", + "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-6.52.0.tgz", + "integrity": "sha512-3cl4vODbsN2D+7E/y7QiOlm11lgWSsUBH8lnznFNuTPQc7LlqumyaY8Bxuek1BIBerbmam0R/Ww8krl1zSpvPQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "json2php": "^0.0.9" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/@wordpress/deprecated": { + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-4.52.0.tgz", + "integrity": "sha512-94oHBKPty4pp6L5510LWDwJwNqFhikxLfwN6VUcDOQzj3itkc0MQ4ZQhmsfBy3Y6IRxSGx+p2bjSQvA4D+M96A==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/hooks": "^4.52.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/dom": { + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-4.52.0.tgz", + "integrity": "sha512-LUY9nI9h6blk4xBuG83KgyfTnx7PMs/g6ZVzY/SOaCcOc2P3zOfzacADq/vxQydbZpcwtLM6juzgnNta6AX95w==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/deprecated": "^4.52.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/e2e-test-utils-playwright": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-1.52.0.tgz", + "integrity": "sha512-LK0/WJd8iGfTo9ONaHrLSBh0IjKHeXU7urZPaNuXXB7jdt+coLTF+y0gP4c6sQ5m9OgfQqf01FPT/t2XzEbJ+w==", + "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/e2e-test-utils-playwright/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@wordpress/element": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-8.4.0.tgz", + "integrity": "sha512-/LzW+0MpSmKfYiESoXUQtjBPqOPyvqUm17GigQR5c0Z8Wj2NiklP72x4XaF02uSkau1ru1Z9lP8NWEGjwBwLsA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@types/react": "^18.3.27", + "@types/react-dom": "^18.3.1", + "@wordpress/deprecated": "^4.52.0", + "@wordpress/escape-html": "^3.52.0", + "change-case": "^4.1.2", + "is-plain-object": "^5.0.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/escape-html": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-3.52.0.tgz", + "integrity": "sha512-sW8X839Xu8aiJlCGF48MM3iYrcXspuiY0By8iTpXKpnUdd2u0SL9HRR0ALSAsOf2ujOeWm/x58ODMSchovRWGw==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/eslint-plugin": { + "version": "25.8.0", + "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-25.8.0.tgz", + "integrity": "sha512-QqYfiAVUYFLUhiLlVwB1MoGHcyNElwAPFeXnfZhYUPvFYOmQucsn4dxEGpl67PfcM2XWimni5z+mUquv4y1Mow==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/eslint-parser": "^7.28.6", + "@eslint-community/eslint-plugin-eslint-comments": "^4.7.0", + "@eslint/compat": "^2.0.0", + "@wordpress/babel-preset-default": "^8.52.0", + "@wordpress/prettier-config": "^4.52.0", + "@wordpress/theme": "^1.1.0", + "cosmiconfig": "^7.0.0", + "eslint-config-prettier": "^10.0.0", + "eslint-import-resolver-typescript": "^4.4.4", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jest": "^29.16.0", + "eslint-plugin-jsdoc": "^50.0.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-playwright": "^2.1.0", + "eslint-plugin-prettier": "^5.5.5", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^7.1.1", + "globals": "^16.0.0", + "requireindex": "^1.2.0", + "typescript-eslint": "^8.57.1" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "@babel/core": ">=7", + "eslint": "^9.0.0 || ^10.0.0", + "prettier": ">=3", + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "prettier": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@wordpress/eslint-plugin/node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@wordpress/hooks": { + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-4.52.0.tgz", + "integrity": "sha512-EbV/nJTerhqwNW3DLvvGutJfNyXcmBHXuWyJpv1NypzT80k21jPGP79HBE5Z0A2oAI2kIBp6Klaa4O8uEjq/sw==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/i18n": { + "version": "6.24.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.24.0.tgz", + "integrity": "sha512-K4XmCyyyDOKH/5ea75P2L36ZiAvQTy4Hsa73Na3n6NzVjAwrrKZm4JJGf0t+OlThUG4D4GyfEv5szs5EeCA0lA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@tannin/sprintf": "^1.3.2", + "@wordpress/hooks": "^4.51.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/is-shallow-equal": { + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-5.52.0.tgz", + "integrity": "sha512-LMIBLLTZ+vvq0DlYebL4d9XaCu16PNnhPGSn8QqsMPmBzwBYOjO0/btMLpYbs9rRc8k1QKBDWvw8eddvRVsYxA==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/jest-console": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-8.51.0.tgz", + "integrity": "sha512-NhX7hJy0XFYnsjca7RuV7jHsHotRAwKFi8md5By+EJaBdO3itAcBv5/QLLyGBgNXGSvEVGVQfzym/S8CeC8b0Q==", + "license": "GPL-2.0-or-later", + "dependencies": { + "jest-matcher-utils": "^29.6.2", + "jest-mock": "^29.6.2" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "jest": ">=29" + } + }, + "node_modules/@wordpress/jest-preset-default": { + "version": "12.51.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-12.51.0.tgz", + "integrity": "sha512-fJFOCdQHnutv+esdusNufxkrKsCW63UFrgFXWLwGr66YR9q0Z+d1Mw6BVFf7kvzGjT166PBJumf2XTRqUedPNQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/jest-console": "^8.51.0", + "babel-jest": "^29.7.0", + "change-case": "^4.1.2" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "@babel/core": ">=7", + "jest": ">=29" + } + }, + "node_modules/@wordpress/keycodes": { + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-4.52.0.tgz", + "integrity": "sha512-KRvwViTTdxUDgikYaCm+qnXSH2UlFuCDjAIvtjjcen8oRviSkeMv6bPn1T2vqFRKcVgt88xTtKlvI8l5U99o2Q==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/i18n": "^6.25.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "@types/react": "^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@wordpress/keycodes/node_modules/@wordpress/i18n": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.25.0.tgz", + "integrity": "sha512-UcyUT8CJgEkhjzEGTVV6kw0Qi4OraF0I9J9FBLmTda/1Wi1o6rLAXZBm2aJrP740ezFxPneHH92aQF4Z5xZqcQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@tannin/sprintf": "^1.3.2", + "@wordpress/hooks": "^4.52.0", + "gettext-parser": "^1.3.1", + "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/npm-package-json-lint-config": { + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-5.52.0.tgz", + "integrity": "sha512-+3fv1jmdmsvSIhlZ2PpYKVQ6VQ1zKJFMTbHoJKSSiUA0Os5ASgAAH6S/+s4B0EGHZ94G3SV3k8oaiOnwOghE5Q==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "npm-package-json-lint": ">=6.0.0" + } + }, + "node_modules/@wordpress/postcss-plugins-preset": { + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-5.52.0.tgz", + "integrity": "sha512-/Wvo88zPqzHlLJR51JAuLm1jrMEt+tAnvmTs2xhysigFuH9gv4UN8sNf8kGmMwPMneSl2MAlLAd5ilkBTcRVag==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/base-styles": "^12.0.0", + "@wordpress/browserslist-config": "^6.52.0", + "autoprefixer": "^10.4.21", + "postcss-import": "^16.1.1" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/@wordpress/prettier-config": { + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-4.52.0.tgz", + "integrity": "sha512-aUxF/a7BtcfIVaJn12eZOwMUPJFMCu37wEyR+KhzR5gXssXi5yGqxJKNIHT/dmTyYC+MNxVM73g+W1HyAoIwxw==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "prettier": ">=3" + } + }, + "node_modules/@wordpress/priority-queue": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-3.52.0.tgz", + "integrity": "sha512-aYnVXxV5j4D73tRld2n1D3G6cKLfSwKWcTnktJ7XlmiswW7xlaAqvn4FyBjwmIe20pZw0A7xLH5xCuQcqOM7nw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "requestidlecallback": "^0.3.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/private-apis": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-1.52.0.tgz", + "integrity": "sha512-gFcmBXSti73Y4uEx++5qUNDaH/o0/2ijrHEJkY5e/df4RtmxVSQOIVxftRiI4m9thCWfJMoehMMreJxhwx6Qtg==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/scripts": { + "version": "32.6.0", + "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-32.6.0.tgz", + "integrity": "sha512-nO5s2RYmOWiVjcG3/zp5NEejLu+J/dzujRIl633hd9yj8gBtQRKfZr/iAwyFQwW0Asn15bbwc1I0b4+lvnumOw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/core": "^7.25.7", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", + "@svgr/webpack": "^8.0.1", + "@wordpress/babel-preset-default": "^8.50.0", + "@wordpress/browserslist-config": "^6.50.0", + "@wordpress/dependency-extraction-webpack-plugin": "^6.50.0", + "@wordpress/e2e-test-utils-playwright": "^1.50.0", + "@wordpress/eslint-plugin": "^25.6.0", + "@wordpress/jest-preset-default": "^12.50.0", + "@wordpress/npm-package-json-lint-config": "^5.50.0", + "@wordpress/postcss-plugins-preset": "^5.50.0", + "@wordpress/prettier-config": "^4.50.0", + "@wordpress/stylelint-config": "^23.42.0", + "adm-zip": "^0.5.9", + "babel-jest": "^29.7.0", + "babel-loader": "^9.2.1", + "browserslist": "^4.21.10", + "chalk": "^4.1.1", + "check-node-version": "^4.1.0", + "copy-webpack-plugin": "^10.2.0", + "cross-spawn": "^7.0.6", + "css-loader": "^6.2.0", + "cssnano": "^6.0.1", + "cwd": "^0.10.0", + "dir-glob": "^3.0.1", + "eslint": "^10.0.0", + "expect-puppeteer": "^4.4.0", + "fast-glob": "^3.2.7", + "filenamify": "^4.2.0", + "jest": "^29.6.2", + "jest-dev-server": "^10.1.4", + "jest-environment-jsdom": "^30.2.0", + "jest-environment-node": "^29.6.2", + "json2php": "^0.0.9", + "markdownlint-cli": "^0.31.1", + "merge-deep": "^3.0.3", + "mini-css-extract-plugin": "^2.9.2", + "minimist": "^1.2.0", + "npm-package-json-lint": "^6.4.0", + "npm-packlist": "^3.0.0", + "postcss": "^8.4.38", + "postcss-loader": "^6.2.1", + "prettier": "npm:wp-prettier@^3.0.3", + "puppeteer-core": "^23.10.1", + "react-refresh": "^0.14.0", + "read-pkg-up": "^7.0.1", + "resolve-bin": "^0.4.0", + "rtlcss": "^4.3.0", + "sass": "^1.54.0", + "sass-loader": "^16.0.3", + "schema-utils": "^4.2.0", + "source-map-loader": "^3.0.0", + "stylelint": "^16.8.2", + "terser-webpack-plugin": "^5.3.10", + "url-loader": "^4.1.1", + "webpack": "^5.97.0", + "webpack-bundle-analyzer": "^4.9.1", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^4.15.1" + }, + "bin": { + "wp-scripts": "bin/wp-scripts.js" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "@playwright/test": "^1.58.2", + "@wordpress/env": ">=10.0.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "@wordpress/env": { + "optional": true + } + } + }, + "node_modules/@wordpress/scripts/node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@wordpress/scripts/node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@wordpress/scripts/node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@wordpress/scripts/node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@wordpress/scripts/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@wordpress/scripts/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@wordpress/scripts/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@wordpress/scripts/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@wordpress/scripts/node_modules/eslint": { + "version": "10.8.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.1.tgz", + "integrity": "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==", + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/@wordpress/scripts/node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@wordpress/scripts/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@wordpress/scripts/node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@wordpress/scripts/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wordpress/scripts/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/@wordpress/scripts/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wordpress/scripts/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@wordpress/scripts/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==", + "license": "MIT" + }, + "node_modules/@wordpress/scripts/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wordpress/style-runtime": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@wordpress/style-runtime/-/style-runtime-0.8.0.tgz", + "integrity": "sha512-gqe5cnsDLwH2NQ//VyUtzS4pcht0iR64hP+3u6p5/pPvwA2ySGn71mJ/7+O2X7sUG5drxNp6+4Kl3LWOzkKYng==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@wordpress/stylelint-config": { + "version": "23.42.0", + "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-23.42.0.tgz", + "integrity": "sha512-k9lNU4sK3fdipdhHI2d/Rzd/NnfznPmOZ60ok8O8QhfCMZw2Jd1M8ckYqaIrHTKU62ZrcsugdfJlHodsdrxEzA==", + "license": "MIT", + "dependencies": { + "@stylistic/stylelint-plugin": "^3.0.1", + "@wordpress/theme": "^0.17.0", + "stylelint-config-recommended": "^14.0.1", + "stylelint-config-recommended-scss": "^14.1.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "stylelint": "^16.8.2", + "stylelint-scss": "^6.4.0" + } + }, + "node_modules/@wordpress/stylelint-config/node_modules/@wordpress/style-runtime": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@wordpress/style-runtime/-/style-runtime-0.5.0.tgz", + "integrity": "sha512-hrXvdDUJpOzT1KIomgtysysgbc5bkkwAuJyEUAXiOCgVBXBeMkZlgfN19W0PuNY51j53K7VQ42txfayxgVmfgA==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@wordpress/stylelint-config/node_modules/@wordpress/theme": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@wordpress/theme/-/theme-0.17.0.tgz", + "integrity": "sha512-BU+PLBLxMBM4WJYrd1QEwy+IA64vUP+8IEEB9lLx2NfB+3/45vvuvXx9o3qPKRDHvtMtEaRArld4Kn05UIW9IA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/compose": "^8.2.0", + "@wordpress/deprecated": "^4.49.0", + "@wordpress/element": "^8.1.0", + "@wordpress/private-apis": "^1.49.0", + "@wordpress/style-runtime": "^0.5.0", + "colorjs.io": "^0.6.0", + "memize": "^2.1.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "@types/react": "^18.3.27", + "esbuild": "^0.27.2", + "postcss": "^8.0.0", + "react": "^18.0.0", + "react-dom": "^18.0.0", + "stylelint": "^16.8.2", + "vite": "^7.3.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "postcss": { + "optional": true + }, + "stylelint": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@wordpress/stylelint-config/node_modules/colorjs.io": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.6.1.tgz", + "integrity": "sha512-8lyR2wHzuIykCpqHKgluGsqQi5iDm3/a2IgP2GBZrasn2sBRkE4NOGsglZxWLs/jZQoNkmA/KM/8NV16rLUdBg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/color" + } + }, + "node_modules/@wordpress/theme": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@wordpress/theme/-/theme-1.1.0.tgz", + "integrity": "sha512-SWEGYY/HnSzmKDJnDhWVfxUDyLlJkz9EtpfAWhgPcCLSaZVc/pp99Fxr+/ueB2mHlQ9gpaWCPAMBxq8knDCbXw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/compose": "^8.5.0", + "@wordpress/deprecated": "^4.52.0", + "@wordpress/element": "^8.4.0", + "@wordpress/private-apis": "^1.52.0", + "@wordpress/style-runtime": "^0.8.0", + "colorjs.io": "^0.7.1", + "memize": "^2.1.1" + }, + "engines": { + "node": "^20.19.0 || >=22.13.0", + "npm": ">=10.2.3" + }, + "peerDependencies": { + "@types/react": "^18 || ^19", + "esbuild": ">=0.27.2 <1.0.0", + "postcss": "^8.0.0", + "react": "^18 || ^19", + "react-dom": "^18 || ^19", + "stylelint": "^16 || ^17", + "vite": "^7 || ^8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "postcss": { + "optional": true + }, + "stylelint": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@wordpress/undo-manager": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-1.52.0.tgz", + "integrity": "sha512-6hI0WWDOtLLVEkWqQokCoQRz5Pba9UNtgt5MV4hHGe5x0mzsowj+GWHedppf8UCVZ2ex3cde7fThaRKMCackrQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/is-shallow-equal": "^5.52.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/warning": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-3.52.0.tgz", + "integrity": "sha512-BjJ+Jte1g2nMITI1IHq0NwMpm/qlFOV1L+gNe0vFQmGKEcXiQ0DjrHQNtA8hV+Mgt9zSWRNS71Fgonht3r72Ew==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wp-playground/blueprints": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@wp-playground/blueprints/-/blueprints-3.1.48.tgz", + "integrity": "sha512-geJ1EdIWpRFxgnJrJqsYq+YUEJAFenKI6OCRRAfvgRAN+pei7HpCpSEnMcYB2atmpj6DtpqpKGlg3Yf1/EoRBA==", + "dependencies": { + "@php-wasm/logger": "3.1.48", + "@php-wasm/progress": "3.1.48", + "@php-wasm/stream-compression": "3.1.48", + "@php-wasm/universal": "3.1.48", + "@php-wasm/util": "3.1.48", + "@php-wasm/web-service-worker": "3.1.48", + "@wp-playground/common": "3.1.48", + "@wp-playground/storage": "3.1.48", + "@wp-playground/wordpress": "3.1.48", + "ajv": "8.18.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@wp-playground/cli": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@wp-playground/cli/-/cli-3.1.48.tgz", + "integrity": "sha512-24Y640IHzv7t/iEep1cUU5kZwUFTDXqljTRqOtddzQNByZzxL4g0zXSQYVUq6IPCh9rVx3/+elId/Ym6MCzRZA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/cli-util": "3.1.48", + "@php-wasm/logger": "3.1.48", + "@php-wasm/node": "3.1.48", + "@php-wasm/progress": "3.1.48", + "@php-wasm/universal": "3.1.48", + "@php-wasm/util": "3.1.48", + "@php-wasm/xdebug-bridge": "3.1.48", + "@wp-playground/blueprints": "3.1.48", + "@wp-playground/common": "3.1.48", + "@wp-playground/storage": "3.1.48", + "@wp-playground/tools": "3.1.48", + "@wp-playground/wordpress": "3.1.48", + "express": "4.22.2", + "fs-extra": "11.1.1", + "tmp-promise": "3.0.3", + "wasm-feature-detect": "1.8.0", + "yargs": "17.7.2" + }, + "bin": { + "wp-playground-cli": "wp-playground.js" + } + }, + "node_modules/@wp-playground/common": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@wp-playground/common/-/common-3.1.48.tgz", + "integrity": "sha512-+k7Ku33GWROchZmE0mP23aPTdCXxR7uZqsSgfpf+ttjuKB8uOI9TzfcqrRfSoLcqN2Rh7FAvJmtgbcgdOLfl+A==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.48", + "@php-wasm/util": "3.1.48" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@wp-playground/storage": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@wp-playground/storage/-/storage-3.1.48.tgz", + "integrity": "sha512-5WMK4PpL5ETkoMsKPgLCwQ5b8Uz5PgFu0uvG+2tgSu7v8x2/GjuLsLloUl2vrPEu3ZrVDXrMOYnBiX8oEHcAPg==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/stream-compression": "3.1.48", + "@php-wasm/universal": "3.1.48", + "@php-wasm/util": "3.1.48", + "@zip.js/zip.js": "2.7.57", + "isomorphic-git": "1.37.6", + "octokit": "3.1.2", + "pako": "^1.0.10", + "sha.js": "2.4.12" + } + }, + "node_modules/@wp-playground/tools": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@wp-playground/tools/-/tools-3.1.48.tgz", + "integrity": "sha512-HnsBfBkMt7YrhO1d867uQg1OivEoPOKlAuEYwXkabv644O95hO5MrQQWv888qQIPHeKJKzXYjmGEL8KwCy9bGw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/util": "3.1.48", + "@wp-playground/blueprints": "3.1.48" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@wp-playground/wordpress": { + "version": "3.1.48", + "resolved": "https://registry.npmjs.org/@wp-playground/wordpress/-/wordpress-3.1.48.tgz", + "integrity": "sha512-L0TSLNAoCXnHo9Ar4/0oTzM2OmJfhmQbko+ypf7EJhXILN73IsOhbAXp2MWfSUt24+rGGb6Z9BzlXmGPD6Brsw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/logger": "3.1.48", + "@php-wasm/universal": "3.1.48", + "@php-wasm/util": "3.1.48", + "@wp-playground/common": "3.1.48", + "zstddec": "^0.2.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" + }, + "node_modules/@zip.js/zip.js": { + "version": "2.7.57", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.57.tgz", + "integrity": "sha512-BtonQ1/jDnGiMed6OkV6rZYW78gLmLswkHOzyMrMb+CAR7CZO8phOHO6c2qw6qb1g1betN7kwEHhhZk30dv+NA==", + "license": "BSD-3-Clause", + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=16.5.0" + } + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "license": "BSD-3-Clause" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "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==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/adm-zip": { + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.18.tgz", + "integrity": "sha512-ufJnssQGbxzLNS1Ho9bCtX4rQKCCvoVuDLHoJyc3F9dOGDB4BkWs2Ci0kv53lqocAEQ/Cbi+I2XCsNYGqVYqng==", + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "license": "MIT", + "peerDependencies": { + "ajv": ">=5.0.0" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "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==", + "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==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.9.tgz", + "integrity": "sha512-ozbS3LuenHVxNRh/wdnN16QapUHzauqSomAl1jwwJRRsGwFwtj644lIhxfWu0Fy0acCij2+AEgHvjscq3dlVXg==", + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "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==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anynum": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", + "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", + "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "license": "MIT" + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/async-lock": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", + "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atomically": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz", + "integrity": "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==", + "license": "MIT", + "dependencies": { + "stubborn-fs": "^2.0.0", + "when-exit": "^2.1.4" + } + }, + "node_modules/autoprefixer": { + "version": "10.5.4", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.4.tgz", + "integrity": "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.6", + "caniuse-lite": "^1.0.30001806", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axios": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", + "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axios/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/axios/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/axios/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/axios/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==", + "license": "MIT" + }, + "node_modules/axios/node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-loader": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", + "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", + "license": "MIT", + "dependencies": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8", + "core-js-compat": "^3.48.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.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==", + "license": "MIT", + "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": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/bare-events": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", + "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.8.0.tgz", + "integrity": "sha512-fM+MhCvdQhZ7NV6S95a07gPSqjIYKn6mFaXfx266wN3ajZGl/+1AzH+ubkXQ0fFZvOe2nk9VHkzdYkQE5zMV3Q==", + "license": "Apache-2.0", + "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.28.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz", + "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==", + "license": "Apache-2.0" + }, + "node_modules/bare-stream": { + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz", + "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.8.1", + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-stream/node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.1.tgz", + "integrity": "sha512-cD5ciQuKlx+eumTCfqbfiL+fhQm+dHbVNB/cX4+d+I/nx4JsVop9VoFEPAs5RJ6I84QR6bZIBjpd2kjDOYeWcg==", + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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/baseline-browser-mapping": { + "version": "2.11.13", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.13.tgz", + "integrity": "sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "license": "MIT" + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "license": "Apache-2.0" + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bonjour-service": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.4.4.tgz", + "integrity": "sha512-jCZcVv7eoc4QesRscwEZtSROBen+6LpKAmBIsQYQrsAeVHLyMXWX/t6eIV5KiRZYNUBl8eVqImEEMQ8L5+c/Kw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "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", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.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==", + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/btoa-lite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", + "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==", + "license": "MIT" + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } + }, + "node_modules/cacheable/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "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==", + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "license": "MIT", + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", + "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" + }, + "node_modules/capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "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==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/change-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", + "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "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==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/check-node-version": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/check-node-version/-/check-node-version-4.2.1.tgz", + "integrity": "sha512-YYmFYHV/X7kSJhuN/QYHUu998n/TRuDe8UenM3+m5NrkiH670lb9ILqHIvBencvJc4SDh+XcbXMR4b+TtubJiw==", + "license": "Unlicense", + "dependencies": { + "chalk": "^3.0.0", + "map-values": "^1.0.1", + "minimist": "^1.2.0", + "object-filter": "^1.0.2", + "run-parallel": "^1.1.4", + "semver": "^6.3.0" + }, + "bin": { + "check-node-version": "bin.js" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/check-node-version/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-node-version/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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==", + "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/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/chromium-bidi": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.11.0.tgz", + "integrity": "sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA==", + "license": "Apache-2.0", + "dependencies": { + "mitt": "3.0.1", + "zod": "3.23.8" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/chromium-bidi/node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/clean-git-ref": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/clean-git-ref/-/clean-git-ref-2.0.1.tgz", + "integrity": "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw==", + "license": "Apache-2.0" + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-deep": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", + "integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==", + "license": "MIT", + "dependencies": { + "for-own": "^0.1.3", + "is-plain-object": "^2.0.1", + "kind-of": "^3.0.2", + "lazy-cache": "^1.0.3", + "shallow-clone": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clone-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "license": "MIT", + "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==", + "license": "MIT" + }, + "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==", + "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==", + "license": "MIT" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/colorjs.io": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.7.1.tgz", + "integrity": "sha512-LY7OHnJZxHwT5UlzNa9bbhHHDbzB6yE5+3MIPwJEQKRvSCt/T4G7epsj+9j2BExUIIfXFIJGsIXUKdfrK9Q5tA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/color" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "license": "ISC" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/configstore": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-7.1.0.tgz", + "integrity": "sha512-N4oog6YJWbR9kGyXvS7jEykLDXIE2C0ILYqNBZBp9iwiJpoCBWYsuAdW6PPFn6w06jjnC+3JstVvWHO4cZqvRg==", + "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/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/copy-webpack-plugin": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz", + "integrity": "sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg==", + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.7", + "glob-parent": "^6.0.1", + "globby": "^12.0.2", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 12.20.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/core-js": { + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.50.0.tgz", + "integrity": "sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw==", + "hasInstallScript": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.50.0.tgz", + "integrity": "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.7" + }, + "engines": { + "node": ">=6.4.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.50.0.tgz", + "integrity": "sha512-6GP3Pxz4IKyWjAfa747vIu/jilB5z29JWROLqH/b+pXVcpgh6tM06ZIBwSuglgVqzDYURhOK6oEzTrG0bCHitA==", + "hasInstallScript": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "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==", + "license": "Apache-2.0" + }, + "node_modules/css-declaration-sorter": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.4.0.tgz", + "integrity": "sha512-LTuzjPoyA2vMGKKcaOqKSp7Ub2eGrNfKiZH4LpezxpNrsICGCSFvsQOI29psISxNZtaXibkC2CXzrQ5enMeGGw==", + "license": "ISC", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-functions-list": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.3.3.tgz", + "integrity": "sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", + "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "license": "MIT", + "dependencies": { + "cssnano-preset-default": "^6.1.2", + "lilconfig": "^3.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-preset-default": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", + "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^4.0.2", + "postcss-calc": "^9.0.1", + "postcss-colormin": "^6.1.0", + "postcss-convert-values": "^6.1.0", + "postcss-discard-comments": "^6.0.2", + "postcss-discard-duplicates": "^6.0.3", + "postcss-discard-empty": "^6.0.3", + "postcss-discard-overridden": "^6.0.2", + "postcss-merge-longhand": "^6.0.5", + "postcss-merge-rules": "^6.1.1", + "postcss-minify-font-values": "^6.1.0", + "postcss-minify-gradients": "^6.0.3", + "postcss-minify-params": "^6.1.0", + "postcss-minify-selectors": "^6.0.4", + "postcss-normalize-charset": "^6.0.2", + "postcss-normalize-display-values": "^6.0.2", + "postcss-normalize-positions": "^6.0.2", + "postcss-normalize-repeat-style": "^6.0.2", + "postcss-normalize-string": "^6.0.2", + "postcss-normalize-timing-functions": "^6.0.2", + "postcss-normalize-unicode": "^6.1.0", + "postcss-normalize-url": "^6.0.2", + "postcss-normalize-whitespace": "^6.0.2", + "postcss-ordered-values": "^6.0.2", + "postcss-reduce-initial": "^6.1.0", + "postcss-reduce-transforms": "^6.0.2", + "postcss-svgo": "^6.0.3", + "postcss-unique-selectors": "^6.0.4" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-utils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", + "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/cwd": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/cwd/-/cwd-0.10.0.tgz", + "integrity": "sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA==", + "license": "MIT", + "dependencies": { + "find-pkg": "^0.1.2", + "fs-exists-sync": "^0.1.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "license": "BSD-2-Clause" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "license": "MIT", + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "license": "BSD-2-Clause", + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "license": "ISC" + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "license": "MIT" + }, + "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==", + "license": "BSD-3-Clause" + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/diff3": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz", + "integrity": "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g==", + "license": "MIT" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "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==", + "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==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "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/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.403", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.403.tgz", + "integrity": "sha512-MQsYmdaLzvaCX5j+ZZBr5Fm6uCCnPQcRtlvmvRlWqrXy+BH2O4ffXIAScF+JQznQWB9brWp4lSD9Z4yNmaf2BA==", + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/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==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.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==", + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/envinfo": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "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==", + "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/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "license": "MIT", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "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==", + "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==", + "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/escodegen/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==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-context": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", + "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", + "license": "MIT", + "dependencies": { + "get-tsconfig": "^4.10.1", + "stable-hash-x": "^0.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-context" + }, + "peerDependencies": { + "unrs-resolver": "^1.0.0" + }, + "peerDependenciesMeta": { + "unrs-resolver": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-node/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==", + "license": "MIT" + }, + "node_modules/eslint-import-resolver-node/node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.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/eslint-import-resolver-typescript": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.5.tgz", + "integrity": "sha512-nbE5XLph6TLtGYcu/U6e6ZVXyKBhbDWK5cLGk76eJ7NdZpwf1P9EFkpt1Z01mNZNrrilsAYWKH6zUkL4reoXbw==", + "license": "ISC", + "dependencies": { + "debug": "^4.4.1", + "eslint-import-context": "^0.1.8", + "get-tsconfig": "^4.10.1", + "is-bun-module": "^2.0.0", + "stable-hash-x": "^0.2.0", + "tinyglobby": "^0.2.14", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^16.17.0 || >=18.6.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-typescript/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-typescript/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==", + "license": "MIT" + }, + "node_modules/eslint-module-utils": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.14.0.tgz", + "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils/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==", + "license": "MIT" + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/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==", + "license": "MIT" + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "29.16.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.16.0.tgz", + "integrity": "sha512-0WFBxDHlT2ratGQfnFQEVIsgQJ5cfd+0IV8Kc6U3X2onB8ATLG23voD2Ch5G9fCkEpCPmCMuzW0tbS0kYb8biw==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.0.0" + }, + "engines": { + "node": "^20.12.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^8.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "jest": "*", + "typescript": ">=4.8.4 <8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jsdoc": { + "version": "50.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.8.0.tgz", + "integrity": "sha512-UyGb5755LMFWPrZTEqqvTJ3urLz1iqj+bYOHFNag+sw3NvaMWP9K2z+uIn37XfNALmQLQyrBlJ5mkiVPL7ADEg==", + "license": "BSD-3-Clause", + "dependencies": { + "@es-joy/jsdoccomment": "~0.50.2", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.4.1", + "escape-string-regexp": "^4.0.0", + "espree": "^10.3.0", + "esquery": "^1.6.0", + "parse-imports-exports": "^0.2.4", + "semver": "^7.7.2", + "spdx-expression-parse": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jsdoc/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==", + "license": "MIT" + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-jsx-a11y/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==", + "license": "MIT" + }, + "node_modules/eslint-plugin-playwright": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-2.11.0.tgz", + "integrity": "sha512-zOIEYyv1TSn2izXkyg/yj0LXc4YBQI6pl3xIFWwMCcXt/dgQCz8dBqXiXFMoM3xc/GqZThAtaGYo8aPu/vBd+Q==", + "license": "MIT", + "dependencies": { + "globals": "^17.3.0" + }, + "engines": { + "node": ">=16.9.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/eslint-plugin-playwright/node_modules/globals": { + "version": "17.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.9.0.tgz", + "integrity": "sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.6.tgz", + "integrity": "sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==", + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.13" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.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/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT", + "peer": true + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/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==", + "license": "MIT", + "peer": true + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "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==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "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==", + "license": "MIT", + "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/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-tilde": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", + "integrity": "sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==", + "license": "MIT", + "dependencies": { + "os-homedir": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/expect-puppeteer": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/expect-puppeteer/-/expect-puppeteer-4.4.0.tgz", + "integrity": "sha512-6Ey4Xy2xvmuQu7z7YQtMsaMV0EHJRpVxIDOd5GRrm04/I3nkTKIutELfECsLp6le+b3SSa3cXhPiw6PgqzxYWA==", + "license": "MIT" + }, + "node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "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/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/extract-zip/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==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "license": "Apache-2.0" + }, + "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==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-builder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.0.tgz", + "integrity": "sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.6.2", + "xml-naming": "^0.3.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.10.1.tgz", + "integrity": "sha512-IEMIf7298kXuZSRFoGfMYrl7is8LpavODgbNz1cwIudv7KwVFnuU+UsMporfq6PD6aXSlawZlARiA3UywCTfMw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^3.0.0", + "fast-xml-builder": "^1.2.0", + "is-unsafe": "^2.0.0", + "path-expression-matcher": "^1.6.2", + "strnum": "^2.4.1", + "xml-naming": "^0.3.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "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==", + "license": "Apache-2.0", + "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==", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "license": "MIT", + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "license": "MIT", + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-file-up": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/find-file-up/-/find-file-up-0.1.3.tgz", + "integrity": "sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A==", + "license": "MIT", + "dependencies": { + "fs-exists-sync": "^0.1.0", + "resolve-dir": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-parent-dir": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.1.tgz", + "integrity": "sha512-o4UcykWV/XN9wm+jMEtWLPlV8RXCZnMhQI6F6OdHeSez7iiJWePw8ijOlskJZMsaQoGR/b7dH6lO02HhaTN7+A==", + "license": "MIT" + }, + "node_modules/find-pkg": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/find-pkg/-/find-pkg-0.1.2.tgz", + "integrity": "sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==", + "license": "MIT", + "dependencies": { + "find-file-up": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-process": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.4.11.tgz", + "integrity": "sha512-mAOh9gGk9WZ4ip5UjV0o6Vb4SrfnAmtsFNzkMRH9HQiFXVQnDyQFrSHTK5UoG6E+KV+s+cIznbtwpfN41l2nFA==", + "license": "MIT", + "dependencies": { + "chalk": "~4.1.2", + "commander": "^12.1.0", + "loglevel": "^1.9.2" + }, + "bin": { + "find-process": "bin/find-process.js" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", + "license": "MIT", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "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": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.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==", + "license": "MIT" + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-exists-sync": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", + "integrity": "sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fs-ext-extra-prebuilt": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/fs-ext-extra-prebuilt/-/fs-ext-extra-prebuilt-2.2.7.tgz", + "integrity": "sha512-Q7rayYRBDIvDF01HWOwSSjoaP+05N1g+o3BXL1Zf8Frw2JkjSmi4EtvCBITuW30l6hB2m2TW1pehdh8wyU/+gw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "nan": "^2.24.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/fs-extra": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-monkey": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", + "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", + "license": "Unlicense" + }, + "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==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "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==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "license": "MIT", + "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==", + "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==", + "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==", + "license": "MIT", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stdin": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", + "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.1.tgz", + "integrity": "sha512-Dz/6HxkrxgNehhxLVeyv8sad9UzF2xBVeaKBQNDfJ5XiSXmp2gTR0eO0RWiT2NCKS5aGP9jjkOMggTN90qU50A==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/get-uri/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==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "encoding": "^0.1.12", + "safe-buffer": "^5.1.1" + } + }, + "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", + "license": "ISC", + "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/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global-modules": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", + "integrity": "sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==", + "license": "MIT", + "dependencies": { + "global-prefix": "^0.1.4", + "is-windows": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", + "integrity": "sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==", + "license": "MIT", + "dependencies": { + "homedir-polyfill": "^1.0.0", + "ini": "^1.3.4", + "is-windows": "^0.2.0", + "which": "^1.2.12" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz", + "integrity": "sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==", + "license": "MIT", + "dependencies": { + "array-union": "^3.0.1", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.7", + "ignore": "^5.1.9", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "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==", + "license": "ISC" + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "license": "MIT" + }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "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==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, + "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==", + "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==", + "license": "MIT", + "dependencies": { + "capital-case": "^1.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "license": "MIT", + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "license": "MIT" + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-link-header": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/http-link-header/-/http-link-header-1.1.4.tgz", + "integrity": "sha512-xT3GPW6/ZbGuw4UvwHqErSCEjNUlwbQJuZn9/q5U4WEKfp2kENVCAlousG1zLxHeaQ/ffOHUNpWamvkbBW0eNw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.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==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/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==", + "license": "MIT" + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.10.tgz", + "integrity": "sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ==", + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "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==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/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==", + "license": "MIT" + }, + "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==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-walk": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-4.0.1.tgz", + "integrity": "sha512-rzDQLaW4jQbh2YrOFlJdCtX8qgJTehFRYiUB2r1osqTeDzV/3+Jh8fz1oAPzUThf3iku8Ds4IDqawI5d8mUiQw==", + "license": "ISC", + "dependencies": { + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/immutable": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", + "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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==", + "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-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "license": "MIT", + "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/import-local/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==", + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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.", + "license": "ISC", + "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==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", + "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "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==", + "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.4.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.4.0.tgz", + "integrity": "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/irregular-plurals": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz", + "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.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==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "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==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unsafe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.0.tgz", + "integrity": "sha512-2LdV822R+wmI86unXA93WCFpL6g+av8ynWk0nrHyJqGop5VoocYsSLFgN8jrfalT6iGeLNM4KXuVSsULP53kEA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isomorphic-git": { + "version": "1.37.6", + "resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.37.6.tgz", + "integrity": "sha512-qr1NFCPsVTZ6YGqTXw0CzamnsHyH9QQ1OTEfeXIweSljRUMzuHFCJdUn0wc6OcjtTDns6knxjPb7N6LmJeftOA==", + "license": "MIT", + "dependencies": { + "async-lock": "^1.4.1", + "clean-git-ref": "^2.0.1", + "crc-32": "^1.2.0", + "diff3": "0.0.3", + "ignore": "^5.1.4", + "minimisted": "^2.0.0", + "pako": "^1.0.10", + "pify": "^4.0.1", + "readable-stream": "^4.0.0", + "sha.js": "^2.4.12", + "simple-get": "^4.0.1" + }, + "bin": { + "isogit": "cli.cjs" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "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==", + "license": "BSD-3-Clause", + "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": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/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==", + "license": "MIT" + }, + "node_modules/istanbul-lib-source-maps/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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-dev-server": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-10.1.4.tgz", + "integrity": "sha512-bGQ6sedNGtT6AFHhCVqGTXMPz7UyJi/ZrhNBgyqsP0XU9N8acCEIfqZEA22rOaZ+NdEVsaltk6tL7UT6aXfI7w==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "cwd": "^0.10.0", + "find-process": "^1.4.7", + "prompts": "^2.4.2", + "spawnd": "^10.1.4", + "tree-kill": "^1.2.2", + "wait-on": "^8.0.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.4.1.tgz", + "integrity": "sha512-o3nfaN4zej7qgk2X0j8Jhq/S9nAVKs2xK3QeQxeHVvpkEPxaA1yxDGydR+iVI7zPy7Cp62Aq2h3Ja46QvfWHGA==", + "license": "MIT", + "dependencies": { + "@jest/environment": "30.4.1", + "@jest/environment-jsdom-abstract": "30.4.1", + "jsdom": "^26.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-jsdom/node_modules/@jest/environment": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz", + "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==", + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "jest-mock": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@jest/fake-timers": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz", + "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==", + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@sinonjs/fake-timers": "^15.4.0", + "@types/node": "*", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@jest/schemas": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@jest/types": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", + "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.4.0", + "@jest/schemas": "30.4.1", + "@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/jest-environment-jsdom/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "license": "MIT" + }, + "node_modules/jest-environment-jsdom/node_modules/@sinonjs/fake-timers": { + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", + "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/jest-environment-jsdom/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==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-environment-jsdom/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==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-environment-jsdom/node_modules/jest-message-util": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", + "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.4.1", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-util": "30.4.1", + "picomatch": "^4.0.3", + "pretty-format": "30.4.1", + "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-environment-jsdom/node_modules/jest-mock": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", + "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/jest-util": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", + "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-environment-jsdom/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/joi": { + "version": "18.2.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-18.2.3.tgz", + "integrity": "sha512-N5A3KTWQpPWT4ExxxPlUx7WmykGXRzhNidWhV41d6Abu9YfI2NyWCJuxdPnslJCPWtbRpSVOWSnSS6GakLM/Rg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/address": "^5.1.1", + "@hapi/formula": "^3.0.2", + "@hapi/hoek": "^11.0.7", + "@hapi/pinpoint": "^2.0.1", + "@hapi/tlds": "^1.1.1", + "@hapi/topo": "^6.0.2", + "@standard-schema/spec": "^1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "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==", + "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==", + "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==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", + "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", + "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "license": "MIT" + }, + "node_modules/json2php": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/json2php/-/json2php-0.0.9.tgz", + "integrity": "sha512-fQMYwvPsQt8hxRnCGyg1r2JVi6yL8Um0DIIawiKiMK9yhAAkcRNj5UsBWoaFvFzPpcWbgw9L6wzj+UMYA702Mw==", + "license": "BSD" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/known-css-properties": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", + "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", + "license": "MIT" + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/launch-editor": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz", + "integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==", + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.4" + } + }, + "node_modules/lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lighthouse": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/lighthouse/-/lighthouse-12.8.2.tgz", + "integrity": "sha512-+5SKYzVaTFj22MgoYDPNrP9tlD2/Ay7j3SxPSFD9FpPyVxGr4UtOQGKyrdZ7wCmcnBaFk0mCkPfARU3CsE0nvA==", + "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==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.1", + "marky": "^1.2.2" + } + }, + "node_modules/lighthouse-logger/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/lighthouse-logger/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==", + "license": "MIT" + }, + "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==", + "license": "Apache-2.0" + }, + "node_modules/lighthouse/node_modules/@puppeteer/browsers": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.2.tgz", + "integrity": "sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw==", + "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.4", + "tar-fs": "^3.1.1", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/lighthouse/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/lighthouse/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==", + "license": "MIT" + }, + "node_modules/lighthouse/node_modules/puppeteer-core": { + "version": "24.43.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.43.1.tgz", + "integrity": "sha512-T5ScUMAsmhdNbgDR41AGESYeS6V9MSgetkSnVhhW+gXvzC42VesKCn5ld87gAZDJ6vLHL9GkRvY9WtQWSnwFbw==", + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.13.2", + "chromium-bidi": "14.0.0", + "debug": "^4.4.3", + "devtools-protocol": "0.0.1608973", + "typed-query-selector": "^2.12.2", + "webdriver-bidi-protocol": "0.4.1", + "ws": "^8.20.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/lighthouse/node_modules/puppeteer-core/node_modules/chromium-bidi": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz", + "integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==", + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/lighthouse/node_modules/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.1608973", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1608973.tgz", + "integrity": "sha512-Tpm17fxYzt+J7VrGdc1k8YdRqS3YV7se/M6KeemEqvUbq/n7At1rWVuXMxQgpWkdwSdIEKYbU//Bve+Shm4YNQ==", + "license": "BSD-3-Clause" + }, + "node_modules/lighthouse/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==", + "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/lighthouse/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==", + "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/lighthouse/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "license": "MIT", + "peer": true + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loglevel": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "name": "@wolfy1339/lru-cache", + "version": "11.0.2-patch.1", + "resolved": "https://registry.npmjs.org/@wolfy1339/lru-cache/-/lru-cache-11.0.2-patch.1.tgz", + "integrity": "sha512-BgYZfL2ADCXKOw2wJtkM3slhHotawWkgIRRxq4wEybnZQPjvAp71SPX35xepMykTw8gXlzWcWPTY31hlbnRsDA==", + "license": "ISC", + "engines": { + "node": "18 >=18.20 || 20 || >=22" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/map-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-values/-/map-values-1.0.1.tgz", + "integrity": "sha512-BbShUnr5OartXJe1GeccAWtfro11hhgNJg6G9/UtWKjVGvV5U4C09cg5nk8JUevhXODaXY+hQ3xxMUKSs62ONQ==", + "license": "Public Domain" + }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/markdownlint": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.25.1.tgz", + "integrity": "sha512-AG7UkLzNa1fxiOv5B+owPsPhtM4D6DoODhsJgiaNg1xowXovrYgOnLqAgOOFQpWOlHFVQUzjMY5ypNNTeov92g==", + "license": "MIT", + "dependencies": { + "markdown-it": "12.3.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/markdownlint-cli": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.31.1.tgz", + "integrity": "sha512-keIOMwQn+Ch7MoBwA+TdkyVMuxAeZFEGmIIlvwgV0Z1TGS5MxPnRr29XCLhkNzCHU+uNKGjU+VEjLX+Z9kli6g==", + "license": "MIT", + "dependencies": { + "commander": "~9.0.0", + "get-stdin": "~9.0.0", + "glob": "~7.2.0", + "ignore": "~5.2.0", + "js-yaml": "^4.1.0", + "jsonc-parser": "~3.0.0", + "markdownlint": "~0.25.1", + "markdownlint-rule-helpers": "~0.16.0", + "minimatch": "~3.0.5", + "run-con": "~1.2.10" + }, + "bin": { + "markdownlint": "markdownlint.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/markdownlint-cli/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/markdownlint-cli/node_modules/commander": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.0.0.tgz", + "integrity": "sha512-JJfP2saEKbQqvW+FI93OYUB4ByV5cizMpFMiiJI8xDbBvQvSkIk0VvQdn1CZ8mqAO8Loq2h0gYTYtDFUZUeERw==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/markdownlint-cli/node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/markdownlint-cli/node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/markdownlint-cli/node_modules/jsonc-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", + "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==", + "license": "MIT" + }, + "node_modules/markdownlint-cli/node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/markdownlint-rule-helpers": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.16.0.tgz", + "integrity": "sha512-oEacRUVeTJ5D5hW1UYd2qExYI0oELdYK72k1TKGvIeYJIbqQWAz476NAc7LNixSySUhcNl++d02DvX0ccDk9/w==", + "license": "MIT" + }, + "node_modules/marky": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", + "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "license": "MIT" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "license": "Unlicense", + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/memize": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/memize/-/memize-2.1.1.tgz", + "integrity": "sha512-8Nl+i9S5D6KXnruM03Jgjb+LwSupvR13WBr4hJegaaEyobvowCVupi79y2WSiWvO1mzBWxPwEYE5feCe8vyA5w==", + "license": "MIT" + }, + "node_modules/meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "license": "MIT", + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/merge-deep": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", + "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", + "license": "MIT", + "dependencies": { + "arr-union": "^3.1.0", + "clone-deep": "^0.2.4", + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.2.tgz", + "integrity": "sha512-AOSS0IdEB95ayVkxn5oGzNQwqAi2J0Jb/kKm43t7H73s8+f5873g0yuj0PNvK4dO75mu5DHg4nlgp4k6Kga8eg==", + "license": "MIT", + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "license": "MIT", + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/minimist-options/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/minimist-options/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/minimisted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minimisted/-/minimisted-2.0.1.tgz", + "integrity": "sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5" + } + }, + "node_modules/minimizer-webpack-plugin": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.6.1.tgz", + "integrity": "sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/minimizer-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/minimizer-webpack-plugin/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==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==", + "license": "MIT", + "dependencies": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-object/node_modules/for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/mousetrap": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz", + "integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==", + "license": "Apache-2.0 WITH LLVM-exception" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/nan": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.28.0.tgz", + "integrity": "sha512-fTsDz99OTq2sVePhGdp4qQhggZFtKr64ZNVyVajRKtMOkJxYekplBh577PiJB12v/D3s2E5cGtOI45LWp6rnLQ==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "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==", + "license": "MIT", + "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==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "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==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, + "node_modules/node-exports-info": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/node-forge": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-bundled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", + "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "license": "ISC" + }, + "node_modules/npm-package-json-lint": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/npm-package-json-lint/-/npm-package-json-lint-6.4.0.tgz", + "integrity": "sha512-cuXAJJB1Rdqz0UO6w524matlBqDBjcNt7Ru+RDIu4y6RI1gVqiWBnylrK8sPRk81gGBA0X8hJbDXolVOoTc+sA==", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.6", + "ajv-errors": "^1.0.1", + "chalk": "^4.1.2", + "cosmiconfig": "^8.0.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "ignore": "^5.2.0", + "is-plain-obj": "^3.0.0", + "jsonc-parser": "^3.2.0", + "log-symbols": "^4.1.0", + "meow": "^9.0.0", + "plur": "^4.0.0", + "semver": "^7.3.8", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1", + "type-fest": "^3.2.0", + "validate-npm-package-name": "^5.0.0" + }, + "bin": { + "npmPkgJsonLint": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/npm-package-json-lint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/npm-package-json-lint/node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm-package-json-lint/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm-package-json-lint/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-package-json-lint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/npm-package-json-lint/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==", + "license": "MIT" + }, + "node_modules/npm-package-json-lint/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-packlist": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-3.0.0.tgz", + "integrity": "sha512-L/cbzmutAwII5glUcf2DBRNY/d0TFd4e/FnaZigJV6JD85RHZXJFGwCndjMWiiViiWSsWt3tiOLpI3ByTnIdFQ==", + "license": "ISC", + "dependencies": { + "glob": "^7.1.6", + "ignore-walk": "^4.0.1", + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + }, + "bin": { + "npm-packlist": "bin/index.js" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.24", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.24.tgz", + "integrity": "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==", + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-filter": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object-filter/-/object-filter-1.0.2.tgz", + "integrity": "sha512-NahvP2vZcy1ZiiYah30CEPw0FpDcSkSePJBMpzl5EQgCmISijiGuJm3SPYp7U+Lf2TljyaIw3E5EgkEx/TNEVA==", + "license": "MIT" + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "license": "MIT" + }, + "node_modules/octokit": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/octokit/-/octokit-3.1.2.tgz", + "integrity": "sha512-MG5qmrTL5y8KYwFgE1A4JWmgfQBaIETE/lOlfwNYx1QOtCQHGVxkRJmdUJltFc1HVn73d61TlMhMyNTOtMl+ng==", + "license": "MIT", + "dependencies": { + "@octokit/app": "^14.0.2", + "@octokit/core": "^5.0.0", + "@octokit/oauth-app": "^6.0.0", + "@octokit/plugin-paginate-graphql": "^4.0.0", + "@octokit/plugin-paginate-rest": "^9.0.0", + "@octokit/plugin-rest-endpoint-methods": "^10.0.0", + "@octokit/plugin-retry": "^6.0.0", + "@octokit/plugin-throttling": "^8.0.0", + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "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==", + "license": "MIT", + "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==", + "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/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "MIT", + "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==", + "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-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/pac-proxy-agent/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==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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==" + }, + "node_modules/parse-imports-exports": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", + "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", + "license": "MIT", + "dependencies": { + "parse-statements": "1.0.11" + } + }, + "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==", + "license": "MIT", + "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/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-statements": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", + "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", + "license": "MIT" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-expression-matcher": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", + "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "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==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.16.0.tgz", + "integrity": "sha512-sILXutLVjCLjcDuOmvhX5e2Z4cS5qG/6Bu3VkpFwdf/633ElGLpEh9bgmuI5I4sqKqkifQiGyiCcx1HdtrK7tg==", + "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==", + "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==", + "license": "ISC" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "license": "MIT", + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "license": "MIT", + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/pkg-dir/node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/playwright": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", + "integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "playwright-core": "1.62.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz", + "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "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==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/plur": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/plur/-/plur-4.0.0.tgz", + "integrity": "sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==", + "license": "MIT", + "dependencies": { + "irregular-plurals": "^3.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-calc": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", + "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-colormin": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", + "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "colord": "^2.9.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-convert-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", + "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-comments": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", + "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", + "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-empty": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", + "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", + "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-import": { + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-16.1.1.tgz", + "integrity": "sha512-2xVS1NCZAfjtVdvXiyegxzJ447GyqCeEI5V7ApgQVOWnros1p5lGNovJNapwPpMombyFBfqDwt7AD3n2l0KOfQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-loader": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", + "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", + "license": "MIT", + "dependencies": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-loader/node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "license": "MIT" + }, + "node_modules/postcss-merge-longhand": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", + "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^6.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-merge-rules": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", + "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^4.0.2", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", + "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", + "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "license": "MIT", + "dependencies": { + "colord": "^2.9.3", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-params": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", + "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", + "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", + "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", + "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", + "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", + "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-string": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", + "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", + "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", + "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", + "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", + "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-ordered-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", + "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "license": "MIT", + "dependencies": { + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", + "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", + "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-resolve-nested-selector": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz", + "integrity": "sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==", + "license": "MIT" + }, + "node_modules/postcss-safe-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", + "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.29" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", + "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^3.2.0" + }, + "engines": { + "node": "^14 || ^16 || >= 18" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", + "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "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==", + "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==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "name": "wp-prettier", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-3.0.3.tgz", + "integrity": "sha512-X4UlrxDTH8oom9qXlcjnydsjAOD2BmB6yFmvS4Z2zdTzqqpRWb+fbqrH412+l+OUXmbzJlSXjlMFYPgYG12IAA==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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==", + "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/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-agent/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==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer-core": { + "version": "23.11.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.11.1.tgz", + "integrity": "sha512-3HZ2/7hdDKZvZQ7dhhITOUg4/wOrDRjyK2ZBllRB0ZCOi9u0cwq1ACHDjBB+nX+7+kltHjQvBRdeY7+W0T+7Gg==", + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.6.1", + "chromium-bidi": "0.11.0", + "debug": "^4.4.0", + "devtools-protocol": "0.0.1367902", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.1367902", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz", + "integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==", + "license": "BSD-3-Clause" + }, + "node_modules/puppeteer-core/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==", + "license": "MIT" + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "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/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/react-is-18": { + "name": "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==", + "license": "MIT" + }, + "node_modules/react-is-19": { + "name": "react-is", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-cache/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "license": "MIT", + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "license": "ISC" + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz", + "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==", + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/requestidlecallback": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/requestidlecallback/-/requestidlecallback-0.3.0.tgz", + "integrity": "sha512-TWHFkT7S9p7IxLC5A1hYmAYQx2Eb9w1skrXmQ+dS1URyvR8tenMLl4lHbqEOUnpEYxNKpkVMXUgknVpBZWXXfQ==", + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "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==", + "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/require-in-the-middle/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/require-in-the-middle/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==", + "license": "MIT" + }, + "node_modules/requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "license": "MIT", + "engines": { + "node": ">=0.10.5" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "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-bin": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/resolve-bin/-/resolve-bin-0.4.3.tgz", + "integrity": "sha512-9u8TMpc+SEHXxQXblXHz5yRvRZERkCZimFN9oz85QI3uhkh7nqfjm6OGTLg+8vucpXGcY4jLK6WkylPmt7GSvw==", + "license": "MIT", + "dependencies": { + "find-parent-dir": "~0.3.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", + "integrity": "sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==", + "license": "MIT", + "dependencies": { + "expand-tilde": "^1.2.2", + "global-modules": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "license": "MIT" + }, + "node_modules/rtlcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", + "license": "MIT", + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0", + "postcss": "^8.4.21", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "rtlcss": "bin/rtlcss.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/run-con": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/run-con/-/run-con-1.2.12.tgz", + "integrity": "sha512-5257ILMYIF4RztL9uoZ7V9Q97zHtNHn5bN3NobeAnzB1P3ASLgg8qocM2u+R18ttp+VEM78N2LK8XcNVtnSRrg==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~3.0.0", + "minimist": "^1.2.8", + "strip-json-comments": "~3.1.1" + }, + "bin": { + "run-con": "cli.js" + } + }, + "node_modules/run-con/node_modules/ini": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.1.tgz", + "integrity": "sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ==", + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "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/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sass": { + "version": "1.102.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.102.0.tgz", + "integrity": "sha512-NSOyTnaQF7rTAEOtI2fwb386vL+akyiQLBZu8Na7hXCb+umJy0GAqlcMIaqACZ6Z1VgTBS4K9PG6B3IdjHGJsw==", + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "immutable": "^5.1.5", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=20.19.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass-loader": { + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.8.tgz", + "integrity": "sha512-hcov4ZwZJIGbEuyNr9EmiTmZueyrxSToE6GOzoZnq5JM7ecRO7ttyvilPn+VmRsqiP16+VYZzVnGZj/hzZgKBA==", + "license": "MIT", + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "license": "MIT", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz", + "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.8.0", + "mime-types": "~2.1.35", + "parseurl": "~1.3.3" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shallow-clone": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", + "integrity": "sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.1", + "kind-of": "^2.0.1", + "lazy-cache": "^0.2.3", + "mixin-object": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shallow-clone/node_modules/kind-of": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "integrity": "sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg==", + "license": "MIT", + "dependencies": { + "is-buffer": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shallow-clone/node_modules/lazy-cache": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", + "integrity": "sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", + "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", + "license": "BSD-2-Clause" + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "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==", + "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==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/socks": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.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==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socks-proxy-agent/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==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.2.tgz", + "integrity": "sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==", + "license": "MIT", + "dependencies": { + "abab": "^2.0.5", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/source-map-loader/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==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "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==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawnd": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-10.1.4.tgz", + "integrity": "sha512-drqHc0mKJmtMsiGMOCwzlc5eZ0RPtRvT7tQAluW2A0qUc0G7TQ8KLcn3E6K5qzkLkH2UkS3nYQiVGULvvsD9dw==", + "license": "MIT", + "dependencies": { + "signal-exit": "^4.1.0", + "tree-kill": "^1.2.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/spawnd/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==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "license": "CC0-1.0" + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/spdy-transport/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/spdy-transport/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==", + "license": "MIT" + }, + "node_modules/spdy-transport/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/spdy/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/spdy/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==", + "license": "MIT" + }, + "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==", + "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==", + "license": "BSD-3-Clause" + }, + "node_modules/stable-hash-x": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", + "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/streamx": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz", + "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.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==", + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-outer/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/strnum": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.1.tgz", + "integrity": "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "anynum": "^1.0.1" + } + }, + "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==", + "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==", + "license": "MIT" + }, + "node_modules/style-search": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", + "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", + "license": "ISC" + }, + "node_modules/stylehacks": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", + "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/stylelint": { + "version": "16.26.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.26.1.tgz", + "integrity": "sha512-v20V59/crfc8sVTAtge0mdafI3AdnzQ2KsWe6v523L4OA1bJO02S7MO2oyXDCS6iWb9ckIPnqAFVItqSBQr7jw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-syntax-patches-for-csstree": "^1.0.19", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3", + "@csstools/selector-specificity": "^5.0.0", + "@dual-bundle/import-meta-resolve": "^4.2.1", + "balanced-match": "^2.0.0", + "colord": "^2.9.3", + "cosmiconfig": "^9.0.0", + "css-functions-list": "^3.2.3", + "css-tree": "^3.1.0", + "debug": "^4.4.3", + "fast-glob": "^3.3.3", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^11.1.1", + "global-modules": "^2.0.0", + "globby": "^11.1.0", + "globjoin": "^0.1.4", + "html-tags": "^3.3.1", + "ignore": "^7.0.5", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.37.0", + "mathml-tag-names": "^2.1.3", + "meow": "^13.2.0", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.5.6", + "postcss-resolve-nested-selector": "^0.1.6", + "postcss-safe-parser": "^7.0.1", + "postcss-selector-parser": "^7.1.0", + "postcss-value-parser": "^4.2.0", + "resolve-from": "^5.0.0", + "string-width": "^4.2.3", + "supports-hyperlinks": "^3.2.0", + "svg-tags": "^1.0.0", + "table": "^6.9.0", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "stylelint": "bin/stylelint.mjs" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/stylelint-config-recommended": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-14.0.1.tgz", + "integrity": "sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": "^16.1.0" + } + }, + "node_modules/stylelint-config-recommended-scss": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-14.1.0.tgz", + "integrity": "sha512-bhaMhh1u5dQqSsf6ri2GVWWQW5iUjBYgcHkh7SgDDn92ijoItC/cfO/W+fpXshgTQWhwFkP1rVcewcv4jaftRg==", + "license": "MIT", + "dependencies": { + "postcss-scss": "^4.0.9", + "stylelint-config-recommended": "^14.0.1", + "stylelint-scss": "^6.4.0" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "postcss": "^8.3.3", + "stylelint": "^16.6.1" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + } + } + }, + "node_modules/stylelint-scss": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.14.0.tgz", + "integrity": "sha512-ZKmHMZolxeuYsnB+PCYrTpFce0/QWX9i9gh0hPXzp73WjuIMqUpzdQaBCrKoLWh6XtCFSaNDErkMPqdjy1/8aA==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.1", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.37.0", + "mdn-data": "^2.25.0", + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.6", + "postcss-selector-parser": "^7.1.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": "^16.8.2" + } + }, + "node_modules/stylelint-scss/node_modules/postcss-selector-parser": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint/node_modules/@csstools/media-query-list-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", + "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/stylelint/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/stylelint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/stylelint/node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "license": "MIT" + }, + "node_modules/stylelint/node_modules/cosmiconfig": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/stylelint/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/stylelint/node_modules/file-entry-cache": { + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", + "license": "MIT", + "dependencies": { + "flat-cache": "^6.1.23" + } + }, + "node_modules/stylelint/node_modules/flat-cache": { + "version": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", + "license": "MIT", + "dependencies": { + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" + } + }, + "node_modules/stylelint/node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "license": "MIT", + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint/node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "license": "MIT", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/globby/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/stylelint/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/stylelint/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/stylelint/node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/stylelint/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint/node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/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==", + "license": "MIT" + }, + "node_modules/stylelint/node_modules/postcss-selector-parser": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint/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==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/stylelint/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/stylelint/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==", + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "license": "MIT" + }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==" + }, + "node_modules/svgo": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.4.tgz", + "integrity": "sha512-GsNRis4e8jxn2Y9ENz/8lbJ93CstG8svtMnuRaHbiF2LTJ5tK0/q3t/URPq9Zc7zVWBJnNnJMIp6bevK7bSmNg==", + "license": "MIT", + "dependencies": { + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0", + "sax": "^1.5.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/svgo/node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/svgo/node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "license": "CC0-1.0" + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" + }, + "node_modules/synckit": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.13.tgz", + "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==", + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.3.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@tannin/plural-forms": "^1.1.0" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar-fs": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.3.tgz", + "integrity": "sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==", + "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.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/tar-stream/node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/terser": { + "version": "5.49.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.2.tgz", + "integrity": "sha512-rGbJiKeQ4WDe3EXlDAIaQcwftVfv2Q8o1awFNfvXolJYKkb1AuZY1RTOmqx4LJXZENbWZA7eIsYGHuEzHsi1nQ==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" }, "engines": { - "node": ">= 0.4" + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz", + "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "uglify-js": { + "optional": true + } } }, - "node_modules/is-unsafe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.0.tgz", - "integrity": "sha512-2LdV822R+wmI86unXA93WCFpL6g+av8ynWk0nrHyJqGop5VoocYsSLFgN8jrfalT6iGeLNM4KXuVSsULP53kEA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/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==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/terser/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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.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==", + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/text-decoder/node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true } - ], + } + }, + "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==", "license": "MIT" }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "license": "MIT" }, - "node_modules/isomorphic-git": { - "version": "1.37.6", - "resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.37.6.tgz", - "integrity": "sha512-qr1NFCPsVTZ6YGqTXw0CzamnsHyH9QQ1OTEfeXIweSljRUMzuHFCJdUn0wc6OcjtTDns6knxjPb7N6LmJeftOA==", + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "license": "MIT", "dependencies": { - "async-lock": "^1.4.1", - "clean-git-ref": "^2.0.1", - "crc-32": "^1.2.0", - "diff3": "0.0.3", - "ignore": "^5.1.4", - "minimisted": "^2.0.0", - "pako": "^1.0.10", - "pify": "^4.0.1", - "readable-stream": "^4.0.0", - "sha.js": "^2.4.12", - "simple-get": "^4.0.1" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, - "bin": { - "isogit": "cli.cjs" + "engines": { + "node": ">=12.0.0" }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", "engines": { - "node": ">=14.17" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.10.tgz", + "integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==", "license": "MIT" }, - "node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "node_modules/tldts-icann": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts-icann/-/tldts-icann-7.4.10.tgz", + "integrity": "sha512-JrzJnTNDURpnyPCf/b0bq/mAiQhPcNwkwpfO67M0nECzVzSIuCFN+EYjqnEjnmO60nPRabS3zIFChbwPp/Q+Lg==", + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.10" + } + }, + "node_modules/tldts/node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", "license": "MIT" }, - "node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", "license": "MIT", "dependencies": { - "universalify": "^2.0.0" + "tmp": "^0.2.0" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "license": "BSD-3-Clause" + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "engines": { + "node": ">= 0.4" } }, - "node_modules/jsonwebtoken": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", - "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "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==", "license": "MIT", "dependencies": { - "jws": "^4.0.1", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" }, "engines": { - "node": ">=12", - "npm": ">=6" + "node": ">=18" } }, - "node_modules/jsonwebtoken/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } }, - "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" + "engines": { + "node": ">=8" } }, - "node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", "license": "MIT", "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "license": "MIT" + "node_modules/trim-repeated/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT" + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "license": "MIT" + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "license": "MIT" + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } }, - "node_modules/lru-cache": { - "name": "@wolfy1339/lru-cache", - "version": "11.0.2-patch.1", - "resolved": "https://registry.npmjs.org/@wolfy1339/lru-cache/-/lru-cache-11.0.2-patch.1.tgz", - "integrity": "sha512-BgYZfL2ADCXKOw2wJtkM3slhHotawWkgIRRxq4wEybnZQPjvAp71SPX35xepMykTw8gXlzWcWPTY31hlbnRsDA==", - "license": "ISC", + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "license": "MIT", "engines": { - "node": "18 >=18.20 || 20 || >=22" + "node": ">=4" } }, - "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==", + "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==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, "engines": { - "node": ">= 0.4" + "node": ">= 0.8.0" } }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "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==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=4" } }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", + "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==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, "engines": { "node": ">= 0.6" } }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "license": "MIT", - "bin": { - "mime": "cli.js" + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" }, "engines": { - "node": ">=4" + "node": ">= 0.4" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, "engines": { - "node": ">= 0.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "node_modules/typed-query-selector": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz", + "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.66.0.tgz", + "integrity": "sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.66.0", + "@typescript-eslint/parser": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0", + "@typescript-eslint/utils": "8.66.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "license": "MIT" + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minimisted": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/minimisted/-/minimisted-2.0.1.tgz", - "integrity": "sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA==", + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", "license": "MIT", "dependencies": { - "minimist": "^1.2.5" + "buffer": "^5.2.1", + "through": "^2.3.8" } }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "node_modules/unbzip2-stream/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } }, - "node_modules/nan": { - "version": "2.28.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.28.0.tgz", - "integrity": "sha512-fTsDz99OTq2sVePhGdp4qQhggZFtKr64ZNVyVajRKtMOkJxYekplBh577PiJB12v/D3s2E5cGtOI45LWp6rnLQ==", + "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==", "license": "MIT" }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=4" } }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=4" } }, - "node_modules/octokit": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/octokit/-/octokit-3.1.2.tgz", - "integrity": "sha512-MG5qmrTL5y8KYwFgE1A4JWmgfQBaIETE/lOlfwNYx1QOtCQHGVxkRJmdUJltFc1HVn73d61TlMhMyNTOtMl+ng==", + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", "license": "MIT", - "dependencies": { - "@octokit/app": "^14.0.2", - "@octokit/core": "^5.0.0", - "@octokit/oauth-app": "^6.0.0", - "@octokit/plugin-paginate-graphql": "^4.0.0", - "@octokit/plugin-paginate-rest": "^9.0.0", - "@octokit/plugin-rest-endpoint-methods": "^10.0.0", - "@octokit/plugin-retry": "^6.0.0", - "@octokit/plugin-throttling": "^8.0.0", - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^12.0.0" - }, "engines": { - "node": ">= 18" + "node": ">=4" } }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, "engines": { - "node": ">= 0.8" + "node": ">=4" } }, - "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==", - "license": "ISC", + "node_modules/universal-github-app-jwt": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.2.0.tgz", + "integrity": "sha512-dncpMpnsKBk0eetwfN8D8OUHGfiDhhJ+mtsbMl+7PfW7mYjiH8LIcqRmYMtzYLgSh47HjfdBtrBwIQ/gizKR3g==", + "license": "MIT", "dependencies": { - "wrappy": "1" + "@types/jsonwebtoken": "^9.0.0", + "jsonwebtoken": "^9.0.2" } }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "license": "(MIT AND Zlib)" + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "license": "ISC" }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/path-expression-matcher": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", - "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", + "node_modules/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.0.tgz", + "integrity": "sha512-x/M6q3w4Ybp91CNaS4S69UnliqR3BzRpOT6LWbksjth0S/+jhfaPJsWjt/TewpT8j9eLIojUf5jr29WextHroA==", "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/NaturalIntelligence" + "url": "https://github.com/sponsors/ai" } ], "license": "MIT", - "engines": { - "node": ">=14.0.0" + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/path-to-regexp": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", - "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", - "license": "MIT" - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "node_modules/upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", + "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "tslib": "^2.0.3" } }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "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==", "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "tslib": "^2.0.3" } }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "license": "MIT", - "engines": { - "node": ">= 0.6.0" + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "node_modules/url-loader": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", + "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", "license": "MIT", "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" + "loader-utils": "^2.0.0", + "mime-types": "^2.1.27", + "schema-utils": "^3.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "file-loader": "*", + "webpack": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "file-loader": { + "optional": true + } } }, - "node_modules/qs": { - "version": "6.15.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", - "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", - "license": "BSD-3-Clause", + "node_modules/url-loader/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "license": "MIT", "dependencies": { - "es-define-property": "^1.0.1", - "side-channel": "^1.1.1" - }, - "engines": { - "node": ">=0.6" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "node_modules/url-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "license": "MIT", - "engines": { - "node": ">= 0.6" + "peerDependencies": { + "ajv": "^6.9.1" } }, - "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "node_modules/url-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/url-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "license": "MIT", "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "unpipe": "~1.0.0" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" }, "engines": { - "node": ">= 0.8" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "node_modules/use-memo-one": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", + "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "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==", + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.4.0" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "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==", + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10.12.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==", - "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/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" + "node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } }, - "node_modules/sax": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", - "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", - "license": "BlueOak-1.0.0", + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "license": "ISC", "engines": { - "node": ">=11.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 0.8" } }, - "node_modules/send": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.4.1", - "range-parser": "~1.2.1", - "statuses": "~2.0.2" + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">=18" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/serve-static": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", - "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "node_modules/wait-on": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-8.0.5.tgz", + "integrity": "sha512-J3WlS0txVHkhLRb2FsmRg3dkMTCV1+M6Xra3Ho7HzZDHpE7DCOnoSoCJsZotrmW3uRMhvIJGSKUKrh/MeF4iag==", "license": "MIT", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "~0.19.1" + "axios": "^1.12.1", + "joi": "^18.0.1", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "rxjs": "^7.8.2" + }, + "bin": { + "wait-on": "bin/wait-on" }, "engines": { - "node": ">= 0.8.0" + "node": ">=12.0.0" } }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/wasm-feature-detect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz", + "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==", + "license": "Apache-2.0" + }, + "node_modules/watchpack": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", "license": "MIT", "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" + "graceful-fs": "^4.1.2" }, "engines": { - "node": ">= 0.4" + "node": ">=10.13.0" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } }, - "node_modules/sha.js": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", - "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", - "license": "(MIT AND BSD-3-Clause)", + "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==", + "license": "Apache-2.0" + }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz", + "integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==", + "license": "Apache-2.0" + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/webpack": { + "version": "5.109.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.109.2.tgz", + "integrity": "sha512-U9/cvLzxObKNEZ9+TtdqrHM5/9z3lgl2c+c4BzbqGxFQvQvBAq87yql5A8pQ+rrMbS496MZJeF5enVBndIy2hw==", + "license": "MIT", "dependencies": { - "inherits": "^2.0.4", - "safe-buffer": "^5.2.1", - "to-buffer": "^1.2.0" + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.16.0", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.24.4", + "es-module-lexer": "^2.1.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "graceful-fs": "^4.2.11", + "mime-db": "^1.54.0", + "minimizer-webpack-plugin": "^5.6.1", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "watchpack": "^2.5.2", + "webpack-sources": "^3.5.1" }, "bin": { - "sha.js": "bin.js" + "webpack": "bin/webpack.js" }, "engines": { - "node": ">= 0.10" + "node": ">=10.13.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } } }, - "node_modules/side-channel": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", - "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "node_modules/webpack-bundle-analyzer": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4", - "side-channel-list": "^1.0.1", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "html-escaper": "^2.0.2", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" }, "engines": { - "node": ">= 0.4" + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-bundle-analyzer/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==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "node_modules/webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">= 0.4" + "node": ">=14.15.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } } }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">= 12.13.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "node_modules/webpack-dev-server": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.4", + "ws": "^8.13.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" }, "engines": { - "node": ">= 0.4" + "node": ">= 12.13.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true }, - { - "type": "consulting", - "url": "https://feross.org/support" + "webpack-cli": { + "optional": true } - ], - "license": "MIT" + } }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/webpack-dev-server/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "license": "MIT", "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/webpack-dev-server/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "node_modules/webpack-dev-server/node_modules/ipaddr.js": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.5.0.tgz", + "integrity": "sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 10" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/webpack-dev-server/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "license": "MIT", "dependencies": { - "safe-buffer": "~5.2.0" + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" } }, - "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==", + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=10.0.0" } }, - "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==", + "node_modules/webpack-merge/node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/strnum": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.1.tgz", - "integrity": "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], + "node_modules/webpack-merge/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "license": "MIT", "dependencies": { - "anynum": "^1.0.1" + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/tmp": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", - "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "node_modules/webpack-merge/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "license": "MIT", "engines": { - "node": ">=14.14" + "node": ">=0.10.0" } }, - "node_modules/tmp-promise": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", - "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "node_modules/webpack-merge/node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "license": "MIT", "dependencies": { - "tmp": "^0.2.0" + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" } }, - "node_modules/to-buffer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", - "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "node_modules/webpack-sources": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.1.tgz", + "integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz", + "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==", + "license": "Apache-2.0", "dependencies": { - "isarray": "^2.0.5", - "safe-buffer": "^5.2.1", - "typed-array-buffer": "^1.0.3" + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" }, "engines": { - "node": ">= 0.4" + "node": ">=0.8.0" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", "engines": { - "node": ">=0.6" + "node": ">=0.8.0" } }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "iconv-lite": "0.6.3" }, "engines": { - "node": ">= 0.6" + "node": ">=18" } }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "node_modules/whatwg-encoding/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==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=0.10.0" } }, - "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "license": "MIT" + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } }, - "node_modules/universal-github-app-jwt": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.2.0.tgz", - "integrity": "sha512-dncpMpnsKBk0eetwfN8D8OUHGfiDhhJ+mtsbMl+7PfW7mYjiH8LIcqRmYMtzYLgSh47HjfdBtrBwIQ/gizKR3g==", + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "license": "MIT", "dependencies": { - "@types/jsonwebtoken": "^9.0.0", - "jsonwebtoken": "^9.0.2" + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" } }, - "node_modules/universal-user-agent": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", - "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", - "license": "ISC" + "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==", + "license": "MIT" }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "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==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, "engines": { - "node": ">= 10.0.0" + "node": ">= 8" } }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, "engines": { - "node": ">= 0.4.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wasm-feature-detect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz", - "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==", - "license": "Apache-2.0" - }, "node_modules/which-typed-array": { "version": "1.1.22", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", @@ -2897,6 +23205,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "license": "MIT" + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -2920,6 +23243,19 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/ws": { "version": "8.21.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", @@ -2941,6 +23277,27 @@ } } }, + "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==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/xml-naming": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", @@ -2978,6 +23335,21 @@ "node": ">=4.0" } }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -2987,6 +23359,21 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -3014,6 +23401,63 @@ "node": ">=12" } }, + "node_modules/yarn": { + "version": "1.22.22", + "resolved": "https://registry.npmjs.org/yarn/-/yarn-1.22.22.tgz", + "integrity": "sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==", + "hasInstallScript": true, + "license": "BSD-2-Clause", + "bin": { + "yarn": "bin/yarn.js", + "yarnpkg": "bin/yarn.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, "node_modules/zstddec": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.2.0.tgz", diff --git a/.github/docs-playground-preview/package.json b/.github/docs-playground-preview/package.json index e2e7db936c1ce..0a94f575c3772 100644 --- a/.github/docs-playground-preview/package.json +++ b/.github/docs-playground-preview/package.json @@ -11,6 +11,9 @@ "test": "node --test ../scripts/docs-playground-preview/test/*.test.mjs" }, "dependencies": { - "@wp-playground/cli": "3.1.48" + "@wordpress/i18n": "6.24.0", + "@wordpress/scripts": "32.6.0", + "@wp-playground/cli": "3.1.48", + "yarn": "1.22.22" } } diff --git a/.github/scripts/docs-playground-preview/lib/config.mjs b/.github/scripts/docs-playground-preview/lib/config.mjs index 72cfd4c5f36e9..9ddac2016bcbc 100644 --- a/.github/scripts/docs-playground-preview/lib/config.mjs +++ b/.github/scripts/docs-playground-preview/lib/config.mjs @@ -49,7 +49,14 @@ export function validateDependencies( dependencies ) { ); assertObject( dependencies.toolchain, 'toolchain' ); assertObject( dependencies.playground, 'playground' ); - for ( const name of [ 'nodeVersion', 'npmVersion', 'composerVersion' ] ) { + for ( const name of [ + 'nodeVersion', + 'npmVersion', + 'composerVersion', + 'yarnVersion', + 'wpScriptsVersion', + 'wpI18nVersion', + ] ) { if ( ! VERSION.test( dependencies.toolchain[ name ] ) ) { throw new Error( `toolchain.${ name } must be an exact version.` ); } diff --git a/.github/scripts/docs-playground-preview/test/config.test.mjs b/.github/scripts/docs-playground-preview/test/config.test.mjs index 3eb3461c06c26..a9d3df95822fc 100644 --- a/.github/scripts/docs-playground-preview/test/config.test.mjs +++ b/.github/scripts/docs-playground-preview/test/config.test.mjs @@ -42,6 +42,18 @@ test( 'the checked-in dependency manifest is valid', async () => { packageManifest.dependencies[ '@wp-playground/cli' ], dependencies.playground.cliVersion ); + assert.equal( + packageManifest.dependencies.yarn, + dependencies.toolchain.yarnVersion + ); + assert.equal( + packageManifest.dependencies[ '@wordpress/scripts' ], + dependencies.toolchain.wpScriptsVersion + ); + assert.equal( + packageManifest.dependencies[ '@wordpress/i18n' ], + dependencies.toolchain.wpI18nVersion + ); } ); test( 'dependency repositories and commits are fixed', async () => { @@ -61,7 +73,14 @@ test( 'dependency repositories and commits are fixed', async () => { } ); test( 'toolchain versions are exact and required', async () => { - for ( const name of [ 'nodeVersion', 'npmVersion', 'composerVersion' ] ) { + for ( const name of [ + 'nodeVersion', + 'npmVersion', + 'composerVersion', + 'yarnVersion', + 'wpScriptsVersion', + 'wpI18nVersion', + ] ) { const dependencies = await manifest(); delete dependencies.toolchain[ name ]; assert.throws( From 62dc2576aa9c24c3943241d7573c92e70e4f317d Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 11:02:55 +0400 Subject: [PATCH 04/51] Build/Test Tools: Cache the Code Reference site base Rebuilding themes, plugins, parser dependencies, and the empty DevHub site on every preview wastes the expensive half of the job. Build that state once under the exact cache key and write the completion marker last. The untrimmed base was 98,884,490 bytes. Drop unused CJK preview fonts before packaging; the real cold build falls to 43,224,482 bytes and leaves room for the reference import without weakening the 100 MiB limit. --- .../docs-playground-preview/lib/archive.mjs | 55 ++++ .../docs-playground-preview/lib/base.mjs | 297 ++++++++++++++++++ .../php/base-policy.php | 40 +++ .../php/configure-base.php | 89 ++++++ .../test/base.test.mjs | 152 +++++++++ .gitignore | 1 + 6 files changed, 634 insertions(+) create mode 100644 .github/scripts/docs-playground-preview/lib/archive.mjs create mode 100644 .github/scripts/docs-playground-preview/lib/base.mjs create mode 100644 .github/scripts/docs-playground-preview/php/base-policy.php create mode 100644 .github/scripts/docs-playground-preview/php/configure-base.php create mode 100644 .github/scripts/docs-playground-preview/test/base.test.mjs diff --git a/.github/scripts/docs-playground-preview/lib/archive.mjs b/.github/scripts/docs-playground-preview/lib/archive.mjs new file mode 100644 index 0000000000000..eba012e219dd3 --- /dev/null +++ b/.github/scripts/docs-playground-preview/lib/archive.mjs @@ -0,0 +1,55 @@ +import { cp, mkdir, mkdtemp, rm } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; + +import { run } from './process.mjs'; + +const DEFAULT_EXCLUDES = [ '.git', '.github', 'node_modules', 'tests' ]; + +function isExcluded( relative, excludes ) { + return excludes.some( + ( excluded ) => + relative === excluded || relative.startsWith( `${ excluded }/` ) + ); +} + +export async function copyDirectory( source, destination, excludes = [] ) { + const root = path.resolve( source ); + await mkdir( destination, { recursive: true } ); + await cp( root, destination, { + recursive: true, + filter: ( filename ) => { + const relative = path.relative( root, filename ); + return ! relative || ! isExcluded( relative, excludes ); + }, + } ); +} + +export async function zipDirectory( + source, + output, + rootName, + runImplementation = run +) { + const temporary = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-zip-' ) ); + const target = rootName === '.' ? temporary : path.join( temporary, rootName ); + try { + await copyDirectory( source, target, DEFAULT_EXCLUDES ); + await rm( output, { force: true } ); + await runImplementation( 'zip', [ '-rq', path.resolve( output ), '.' ], { + cwd: temporary, + label: `package ${ rootName }`, + } ); + } finally { + await rm( temporary, { recursive: true, force: true } ); + } +} + +export async function downloadFile( url, output, runImplementation = run ) { + await mkdir( path.dirname( output ), { recursive: true } ); + await runImplementation( + 'curl', + [ '--fail', '--location', '--output', output, url ], + { label: `download ${ url }` } + ); +} diff --git a/.github/scripts/docs-playground-preview/lib/base.mjs b/.github/scripts/docs-playground-preview/lib/base.mjs new file mode 100644 index 0000000000000..12e776f82f0f4 --- /dev/null +++ b/.github/scripts/docs-playground-preview/lib/base.mjs @@ -0,0 +1,297 @@ +import { fileURLToPath } from 'node:url'; +import { + cp, + mkdir, + readFile, + rm, + writeFile, +} from 'node:fs/promises'; +import path from 'node:path'; + +import { copyDirectory, downloadFile, zipDirectory } from './archive.mjs'; +import { acquireRepositories, exists } from './files.mjs'; +import { run } from './process.mjs'; + +const LIBRARY_ROOT = path.dirname( fileURLToPath( import.meta.url ) ); +const SCRIPT_ROOT = path.dirname( LIBRARY_ROOT ); +const TOOLING_ROOT = path.resolve( LIBRARY_ROOT, '../../../docs-playground-preview' ); +const PHP_ROOT = path.join( SCRIPT_ROOT, 'php' ); + +function executable( name ) { + return path.join( + TOOLING_ROOT, + 'node_modules/.bin', + process.platform === 'win32' ? `${ name }.cmd` : name + ); +} + +export function dependencyBuildPlan( roots, composer ) { + const nodePath = path.join( TOOLING_ROOT, 'node_modules' ); + const wpScriptsEnvironment = { NODE_PATH: nodePath }; + return [ + { + command: 'php', + args: [ composer, 'install', '--no-interaction', '--no-dev', '--prefer-dist' ], + cwd: roots.phpdocParser, + label: 'install phpdoc-parser dependencies', + }, + { + command: executable( 'yarn' ), + args: [ 'install', '--frozen-lockfile', '--ignore-scripts' ], + cwd: roots.wporgParent2021, + label: 'install wporg-parent-2021 dependencies', + }, + { + command: executable( 'yarn' ), + args: [ 'build:theme' ], + cwd: roots.wporgParent2021, + label: 'build wporg-parent-2021', + }, + { + command: executable( 'wp-scripts' ), + args: [ 'build' ], + cwd: roots.wporgDeveloperTheme, + env: wpScriptsEnvironment, + label: 'build wporg-developer-2023', + }, + { + command: 'npm', + args: [ 'ci', '--ignore-scripts', '--no-audit', '--no-fund' ], + cwd: roots.wporgMuPlugins, + label: 'install wporg-mu-plugins dependencies', + }, + { + command: 'php', + args: [ composer, 'install', '--no-interaction', '--no-dev', '--prefer-dist' ], + cwd: roots.wporgMuPlugins, + label: 'install wporg-mu-plugins Composer dependencies', + }, + { + command: 'npm', + args: [ 'run', 'build' ], + cwd: roots.wporgMuPlugins, + label: 'build wporg-mu-plugins', + }, + { + command: 'php', + args: [ composer, 'config', 'allow-plugins.composer/installers', 'true' ], + cwd: roots.postsToPosts, + label: 'configure posts-to-posts installer', + }, + { + command: 'php', + args: [ composer, 'install', '--no-interaction', '--no-dev', '--prefer-dist' ], + cwd: roots.postsToPosts, + label: 'install posts-to-posts dependencies', + }, + { + command: executable( 'wp-scripts' ), + args: [ 'build' ], + cwd: roots.codeSyntaxBlock, + env: wpScriptsEnvironment, + label: 'build code-syntax-block', + }, + ]; +} + +function bundled( resourcePath ) { + return { resource: 'bundled', path: resourcePath }; +} + +export function createInvariantBaseBlueprint( inputs ) { + return { + $schema: inputs.dependencies.playground.blueprintSchema, + meta: { + title: 'WordPress Core Code Reference invariant base', + author: 'WordPress', + description: 'Pinned DevHub dependencies with an empty reference index.', + }, + preferredVersions: { + php: inputs.dependencies.playground.phpVersion, + wp: inputs.wordpress.version, + }, + landingPage: '/reference/', + login: false, + features: { networking: false }, + extraLibraries: [ 'wp-cli' ], + steps: [ + { + step: 'writeFile', + path: '/wordpress/wp-content/mu-plugins/000-docs-preview-build.php', + data: bundled( 'php/base-policy.php' ), + }, + { + step: 'unzip', + zipFile: bundled( 'bundles/wporg-mu-plugins.zip' ), + extractToPath: '/wordpress/wp-content/mu-plugins', + }, + { + step: 'installTheme', + themeData: bundled( 'bundles/wporg-parent-2021.zip' ), + ifAlreadyInstalled: 'overwrite', + }, + { + step: 'installTheme', + themeData: bundled( 'bundles/wporg-developer-2023.zip' ), + ifAlreadyInstalled: 'overwrite', + }, + ...[ + 'code-syntax-block', + 'posts-to-posts', + 'phpdoc-parser', + ].map( ( plugin ) => ( { + step: 'unzip', + zipFile: bundled( `bundles/${ plugin }.zip` ), + extractToPath: '/wordpress/wp-content/plugins', + } ) ), + { + step: 'writeFile', + path: '/tmp/docs-preview-configure-base.php', + data: bundled( 'php/configure-base.php' ), + }, + { + step: 'wp-cli', + command: 'wp eval-file /tmp/docs-preview-configure-base.php', + }, + ], + }; +} + +export async function prunePreviewFonts( muPlugins ) { + await rm( path.join( muPlugins, 'global-fonts/NotoSerif' ), { + recursive: true, + force: true, + } ); + const stylesheet = path.join( muPlugins, 'global-fonts/style.css' ); + const css = await readFile( stylesheet, 'utf8' ); + await writeFile( + stylesheet, + css + .split( '\n' ) + .filter( ( line ) => ! line.includes( '@import "./NotoSerif/' ) ) + .join( '\n' ) + ); +} + +async function ensureComposer( inputs, tools, runImplementation ) { + const version = inputs.dependencies.toolchain.composerVersion; + const composer = path.join( tools, `composer-${ version }.phar` ); + if ( ! ( await exists( composer ) ) ) { + await downloadFile( + `https://getcomposer.org/download/${ version }/composer.phar`, + composer, + runImplementation + ); + } + return composer; +} + +function repositoryRoots( upstreams, inputs ) { + const root = ( name ) => path.join( upstreams, name ); + const selected = ( name ) => + path.join( root( name ), inputs.dependencies.repositories[ name ].path ); + return { + phpdocParser: selected( 'phpdocParser' ), + wporgDeveloperTheme: selected( 'wporgDeveloper' ), + wporgParent2021: root( 'wporgParent2021' ), + wporgParentTheme: selected( 'wporgParent2021' ), + wporgMuPlugins: root( 'wporgMuPlugins' ), + wporgMuPluginsFiles: selected( 'wporgMuPlugins' ), + postsToPosts: selected( 'postsToPosts' ), + codeSyntaxBlock: selected( 'codeSyntaxBlock' ), + }; +} + +async function buildInvariantBase( inputs, options ) { + const runImplementation = options.runImplementation || run; + const work = path.join( inputs.cacheDirectory, 'work' ); + const upstreams = path.join( work, 'upstreams' ); + const bundles = path.join( work, 'bundles' ); + const tools = path.join( work, 'tools' ); + await mkdir( bundles, { recursive: true } ); + await acquireRepositories( + inputs.dependencies.repositories, + upstreams, + runImplementation + ); + const roots = repositoryRoots( upstreams, inputs ); + const composer = await ensureComposer( inputs, tools, runImplementation ); + for ( const task of dependencyBuildPlan( roots, composer ) ) { + await runImplementation( task.command, task.args, task ); + } + await prunePreviewFonts( roots.wporgMuPluginsFiles ); + + for ( const [ name, source, rootName ] of [ + [ 'wporg-parent-2021', roots.wporgParentTheme, 'wporg-parent-2021' ], + [ 'wporg-developer-2023', roots.wporgDeveloperTheme, 'wporg-developer-2023' ], + [ 'wporg-mu-plugins', roots.wporgMuPluginsFiles, '.' ], + [ 'posts-to-posts', roots.postsToPosts, 'posts-to-posts' ], + [ 'phpdoc-parser', roots.phpdocParser, 'phpdoc-parser' ], + [ 'code-syntax-block', roots.codeSyntaxBlock, 'code-syntax-block' ], + ] ) { + await zipDirectory( + source, + path.join( bundles, `${ name }.zip` ), + rootName, + runImplementation + ); + } + + await mkdir( path.join( work, 'php' ), { recursive: true } ); + for ( const script of [ 'base-policy.php', 'configure-base.php' ] ) { + await cp( path.join( PHP_ROOT, script ), path.join( work, 'php', script ) ); + } + const blueprint = path.join( work, 'base-blueprint.json' ); + await writeFile( + blueprint, + `${ JSON.stringify( createInvariantBaseBlueprint( inputs ), null, 2 ) }\n` + ); + await runImplementation( + options.playgroundCli || executable( 'wp-playground-cli' ), + [ + 'build-snapshot', + '--php', + inputs.dependencies.playground.phpVersion, + '--wp', + inputs.wordpress.version, + '--blueprint', + blueprint, + '--blueprint-may-read-adjacent-files', + '--outfile', + path.join( inputs.cacheDirectory, 'base.zip' ), + '--verbosity', + 'normal', + ], + { label: 'build invariant Playground base' } + ); + await copyDirectory( roots.phpdocParser, path.join( inputs.cacheDirectory, 'parser' ), [ '.git' ] ); + await rm( work, { recursive: true, force: true } ); +} + +export async function ensureInvariantBase( inputs, options = {} ) { + const markerFile = path.join( inputs.cacheDirectory, 'base.json' ); + if ( await exists( markerFile ) ) { + const marker = JSON.parse( await readFile( markerFile, 'utf8' ) ); + if ( + marker.cacheKey === inputs.cacheKey && + ( await exists( path.join( inputs.cacheDirectory, 'base.zip' ) ) ) && + ( await exists( + path.join( inputs.cacheDirectory, 'parser/generate-json-manually.php' ) + ) ) + ) { + return { ...marker, cacheHit: true }; + } + } + await rm( inputs.cacheDirectory, { recursive: true, force: true } ); + await mkdir( inputs.cacheDirectory, { recursive: true } ); + await ( options.buildImplementation || buildInvariantBase )( inputs, options ); + const marker = { + schemaVersion: 1, + cacheKey: inputs.cacheKey, + wordpress: inputs.wordpress, + dependencyDigest: inputs.cacheInputs.dependencyDigest, + harnessDigest: inputs.cacheInputs.harnessDigest, + }; + await writeFile( markerFile, `${ JSON.stringify( marker, null, 2 ) }\n` ); + return { ...marker, cacheHit: false }; +} diff --git a/.github/scripts/docs-playground-preview/php/base-policy.php b/.github/scripts/docs-playground-preview/php/base-policy.php new file mode 100644 index 0000000000000..aa8eb49b59d31 --- /dev/null +++ b/.github/scripts/docs-playground-preview/php/base-policy.php @@ -0,0 +1,40 @@ + time(), + 'checked' => array(), + 'no_update' => array(), + 'response' => array(), + 'translations' => array(), + 'updates' => array(), + ); + } + ); +} diff --git a/.github/scripts/docs-playground-preview/php/configure-base.php b/.github/scripts/docs-playground-preview/php/configure-base.php new file mode 100644 index 0000000000000..6b866c0b3ca72 --- /dev/null +++ b/.github/scripts/docs-playground-preview/php/configure-base.php @@ -0,0 +1,89 @@ +get_error_message() ); + } +} + +sort( $active_plugins ); +update_option( 'active_plugins', $active_plugins ); +switch_theme( 'wporg-developer-2023' ); +update_option( 'permalink_structure', '/%year%/%monthnum%/%postname%/' ); + +$ensure_page = static function( $slug, $title ) { + $page = get_page_by_path( $slug, OBJECT, 'page' ); + if ( $page ) { + return $page->ID; + } + $result = wp_insert_post( + array( + 'post_type' => 'page', + 'post_title' => $title, + 'post_status' => 'publish', + 'post_name' => $slug, + ), + true + ); + if ( is_wp_error( $result ) ) { + throw new RuntimeException( $result->get_error_message() ); + } + return $result; +}; + +update_option( 'show_on_front', 'page' ); +update_option( 'page_on_front', $ensure_page( 'home', 'Home' ) ); +$ensure_page( 'reference', 'Reference' ); + +$navigation = ''; +$existing = get_page_by_path( 'reference-api-menu', OBJECT, 'wp_navigation' ); +$nav_args = array( + 'post_title' => 'Reference API Menu', + 'post_name' => 'reference-api-menu', + 'post_type' => 'wp_navigation', + 'post_status' => 'publish', + 'post_content' => $navigation, +); +if ( $existing ) { + $nav_args['ID'] = $existing->ID; + $result = wp_update_post( $nav_args, true ); +} else { + $nav_args['import_id'] = 148843; + $result = wp_insert_post( $nav_args, true ); +} +if ( is_wp_error( $result ) ) { + throw new RuntimeException( $result->get_error_message() ); +} + +global $wpdb; +$reference_types = array( + 'wp-parser-class', + 'wp-parser-function', + 'wp-parser-hook', + 'wp-parser-method', + 'wp-parser-source-file', +); +$placeholders = implode( ',', array_fill( 0, count( $reference_types ), '%s' ) ); +$count = $wpdb->get_var( + $wpdb->prepare( + "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type IN ({$placeholders})", + $reference_types + ) +); +if ( 0 !== (int) $count ) { + throw new RuntimeException( 'Invariant base contains generated reference posts.' ); +} + +flush_rewrite_rules( false ); diff --git a/.github/scripts/docs-playground-preview/test/base.test.mjs b/.github/scripts/docs-playground-preview/test/base.test.mjs new file mode 100644 index 0000000000000..faad291f06a81 --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/base.test.mjs @@ -0,0 +1,152 @@ +import assert from 'node:assert/strict'; +import { mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { test } from 'node:test'; + +import { + createInvariantBaseBlueprint, + dependencyBuildPlan, + ensureInvariantBase, + prunePreviewFonts, +} from '../lib/base.mjs'; +import { zipDirectory } from '../lib/archive.mjs'; +import { run } from '../lib/process.mjs'; + +function inputs( cacheDirectory ) { + return { + cacheKey: 'docs-preview-base-v1-' + 'a'.repeat( 64 ), + cacheDirectory, + cacheInputs: { + dependencyDigest: 'b'.repeat( 64 ), + harnessDigest: 'c'.repeat( 64 ), + }, + wordpress: { + channel: 'beta', + version: '7.2-beta1', + downloadUrl: + 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', + }, + dependencies: { + playground: { + blueprintSchema: 'https://playground.wordpress.net/blueprint-schema.json', + phpVersion: '8.4', + }, + }, + }; +} + +test( 'the invariant Blueprint installs dependencies without importing source', () => { + const blueprint = createInvariantBaseBlueprint( inputs( '/cache' ) ); + assert.deepEqual( blueprint.preferredVersions, { + php: '8.4', + wp: '7.2-beta1', + } ); + assert.deepEqual( blueprint.features, { networking: false } ); + assert.equal( blueprint.login, false ); + assert.equal( + blueprint.steps.some( ( step ) => /import|reference\.json/.test( JSON.stringify( step ) ) ), + false + ); + assert.deepEqual( + blueprint.steps + .filter( ( step ) => step.step === 'installTheme' ) + .map( ( step ) => step.themeData.path ), + [ + 'bundles/wporg-parent-2021.zip', + 'bundles/wporg-developer-2023.zip', + ] + ); +} ); + +test( 'dependency build plan uses pinned harness tools and upstream locks', () => { + const roots = { + phpdocParser: '/repos/parser', + wporgParent2021: '/repos/parent', + wporgDeveloperTheme: '/repos/developer/theme', + wporgMuPlugins: '/repos/mu', + postsToPosts: '/repos/p2p', + codeSyntaxBlock: '/repos/syntax', + }; + const plan = dependencyBuildPlan( roots, '/tools/composer.phar' ); + assert.equal( plan.length, 10 ); + assert.deepEqual( plan[ 0 ].args.slice( 0, 2 ), [ + '/tools/composer.phar', + 'install', + ] ); + assert.match( plan[ 1 ].command, /node_modules\/\.bin\/yarn$/ ); + assert.deepEqual( plan[ 1 ].args, [ + 'install', + '--frozen-lockfile', + '--ignore-scripts', + ] ); + assert.match( plan[ 3 ].command, /node_modules\/\.bin\/wp-scripts$/ ); + assert.deepEqual( plan[ 4 ].args.slice( 0, 2 ), [ 'ci', '--ignore-scripts' ] ); +} ); + +test( 'the invariant base marker is written last and enables exact reuse', async () => { + const cache = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-base-' ) ); + const resolved = inputs( path.join( cache, 'entry' ) ); + let builds = 0; + const buildImplementation = async ( current ) => { + builds++; + await writeFile( path.join( current.cacheDirectory, 'base.zip' ), 'base' ); + await mkdir( path.join( current.cacheDirectory, 'parser' ) ); + await writeFile( + path.join( current.cacheDirectory, 'parser/generate-json-manually.php' ), + ' { + const policy = await readFile( + new URL( '../php/base-policy.php', import.meta.url ), + 'utf8' + ); + assert.doesNotMatch( policy, /DISALLOW_FILE_MODS|AUTOMATIC_UPDATER_DISABLED/ ); +} ); + +test( 'dependency bundles have the requested install root and omit build tools', async () => { + const temporary = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-bundle-' ) ); + const source = path.join( temporary, 'source' ); + await mkdir( path.join( source, 'node_modules' ), { recursive: true } ); + await writeFile( path.join( source, 'plugin.php' ), ' { + const root = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-fonts-' ) ); + const fonts = path.join( root, 'global-fonts' ); + await mkdir( path.join( fonts, 'NotoSerif' ), { recursive: true } ); + await writeFile( path.join( fonts, 'NotoSerif/font.woff2' ), 'large' ); + await writeFile( + path.join( fonts, 'style.css' ), + '@import "./NotoSerif/NotoSerifJP/style.css";\nbody { color: black; }\n' + ); + await prunePreviewFonts( root ); + await assert.rejects( readFile( path.join( fonts, 'NotoSerif/font.woff2' ) ) ); + assert.equal( + await readFile( path.join( fonts, 'style.css' ), 'utf8' ), + 'body { color: black; }\n' + ); +} ); diff --git a/.gitignore b/.gitignore index 15876fa47fee8..98bd3613b9f5a 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ wp-tests-config.php /tests/performance/**/*.test.results.json /src/.wp-tests-version /node_modules +/.github/docs-playground-preview/node_modules /npm-debug.log /build /gutenberg From b97d59fb235a3797f244643985ca5931dd2e141d Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 11:09:26 +0400 Subject: [PATCH 05/51] Build/Test Tools: Generate Code Reference parser data The preview must parse the exact Core checkout without loading it. Stage only eligible PHP, run the pinned parser, and normalize source roots for the later import. Current trunk yields 1,318 files, 764 classes, 7,316 methods, 4,386 functions, 1,031 hooks, and 2,397 filters. Fixed low floors catch collapse without pretending counts are stable. Empty output is advisory metadata, not a publisher candidate. --- .../docs-playground-preview/dependencies.json | 2 +- .../docs-playground-preview/lib/parser.mjs | 99 +++++++++++++ .../test/parser.test.mjs | 130 ++++++++++++++++++ 3 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 .github/scripts/docs-playground-preview/lib/parser.mjs create mode 100644 .github/scripts/docs-playground-preview/test/parser.test.mjs diff --git a/.github/docs-playground-preview/dependencies.json b/.github/docs-playground-preview/dependencies.json index b8ffebe75e70d..5745399688474 100644 --- a/.github/docs-playground-preview/dependencies.json +++ b/.github/docs-playground-preview/dependencies.json @@ -56,7 +56,7 @@ "minimumSymbols": { "classes": 400, "methods": 3000, - "functions": 1200, + "functions": 1800, "hooks": 600, "filters": 1000 }, diff --git a/.github/scripts/docs-playground-preview/lib/parser.mjs b/.github/scripts/docs-playground-preview/lib/parser.mjs new file mode 100644 index 0000000000000..fc9fccef53b26 --- /dev/null +++ b/.github/scripts/docs-playground-preview/lib/parser.mjs @@ -0,0 +1,99 @@ +import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import path from 'node:path'; + +import { stageCorePhp } from './files.mjs'; +import { run } from './process.mjs'; + +const HOOK_TYPE = /^(?:action|filter)(?:_(?:deprecated|reference))?$/; + +function hooksIn( value ) { + return Array.isArray( value?.hooks ) ? value.hooks : []; +} + +function countHooks( hooks, counts ) { + for ( const hook of hooks ) { + if ( ! hook || typeof hook !== 'object' || ! HOOK_TYPE.test( hook.type ) ) { + throw new Error( 'Parser JSON contains a hook with an invalid type.' ); + } + if ( hook.type.startsWith( 'action' ) ) { + counts.hooks++; + } else { + counts.filters++; + } + } +} + +export function inspectParserRecords( records, minimumSymbols ) { + if ( ! Array.isArray( records ) ) { + throw new Error( 'Parser JSON must be an array.' ); + } + const counts = { + classes: 0, + methods: 0, + functions: 0, + hooks: 0, + filters: 0, + }; + for ( const record of records ) { + if ( ! record || typeof record !== 'object' || typeof record.path !== 'string' ) { + throw new Error( 'Parser JSON contains an invalid file record.' ); + } + if ( + record.path.startsWith( 'wp-content/plugins/' ) || + record.path.startsWith( 'wp-content/themes/' ) + ) { + throw new Error( `Parser JSON contains excluded source: ${ record.path }.` ); + } + const functions = Array.isArray( record.functions ) ? record.functions : []; + const classes = Array.isArray( record.classes ) ? record.classes : []; + counts.functions += functions.length; + counts.classes += classes.length; + for ( const item of functions ) { + countHooks( hooksIn( item ), counts ); + } + for ( const item of classes ) { + const methods = Array.isArray( item?.methods ) ? item.methods : []; + counts.methods += methods.length; + for ( const method of methods ) { + countHooks( hooksIn( method ), counts ); + } + } + countHooks( hooksIn( record ), counts ); + record.root = '/tmp/docs-preview-source'; + } + const failures = records.length === 0 ? [ 'Parser produced no file records.' ] : []; + failures.push( ...Object.entries( minimumSymbols ) + .filter( ( [ type, minimum ] ) => counts[ type ] < minimum ) + .map( + ( [ type, minimum ] ) => + `Parser produced ${ counts[ type ] } ${ type }; expected at least ${ minimum }.` + ) ); + return { records: records.length, counts, failures }; +} + +export async function generateParserJson( options ) { + const runImplementation = options.runImplementation || run; + const staged = await stageCorePhp( options.source, options.stagedSource ); + await mkdir( path.dirname( options.output ), { recursive: true } ); + await runImplementation( + 'php', + [ + '-d', + 'memory_limit=4G', + path.join( options.parser, 'generate-json-manually.php' ), + '-d', + options.stagedSource, + '-o', + options.output, + ], + { + capture: Boolean( options.logFile ), + logFile: options.logFile, + label: 'generate Code Reference parser JSON', + } + ); + const records = JSON.parse( await readFile( options.output, 'utf8' ) ); + const inspection = inspectParserRecords( records, options.minimumSymbols ); + await writeFile( options.output, `${ JSON.stringify( records ) }\n` ); + return { ...inspection, sourceFiles: staged.files }; +} diff --git a/.github/scripts/docs-playground-preview/test/parser.test.mjs b/.github/scripts/docs-playground-preview/test/parser.test.mjs new file mode 100644 index 0000000000000..187f8a9f8160c --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/parser.test.mjs @@ -0,0 +1,130 @@ +import assert from 'node:assert/strict'; +import { mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { test } from 'node:test'; + +import { + generateParserJson, + inspectParserRecords, +} from '../lib/parser.mjs'; + +function records() { + return [ + { + path: 'wp-includes/example.php', + functions: [ + { + name: 'example', + hooks: [ { type: 'filter' }, { type: 'action_deprecated' } ], + }, + ], + classes: [ + { + name: 'Example', + methods: [ + { name: 'first', hooks: [ { type: 'filter_reference' } ] }, + { name: 'second', hooks: [] }, + ], + }, + ], + hooks: [ { type: 'action' } ], + }, + ]; +} + +test( 'parser inspection counts every supported reference type', () => { + const parsed = records(); + const result = inspectParserRecords( parsed, { + classes: 1, + methods: 2, + functions: 1, + hooks: 2, + filters: 2, + } ); + assert.deepEqual( result, { + records: 1, + counts: { + classes: 1, + methods: 2, + functions: 1, + hooks: 2, + filters: 2, + }, + failures: [], + } ); + assert.equal( parsed[ 0 ].root, '/tmp/docs-preview-source' ); +} ); + +test( 'sanity floors report catastrophic undercounts by symbol type', () => { + const result = inspectParserRecords( records(), { + classes: 2, + methods: 3, + functions: 2, + hooks: 3, + filters: 3, + } ); + assert.equal( result.failures.length, 5 ); + assert.match( result.failures[ 0 ], /1 classes; expected at least 2/ ); +} ); + +test( 'an empty parser array is an advisory validation failure', () => { + const result = inspectParserRecords( [], { + classes: 1, + methods: 1, + functions: 1, + hooks: 1, + filters: 1, + } ); + assert.deepEqual( result.counts, { + classes: 0, + methods: 0, + functions: 0, + hooks: 0, + filters: 0, + } ); + assert.equal( result.failures.length, 6 ); + assert.equal( result.failures[ 0 ], 'Parser produced no file records.' ); +} ); + +test( 'parser inspection rejects excluded bundled extension source', () => { + const parsed = records(); + parsed[ 0 ].path = 'wp-content/plugins/hello.php'; + assert.throws( + () => inspectParserRecords( parsed, {} ), + /contains excluded source/ + ); +} ); + +test( 'generateParserJson stages PHP, invokes the pinned parser, and normalizes output', async () => { + const temporary = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-parser-' ) ); + const source = path.join( temporary, 'wordpress/src' ); + await mkdir( path.join( source, 'wp-includes' ), { recursive: true } ); + await writeFile( path.join( source, 'wp-includes/version.php' ), ' { + invocation = { command, args }; + await writeFile( output, JSON.stringify( records() ) ); + }; + const result = await generateParserJson( { + source: path.dirname( source ), + stagedSource: path.join( temporary, 'staged' ), + parser: '/cache/parser', + output, + minimumSymbols: { + classes: 1, + methods: 2, + functions: 1, + hooks: 2, + filters: 2, + }, + runImplementation, + } ); + assert.equal( result.sourceFiles, 2 ); + assert.equal( invocation.command, 'php' ); + assert.equal( invocation.args.at( -1 ), output ); + const normalized = JSON.parse( await readFile( output, 'utf8' ) ); + assert.equal( normalized[ 0 ].root, '/tmp/docs-preview-source' ); +} ); From 173b818807f4e037f7db944f53197f871ca618c7 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 11:38:37 +0400 Subject: [PATCH 06/51] Build/Test Tools: Package Code Reference snapshots The invariant base is not a runnable reference. Restore it, import every parser record, cache source, remove the parser, and only then lock the runtime. An oversized result must never reach publication. --- .../docs-playground-preview/lib/final.mjs | 122 +++++++++++++++++ .../php/complete-import.php | 48 +++++++ .../test/final.test.mjs | 126 ++++++++++++++++++ 3 files changed, 296 insertions(+) create mode 100644 .github/scripts/docs-playground-preview/lib/final.mjs create mode 100644 .github/scripts/docs-playground-preview/php/complete-import.php create mode 100644 .github/scripts/docs-playground-preview/test/final.test.mjs diff --git a/.github/scripts/docs-playground-preview/lib/final.mjs b/.github/scripts/docs-playground-preview/lib/final.mjs new file mode 100644 index 0000000000000..53950b94c54ad --- /dev/null +++ b/.github/scripts/docs-playground-preview/lib/final.mjs @@ -0,0 +1,122 @@ +import { copyFile, mkdir, rm, stat, writeFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; + +import { sha256File } from './files.mjs'; +import { run } from './process.mjs'; + +const LIBRARY_ROOT = path.dirname( fileURLToPath( import.meta.url ) ); +const PHP_ROOT = path.resolve( LIBRARY_ROOT, '../php' ); + +function bundled( resourcePath ) { + return { resource: 'bundled', path: resourcePath }; +} + +export function createFinalBlueprint( inputs ) { + return { + $schema: inputs.dependencies.playground.blueprintSchema, + meta: { + title: 'WordPress Core Code Reference preview', + author: 'WordPress', + description: 'Complete Core Code Reference imported into the cached site.', + }, + preferredVersions: { + php: inputs.dependencies.playground.phpVersion, + wp: inputs.wordpress.version, + }, + landingPage: '/reference/', + login: false, + features: { networking: false }, + extraLibraries: [ 'wp-cli' ], + steps: [ + { + step: 'unzip', + zipFile: bundled( 'base.zip' ), + extractToPath: '/', + }, + { + step: 'writeFile', + path: '/tmp/reference.json', + data: bundled( 'reference.json' ), + }, + { + step: 'wp-cli', + command: 'wp parser import /tmp/reference.json --quick --user=1', + }, + { + step: 'writeFile', + path: '/tmp/docs-preview-complete-import.php', + data: bundled( 'complete-import.php' ), + }, + { + step: 'wp-cli', + command: 'wp eval-file /tmp/docs-preview-complete-import.php', + }, + { + step: 'rmdir', + path: '/wordpress/wp-content/plugins/phpdoc-parser', + }, + { + step: 'defineWpConfigConsts', + method: 'rewrite-wp-config', + consts: { + DISABLE_WP_CRON: true, + AUTOMATIC_UPDATER_DISABLED: true, + WP_AUTO_UPDATE_CORE: false, + DISALLOW_FILE_MODS: true, + }, + }, + ], + }; +} + +export async function packageFinalSnapshot( inputs, options ) { + const runImplementation = options.runImplementation || run; + const work = path.resolve( options.workDirectory ); + const output = path.resolve( options.output ); + await rm( work, { recursive: true, force: true } ); + await mkdir( work, { recursive: true } ); + await mkdir( path.dirname( output ), { recursive: true } ); + await copyFile( path.join( inputs.cacheDirectory, 'base.zip' ), path.join( work, 'base.zip' ) ); + await copyFile( options.referenceJson, path.join( work, 'reference.json' ) ); + await copyFile( + path.join( PHP_ROOT, 'complete-import.php' ), + path.join( work, 'complete-import.php' ) + ); + const blueprint = path.join( work, 'final-blueprint.json' ); + await writeFile( + blueprint, + `${ JSON.stringify( createFinalBlueprint( inputs ), null, 2 ) }\n` + ); + await runImplementation( + options.playgroundCli, + [ + 'build-snapshot', + '--php', + inputs.dependencies.playground.phpVersion, + '--wp', + inputs.wordpress.version, + '--blueprint', + blueprint, + '--blueprint-may-read-adjacent-files', + '--mount', + `${ path.resolve( options.stagedSource ) }:/tmp/docs-preview-source`, + '--outfile', + output, + '--verbosity', + 'normal', + ], + { label: 'build final Code Reference snapshot' } + ); + const snapshot = await stat( output ); + if ( snapshot.size > inputs.dependencies.limits.snapshotBytes ) { + throw new Error( + `Snapshot exceeds 100 MiB: ${ snapshot.size } > ${ inputs.dependencies.limits.snapshotBytes }.` + ); + } + return { + filename: path.basename( output ), + bytes: snapshot.size, + sha256: await sha256File( output ), + }; +} diff --git a/.github/scripts/docs-playground-preview/php/complete-import.php b/.github/scripts/docs-playground-preview/php/complete-import.php new file mode 100644 index 0000000000000..9839af6059f13 --- /dev/null +++ b/.github/scripts/docs-playground-preview/php/complete-import.php @@ -0,0 +1,48 @@ + 'wp-parser-class', + 'methods' => 'wp-parser-method', + 'functions' => 'wp-parser-function', + 'hooks' => 'wp-parser-hook', +); +$counts = array(); +foreach ( $post_types as $name => $post_type ) { + $counts[ $name ] = (int) wp_count_posts( $post_type )->publish; +} + +$marker = array( + 'schemaVersion' => 1, + 'stage' => 'complete-import', + 'counts' => $counts, +); +$written = file_put_contents( + WP_CONTENT_DIR . '/docs-preview-import.json', + wp_json_encode( $marker, JSON_PRETTY_PRINT ) . "\n" +); +if ( false === $written ) { + throw new RuntimeException( 'Unable to record the completed import.' ); +} + +flush_rewrite_rules( false ); diff --git a/.github/scripts/docs-playground-preview/test/final.test.mjs b/.github/scripts/docs-playground-preview/test/final.test.mjs new file mode 100644 index 0000000000000..9e4d43c322b39 --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/final.test.mjs @@ -0,0 +1,126 @@ +import assert from 'node:assert/strict'; +import { + mkdir, + mkdtemp, + open, + readFile, + writeFile, +} from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { test } from 'node:test'; + +import { + createFinalBlueprint, + packageFinalSnapshot, +} from '../lib/final.mjs'; + +function inputs( cacheDirectory, snapshotBytes = 1024 ) { + return { + cacheDirectory, + wordpress: { version: '7.2-beta1' }, + dependencies: { + playground: { + blueprintSchema: 'https://playground.wordpress.net/blueprint-schema.json', + phpVersion: '8.4', + }, + limits: { snapshotBytes }, + }, + }; +} + +test( 'the final Blueprint restores the base before the complete import', () => { + const blueprint = createFinalBlueprint( inputs( '/cache' ) ); + assert.equal( blueprint.landingPage, '/reference/' ); + assert.equal( blueprint.login, false ); + assert.deepEqual( blueprint.features, { networking: false } ); + assert.deepEqual( + blueprint.steps.map( ( step ) => step.step ), + [ + 'unzip', + 'writeFile', + 'wp-cli', + 'writeFile', + 'wp-cli', + 'rmdir', + 'defineWpConfigConsts', + ] + ); + assert.equal( blueprint.steps[ 0 ].extractToPath, '/' ); + assert.match( blueprint.steps[ 2 ].command, /parser import.+--quick/ ); + assert.equal( + blueprint.steps.at( -2 ).path, + '/wordpress/wp-content/plugins/phpdoc-parser' + ); + assert.deepEqual( blueprint.steps.at( -1 ), { + step: 'defineWpConfigConsts', + method: 'rewrite-wp-config', + consts: { + DISABLE_WP_CRON: true, + AUTOMATIC_UPDATER_DISABLED: true, + WP_AUTO_UPDATE_CORE: false, + DISALLOW_FILE_MODS: true, + }, + } ); +} ); + +async function fixture( snapshotBytes = 1024 ) { + const root = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-final-' ) ); + const cache = path.join( root, 'cache' ); + await mkdir( cache ); + await writeFile( path.join( cache, 'base.zip' ), 'base' ); + const referenceJson = path.join( root, 'reference.json' ); + await writeFile( referenceJson, '[]' ); + return { + root, + resolved: inputs( cache, snapshotBytes ), + referenceJson, + output: path.join( root, 'output/snapshot.zip' ), + }; +} + +test( 'final packaging returns publisher metadata for a bounded snapshot', async () => { + const current = await fixture(); + let invocation; + const runImplementation = async ( command, args ) => { + invocation = { command, args }; + await writeFile( current.output, 'snapshot' ); + }; + const snapshot = await packageFinalSnapshot( current.resolved, { + workDirectory: path.join( current.root, 'work' ), + output: current.output, + referenceJson: current.referenceJson, + stagedSource: '/source', + playgroundCli: '/tools/wp-playground-cli', + runImplementation, + } ); + assert.equal( snapshot.filename, 'snapshot.zip' ); + assert.equal( snapshot.bytes, 8 ); + assert.match( snapshot.sha256, /^[0-9a-f]{64}$/ ); + assert.equal( invocation.command, '/tools/wp-playground-cli' ); + assert.equal( invocation.args[ invocation.args.indexOf( '--mount' ) + 1 ], '/source:/tmp/docs-preview-source' ); + const blueprint = JSON.parse( + await readFile( path.join( current.root, 'work/final-blueprint.json' ), 'utf8' ) + ); + assert.equal( blueprint.steps[ 0 ].zipFile.path, 'base.zip' ); +} ); + +test( 'the 100 MiB snapshot boundary always fails closed', async () => { + const current = await fixture( 8 ); + const runImplementation = async () => { + const output = await open( current.output, 'w' ); + await output.truncate( 9 ); + await output.close(); + }; + await assert.rejects( + packageFinalSnapshot( current.resolved, { + workDirectory: path.join( current.root, 'work' ), + output: current.output, + referenceJson: current.referenceJson, + stagedSource: '/source', + playgroundCli: '/tools/wp-playground-cli', + runImplementation, + } ), + /exceeds 100 MiB/ + ); +} ); From 2a8dc89ad4dcf24e64820cb33ebab89a89950f95 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 11:50:55 +0400 Subject: [PATCH 07/51] Build/Test Tools: Add Code Reference runtime policy Runtime policy cannot live only in build metadata. Install it after import, show the exact source and run on every page, and expose the final state for behavioral validation. Block WordPress HTTP requests so the preview cannot quietly depend on the network. --- .../docs-playground-preview/lib/final.mjs | 30 ++++- .../docs-playground-preview/lib/runtime.mjs | 127 ++++++++++++++++++ .../test/final.test.mjs | 49 ++++--- .../test/runtime.test.mjs | 51 +++++++ 4 files changed, 236 insertions(+), 21 deletions(-) create mode 100644 .github/scripts/docs-playground-preview/lib/runtime.mjs create mode 100644 .github/scripts/docs-playground-preview/test/runtime.test.mjs diff --git a/.github/scripts/docs-playground-preview/lib/final.mjs b/.github/scripts/docs-playground-preview/lib/final.mjs index 53950b94c54ad..059e64ca69132 100644 --- a/.github/scripts/docs-playground-preview/lib/final.mjs +++ b/.github/scripts/docs-playground-preview/lib/final.mjs @@ -4,6 +4,7 @@ import path from 'node:path'; import { sha256File } from './files.mjs'; import { run } from './process.mjs'; +import { renderRuntimePlugin } from './runtime.mjs'; const LIBRARY_ROOT = path.dirname( fileURLToPath( import.meta.url ) ); const PHP_ROOT = path.resolve( LIBRARY_ROOT, '../php' ); @@ -18,7 +19,8 @@ export function createFinalBlueprint( inputs ) { meta: { title: 'WordPress Core Code Reference preview', author: 'WordPress', - description: 'Complete Core Code Reference imported into the cached site.', + description: + 'Complete Core Code Reference imported into the cached site.', }, preferredVersions: { php: inputs.dependencies.playground.phpVersion, @@ -41,7 +43,8 @@ export function createFinalBlueprint( inputs ) { }, { step: 'wp-cli', - command: 'wp parser import /tmp/reference.json --quick --user=1', + command: + 'wp parser import /tmp/reference.json --quick --user=1', }, { step: 'writeFile', @@ -56,6 +59,11 @@ export function createFinalBlueprint( inputs ) { step: 'rmdir', path: '/wordpress/wp-content/plugins/phpdoc-parser', }, + { + step: 'writeFile', + path: '/wordpress/wp-content/mu-plugins/001-docs-preview-runtime.php', + data: bundled( 'runtime.php' ), + }, { step: 'defineWpConfigConsts', method: 'rewrite-wp-config', @@ -77,12 +85,22 @@ export async function packageFinalSnapshot( inputs, options ) { await rm( work, { recursive: true, force: true } ); await mkdir( work, { recursive: true } ); await mkdir( path.dirname( output ), { recursive: true } ); - await copyFile( path.join( inputs.cacheDirectory, 'base.zip' ), path.join( work, 'base.zip' ) ); - await copyFile( options.referenceJson, path.join( work, 'reference.json' ) ); + await copyFile( + path.join( inputs.cacheDirectory, 'base.zip' ), + path.join( work, 'base.zip' ) + ); + await copyFile( + options.referenceJson, + path.join( work, 'reference.json' ) + ); await copyFile( path.join( PHP_ROOT, 'complete-import.php' ), path.join( work, 'complete-import.php' ) ); + await writeFile( + path.join( work, 'runtime.php' ), + renderRuntimePlugin( options.provenance ) + ); const blueprint = path.join( work, 'final-blueprint.json' ); await writeFile( blueprint, @@ -100,7 +118,9 @@ export async function packageFinalSnapshot( inputs, options ) { blueprint, '--blueprint-may-read-adjacent-files', '--mount', - `${ path.resolve( options.stagedSource ) }:/tmp/docs-preview-source`, + `${ path.resolve( + options.stagedSource + ) }:/tmp/docs-preview-source`, '--outfile', output, '--verbosity', diff --git a/.github/scripts/docs-playground-preview/lib/runtime.mjs b/.github/scripts/docs-playground-preview/lib/runtime.mjs new file mode 100644 index 0000000000000..a37701e0e3e67 --- /dev/null +++ b/.github/scripts/docs-playground-preview/lib/runtime.mjs @@ -0,0 +1,127 @@ +const FULL_COMMIT = /^[0-9a-f]{40}$/; +const REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/; +const RUN_URL = + /^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/actions\/runs\/\d+$/; + +function phpString( value ) { + return JSON.stringify( value ); +} + +export function validateProvenance( provenance ) { + if ( ! REPOSITORY.test( provenance?.sourceRepository || '' ) ) { + throw new Error( 'Preview provenance repository is invalid.' ); + } + if ( ! FULL_COMMIT.test( provenance.sourceSha || '' ) ) { + throw new Error( 'Preview provenance commit is invalid.' ); + } + if ( + Number.isNaN( Date.parse( provenance.generationTimestamp ) ) || + ! provenance.generationTimestamp.endsWith( 'Z' ) + ) { + throw new Error( 'Preview provenance timestamp must be UTC.' ); + } + if ( + provenance.runUrl !== null && + ! RUN_URL.test( provenance.runUrl || '' ) + ) { + throw new Error( 'Preview provenance run URL is invalid.' ); + } + return provenance; +} + +export function renderRuntimePlugin( candidate ) { + const provenance = validateProvenance( candidate ); + const runUrl = + provenance.runUrl === null ? 'null' : phpString( provenance.runUrl ); + return ` ${ phpString( provenance.sourceRepository ) }, + 'sourceSha' => ${ phpString( provenance.sourceSha ) }, + 'generationTimestamp' => ${ phpString( provenance.generationTimestamp ) }, + 'runUrl' => ${ runUrl }, +); + +add_filter( + 'pre_http_request', + static function() { + return new WP_Error( + 'docs_preview_network_disabled', + 'Outbound networking is disabled in the Code Reference preview.' + ); + }, + PHP_INT_MAX +); + +$wporg_docs_preview_banner_rendered = false; +$wporg_docs_preview_render_banner = static function() use ( &$wporg_docs_preview_banner_rendered, $wporg_docs_preview_provenance ) { + if ( $wporg_docs_preview_banner_rendered || is_admin() ) { + return; + } + $wporg_docs_preview_banner_rendered = true; + $commit_url = sprintf( + 'https://github.com/%s/commit/%s', + $wporg_docs_preview_provenance['sourceRepository'], + $wporg_docs_preview_provenance['sourceSha'] + ); + ?> + + 'GET', + 'permission_callback' => '__return_true', + 'callback' => static function() use ( $wporg_docs_preview_provenance ) { + $marker_file = WP_CONTENT_DIR . '/docs-preview-import.json'; + $marker = file_exists( $marker_file ) + ? json_decode( file_get_contents( $marker_file ), true ) + : null; + $network = wp_remote_get( 'https://api.wordpress.org/' ); + return array( + 'provenance' => $wporg_docs_preview_provenance, + 'import' => $marker, + 'outboundNetworkDisabled' => is_wp_error( $network ) && 'docs_preview_network_disabled' === $network->get_error_code(), + 'constants' => array( + 'DISABLE_WP_CRON' => defined( 'DISABLE_WP_CRON' ) && true === DISABLE_WP_CRON, + 'AUTOMATIC_UPDATER_DISABLED' => defined( 'AUTOMATIC_UPDATER_DISABLED' ) && true === AUTOMATIC_UPDATER_DISABLED, + 'WP_AUTO_UPDATE_CORE' => defined( 'WP_AUTO_UPDATE_CORE' ) && false === WP_AUTO_UPDATE_CORE, + 'DISALLOW_FILE_MODS' => defined( 'DISALLOW_FILE_MODS' ) && true === DISALLOW_FILE_MODS, + ), + ); + }, + ) + ); + } +); +`; +} diff --git a/.github/scripts/docs-playground-preview/test/final.test.mjs b/.github/scripts/docs-playground-preview/test/final.test.mjs index 9e4d43c322b39..f2bc4773dc055 100644 --- a/.github/scripts/docs-playground-preview/test/final.test.mjs +++ b/.github/scripts/docs-playground-preview/test/final.test.mjs @@ -1,19 +1,10 @@ import assert from 'node:assert/strict'; -import { - mkdir, - mkdtemp, - open, - readFile, - writeFile, -} from 'node:fs/promises'; +import { mkdir, mkdtemp, open, readFile, writeFile } from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import { test } from 'node:test'; -import { - createFinalBlueprint, - packageFinalSnapshot, -} from '../lib/final.mjs'; +import { createFinalBlueprint, packageFinalSnapshot } from '../lib/final.mjs'; function inputs( cacheDirectory, snapshotBytes = 1024 ) { return { @@ -21,7 +12,8 @@ function inputs( cacheDirectory, snapshotBytes = 1024 ) { wordpress: { version: '7.2-beta1' }, dependencies: { playground: { - blueprintSchema: 'https://playground.wordpress.net/blueprint-schema.json', + blueprintSchema: + 'https://playground.wordpress.net/blueprint-schema.json', phpVersion: '8.4', }, limits: { snapshotBytes }, @@ -43,15 +35,20 @@ test( 'the final Blueprint restores the base before the complete import', () => 'writeFile', 'wp-cli', 'rmdir', + 'writeFile', 'defineWpConfigConsts', ] ); assert.equal( blueprint.steps[ 0 ].extractToPath, '/' ); assert.match( blueprint.steps[ 2 ].command, /parser import.+--quick/ ); assert.equal( - blueprint.steps.at( -2 ).path, + blueprint.steps.at( -3 ).path, '/wordpress/wp-content/plugins/phpdoc-parser' ); + assert.equal( + blueprint.steps.at( -2 ).path, + '/wordpress/wp-content/mu-plugins/001-docs-preview-runtime.php' + ); assert.deepEqual( blueprint.steps.at( -1 ), { step: 'defineWpConfigConsts', method: 'rewrite-wp-config', @@ -65,7 +62,9 @@ test( 'the final Blueprint restores the base before the complete import', () => } ); async function fixture( snapshotBytes = 1024 ) { - const root = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-final-' ) ); + const root = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-final-' ) + ); const cache = path.join( root, 'cache' ); await mkdir( cache ); await writeFile( path.join( cache, 'base.zip' ), 'base' ); @@ -92,15 +91,27 @@ test( 'final packaging returns publisher metadata for a bounded snapshot', async referenceJson: current.referenceJson, stagedSource: '/source', playgroundCli: '/tools/wp-playground-cli', + provenance: { + sourceRepository: 'example/wordpress-develop', + sourceSha: 'a'.repeat( 40 ), + generationTimestamp: '2026-08-09T12:34:56.000Z', + runUrl: 'https://github.com/example/wordpress-develop/actions/runs/123', + }, runImplementation, } ); assert.equal( snapshot.filename, 'snapshot.zip' ); assert.equal( snapshot.bytes, 8 ); assert.match( snapshot.sha256, /^[0-9a-f]{64}$/ ); assert.equal( invocation.command, '/tools/wp-playground-cli' ); - assert.equal( invocation.args[ invocation.args.indexOf( '--mount' ) + 1 ], '/source:/tmp/docs-preview-source' ); + assert.equal( + invocation.args[ invocation.args.indexOf( '--mount' ) + 1 ], + '/source:/tmp/docs-preview-source' + ); const blueprint = JSON.parse( - await readFile( path.join( current.root, 'work/final-blueprint.json' ), 'utf8' ) + await readFile( + path.join( current.root, 'work/final-blueprint.json' ), + 'utf8' + ) ); assert.equal( blueprint.steps[ 0 ].zipFile.path, 'base.zip' ); } ); @@ -119,6 +130,12 @@ test( 'the 100 MiB snapshot boundary always fails closed', async () => { referenceJson: current.referenceJson, stagedSource: '/source', playgroundCli: '/tools/wp-playground-cli', + provenance: { + sourceRepository: 'example/wordpress-develop', + sourceSha: 'a'.repeat( 40 ), + generationTimestamp: '2026-08-09T12:34:56.000Z', + runUrl: 'https://github.com/example/wordpress-develop/actions/runs/123', + }, runImplementation, } ), /exceeds 100 MiB/ diff --git a/.github/scripts/docs-playground-preview/test/runtime.test.mjs b/.github/scripts/docs-playground-preview/test/runtime.test.mjs new file mode 100644 index 0000000000000..9cecb70a94378 --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/runtime.test.mjs @@ -0,0 +1,51 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; + +import { renderRuntimePlugin, validateProvenance } from '../lib/runtime.mjs'; + +const provenance = { + sourceRepository: 'example/wordpress-develop', + sourceSha: 'a'.repeat( 40 ), + generationTimestamp: '2026-08-09T12:34:56.000Z', + runUrl: 'https://github.com/example/wordpress-develop/actions/runs/123', +}; + +test( 'runtime provenance requires exact public build identity', () => { + assert.equal( validateProvenance( provenance ), provenance ); + assert.throws( + () => validateProvenance( { ...provenance, sourceSha: 'trunk' } ), + /commit is invalid/ + ); + assert.throws( + () => + validateProvenance( { + ...provenance, + runUrl: 'https://example.com/', + } ), + /run URL is invalid/ + ); +} ); + +test( 'runtime plugin exposes the banner, health, network, and policy behavior', () => { + const plugin = renderRuntimePlugin( provenance ); + assert.match( plugin, /wporg-code-reference-preview-provenance/ ); + assert.match( plugin, /wp_body_open/ ); + assert.match( plugin, /wp_footer/ ); + assert.match( plugin, /docs-preview\/v1/ ); + assert.match( plugin, /docs_preview_network_disabled/ ); + for ( const name of [ + 'DISABLE_WP_CRON', + 'AUTOMATIC_UPDATER_DISABLED', + 'WP_AUTO_UPDATE_CORE', + 'DISALLOW_FILE_MODS', + ] ) { + assert.match( plugin, new RegExp( name ) ); + } + assert.match( plugin, new RegExp( provenance.sourceSha ) ); + assert.match( plugin, new RegExp( provenance.runUrl ) ); +} ); + +test( 'local runtime provenance may omit an Actions run', () => { + const plugin = renderRuntimePlugin( { ...provenance, runUrl: null } ); + assert.match( plugin, /'runUrl'\s+=> null/ ); +} ); From ca620187e7c065554984c591d853064117115081 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 12:10:33 +0400 Subject: [PATCH 08/51] Build/Test Tools: Validate Code Reference snapshots Parser success does not prove a runnable preview. Boot the exported snapshot and exercise the index, representative symbols, search, provenance, import completion, and runtime policy through Playground. Return defects as validation state so advisory mode can report them without publishing the candidate. --- .../docs-playground-preview/lib/validate.mjs | 308 ++++++++++++++++++ .../test/validate.test.mjs | 203 ++++++++++++ 2 files changed, 511 insertions(+) create mode 100644 .github/scripts/docs-playground-preview/lib/validate.mjs create mode 100644 .github/scripts/docs-playground-preview/test/validate.test.mjs diff --git a/.github/scripts/docs-playground-preview/lib/validate.mjs b/.github/scripts/docs-playground-preview/lib/validate.mjs new file mode 100644 index 0000000000000..d9346326b17d0 --- /dev/null +++ b/.github/scripts/docs-playground-preview/lib/validate.mjs @@ -0,0 +1,308 @@ +import { spawn } from 'node:child_process'; +import { copyFile, mkdir, rm, writeFile } from 'node:fs/promises'; +import { createServer } from 'node:net'; +import path from 'node:path'; + +const BANNER_ID = 'wporg-code-reference-preview-provenance'; + +function bundled( resourcePath ) { + return { resource: 'bundled', path: resourcePath }; +} + +export function createValidationBlueprint( inputs ) { + return { + $schema: inputs.dependencies.playground.blueprintSchema, + preferredVersions: { + php: inputs.dependencies.playground.phpVersion, + wp: inputs.wordpress.version, + }, + landingPage: '/reference/', + login: false, + features: { networking: false }, + steps: [ + { + step: 'unzip', + zipFile: bundled( 'snapshot.zip' ), + extractToPath: '/', + }, + ], + }; +} + +function delay( milliseconds ) { + return new Promise( ( resolve ) => setTimeout( resolve, milliseconds ) ); +} + +async function availablePort() { + const server = createServer(); + await new Promise( ( resolve, reject ) => { + server.once( 'error', reject ); + server.listen( 0, '127.0.0.1', resolve ); + } ); + const port = server.address().port; + await new Promise( ( resolve ) => server.close( resolve ) ); + return port; +} + +function startServer( command, args ) { + const child = spawn( command, args, { + stdio: [ 'ignore', 'pipe', 'pipe' ], + } ); + let output = ''; + child.stdout.on( 'data', ( chunk ) => { + output += chunk; + } ); + child.stderr.on( 'data', ( chunk ) => { + output += chunk; + } ); + const closed = new Promise( ( resolve ) => { + child.once( 'error', ( error ) => resolve( { error } ) ); + child.once( 'close', ( code, signal ) => resolve( { code, signal } ) ); + } ); + return { child, closed, output: () => output }; +} + +async function stopServer( server ) { + if ( server.child.exitCode !== null || server.child.signalCode !== null ) { + return; + } + server.child.kill( 'SIGTERM' ); + const stopped = await Promise.race( [ + server.closed.then( () => true ), + delay( 5000 ).then( () => false ), + ] ); + if ( ! stopped ) { + server.child.kill( 'SIGKILL' ); + await server.closed; + } +} + +async function waitForBoot( baseUrl, server, fetchImplementation ) { + const deadline = Date.now() + 120000; + while ( Date.now() < deadline ) { + const state = await Promise.race( [ + server.closed, + delay( 250 ).then( () => null ), + ] ); + if ( state ) { + throw new Error( + `Playground exited before boot (${ + state.code ?? state.signal ?? state.error?.message + }).\n${ server.output() }` + ); + } + try { + const response = await fetchImplementation( + `${ baseUrl }/wp-json/docs-preview/v1/health` + ); + if ( response.ok ) { + return; + } + } catch { + // The server is not accepting requests yet. + } + } + throw new Error( + `Playground did not boot within 120 seconds.\n${ server.output() }` + ); +} + +async function withPlaygroundServer( inputs, options, inspect ) { + const work = path.resolve( options.workDirectory ); + await rm( work, { recursive: true, force: true } ); + await mkdir( work, { recursive: true } ); + await copyFile( options.snapshot, path.join( work, 'snapshot.zip' ) ); + const blueprint = path.join( work, 'validation-blueprint.json' ); + await writeFile( + blueprint, + `${ JSON.stringify( createValidationBlueprint( inputs ), null, 2 ) }\n` + ); + const port = await availablePort(); + const baseUrl = `http://127.0.0.1:${ port }`; + const server = startServer( options.playgroundCli, [ + 'server', + '--php', + inputs.dependencies.playground.phpVersion, + '--wp', + inputs.wordpress.version, + '--blueprint', + blueprint, + '--blueprint-may-read-adjacent-files', + '--site-url', + baseUrl, + '--port', + String( port ), + '--workers', + '1', + '--verbosity', + 'normal', + ] ); + try { + await waitForBoot( baseUrl, server, options.fetchImplementation ); + return await inspect( baseUrl ); + } finally { + await stopServer( server ); + await rm( work, { recursive: true, force: true } ); + } +} + +function provenanceFailure( actual, expected ) { + for ( const name of [ + 'sourceRepository', + 'sourceSha', + 'generationTimestamp', + 'runUrl', + ] ) { + if ( actual?.[ name ] !== expected[ name ] ) { + return `Health provenance ${ name } does not match the build.`; + } + } + return null; +} + +function checkBanner( name, body, provenance, failures ) { + const timestamp = provenance.generationTimestamp + .slice( 0, 19 ) + .replace( 'T', ' ' ); + const expected = [ + `id="${ BANNER_ID }"`, + provenance.sourceRepository, + provenance.sourceSha, + `${ timestamp } UTC`, + ]; + if ( provenance.runUrl ) { + expected.push( provenance.runUrl ); + } + for ( const value of expected ) { + if ( ! body.includes( value ) ) { + failures.push( + `${ name } route has incomplete provenance banner.` + ); + } + } +} + +export async function inspectSnapshotBehavior( inputs, options ) { + const failures = []; + const checks = {}; + const request = async ( route ) => { + const response = await options.fetchImplementation( + `${ options.baseUrl }${ route }` + ); + return { + status: response.status, + url: response.url, + body: await response.text(), + }; + }; + + let health; + try { + const response = await request( '/wp-json/docs-preview/v1/health' ); + checks.health = response.status; + health = JSON.parse( response.body ); + if ( response.status !== 200 ) { + failures.push( `Health route returned HTTP ${ response.status }.` ); + } + } catch ( error ) { + failures.push( `Health route failed: ${ error.message }` ); + } + if ( health ) { + const mismatch = provenanceFailure( + health.provenance, + options.provenance + ); + if ( mismatch ) { + failures.push( mismatch ); + } + if ( health.import?.stage !== 'complete-import' ) { + failures.push( 'Complete import terminal stage is missing.' ); + } + if ( health.outboundNetworkDisabled !== true ) { + failures.push( 'WordPress outbound networking is not disabled.' ); + } + for ( const name of [ + 'DISABLE_WP_CRON', + 'AUTOMATIC_UPDATER_DISABLED', + 'WP_AUTO_UPDATE_CORE', + 'DISALLOW_FILE_MODS', + ] ) { + if ( health.constants?.[ name ] !== true ) { + failures.push( + `Runtime policy constant ${ name } is not enforced.` + ); + } + } + } + + for ( const [ name, route ] of Object.entries( + inputs.dependencies.validation.routes + ) ) { + try { + const response = await request( route ); + checks[ name ] = response.status; + if ( response.status !== 200 ) { + failures.push( + `${ name } route returned HTTP ${ response.status }.` + ); + } + const requested = new URL( route, options.baseUrl ); + if ( + ! response.url || + new URL( response.url ).pathname !== requested.pathname + ) { + failures.push( + `${ name } route redirected away from its stable path.` + ); + } + checkBanner( name, response.body, options.provenance, failures ); + } catch ( error ) { + failures.push( `${ name } route failed: ${ error.message }` ); + } + } + + try { + const search = inputs.dependencies.validation.search; + const response = await request( search.path ); + checks.search = response.status; + if ( + response.status !== 200 || + ! response.body.includes( search.expectedText ) + ) { + failures.push( + 'Local Code Reference search did not return the expected result.' + ); + } + checkBanner( 'search', response.body, options.provenance, failures ); + } catch ( error ) { + failures.push( + `Local Code Reference search failed: ${ error.message }` + ); + } + + if ( options.requireRunUrl && ! options.provenance.runUrl ) { + failures.push( 'Published previews require an Actions run URL.' ); + } + return { failures, checks }; +} + +export async function validateSnapshot( inputs, options ) { + const fetchImplementation = options.fetchImplementation || globalThis.fetch; + try { + return await ( options.serveImplementation || withPlaygroundServer )( + inputs, + { ...options, fetchImplementation }, + ( baseUrl ) => + inspectSnapshotBehavior( inputs, { + ...options, + baseUrl, + fetchImplementation, + } ) + ); + } catch ( error ) { + return { + failures: [ `Playground boot failed: ${ error.message }` ], + checks: {}, + }; + } +} diff --git a/.github/scripts/docs-playground-preview/test/validate.test.mjs b/.github/scripts/docs-playground-preview/test/validate.test.mjs new file mode 100644 index 0000000000000..2233c8a547f61 --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/validate.test.mjs @@ -0,0 +1,203 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; + +import { + createValidationBlueprint, + inspectSnapshotBehavior, + validateSnapshot, +} from '../lib/validate.mjs'; + +const provenance = { + sourceRepository: 'example/wordpress-develop', + sourceSha: 'a'.repeat( 40 ), + generationTimestamp: '2026-08-09T12:34:56.000Z', + runUrl: 'https://github.com/example/wordpress-develop/actions/runs/123', +}; + +function inputs() { + return { + wordpress: { version: '7.2-beta1' }, + dependencies: { + playground: { + blueprintSchema: + 'https://playground.wordpress.net/blueprint-schema.json', + phpVersion: '8.4', + }, + validation: { + routes: { + index: '/reference/', + class: '/reference/classes/example/', + method: '/reference/classes/example/run/', + function: '/reference/functions/example/', + hook: '/reference/hooks/example_action/', + filter: '/reference/hooks/example_filter/', + }, + search: { path: '/?s=example', expectedText: 'example_result' }, + }, + }, + }; +} + +function response( body, status = 200, url = 'http://127.0.0.1:9400/' ) { + return { + status, + url, + text: async () => + typeof body === 'string' ? body : JSON.stringify( body ), + }; +} + +function healthyFetch( url ) { + if ( url.endsWith( '/wp-json/docs-preview/v1/health' ) ) { + return response( + { + provenance, + import: { stage: 'complete-import' }, + outboundNetworkDisabled: true, + constants: { + DISABLE_WP_CRON: true, + AUTOMATIC_UPDATER_DISABLED: true, + WP_AUTO_UPDATE_CORE: true, + DISALLOW_FILE_MODS: true, + }, + }, + 200, + url + ); + } + if ( url.includes( '?s=example' ) ) { + return response( + ` example_result`, + 200, + url + ); + } + return response( + ``, + 200, + url + ); +} + +test( 'validation Blueprint boots the exact snapshot without login or networking', () => { + const blueprint = createValidationBlueprint( inputs() ); + assert.equal( blueprint.login, false ); + assert.deepEqual( blueprint.features, { networking: false } ); + assert.equal( blueprint.preferredVersions.php, '8.4' ); + assert.equal( blueprint.preferredVersions.wp, '7.2-beta1' ); + assert.deepEqual( blueprint.steps, [ + { + step: 'unzip', + zipFile: { resource: 'bundled', path: 'snapshot.zip' }, + extractToPath: '/', + }, + ] ); +} ); + +test( 'behavioral validation covers health, routes, search, and banner', async () => { + const result = await inspectSnapshotBehavior( inputs(), { + baseUrl: 'http://127.0.0.1:9400', + fetchImplementation: healthyFetch, + provenance, + requireRunUrl: true, + } ); + assert.deepEqual( result.failures, [] ); + assert.deepEqual( result.checks, { + health: 200, + index: 200, + class: 200, + method: 200, + function: 200, + hook: 200, + filter: 200, + search: 200, + } ); +} ); + +test( 'behavioral defects are advisory validation failures', async () => { + const result = await inspectSnapshotBehavior( inputs(), { + baseUrl: 'http://127.0.0.1:9400', + fetchImplementation: async ( url ) => { + if ( url.endsWith( '/health' ) ) { + return response( + { + provenance: { + ...provenance, + sourceSha: 'b'.repeat( 40 ), + }, + import: null, + outboundNetworkDisabled: false, + constants: {}, + }, + 200, + url + ); + } + return response( 'broken', 404, url ); + }, + provenance: { ...provenance, runUrl: null }, + requireRunUrl: true, + } ); + assert.ok( + result.failures.some( ( failure ) => failure.includes( 'sourceSha' ) ) + ); + assert.ok( + result.failures.some( ( failure ) => + failure.includes( 'terminal stage' ) + ) + ); + assert.ok( + result.failures.some( ( failure ) => + failure.includes( 'outbound networking' ) + ) + ); + assert.ok( + result.failures.some( ( failure ) => + failure.includes( 'DISABLE_WP_CRON' ) + ) + ); + assert.ok( + result.failures.some( ( failure ) => failure.includes( 'class route' ) ) + ); + assert.ok( + result.failures.some( ( failure ) => failure.includes( 'search' ) ) + ); + assert.ok( + result.failures.some( ( failure ) => + failure.includes( 'Actions run URL' ) + ) + ); +} ); + +test( 'symbol routes may not redirect to a generic successful page', async () => { + const baseUrl = 'http://127.0.0.1:9400'; + const result = await inspectSnapshotBehavior( inputs(), { + baseUrl, + fetchImplementation: async ( url ) => { + const current = await healthyFetch( url ); + if ( url.includes( '/reference/classes/example/' ) ) { + return { ...current, url: `${ baseUrl }/reference/` }; + } + return current; + }, + provenance, + requireRunUrl: true, + } ); + assert.ok( + result.failures.includes( + 'class route redirected away from its stable path.' + ) + ); +} ); + +test( 'a Playground boot failure is returned as validation state', async () => { + const result = await validateSnapshot( inputs(), { + serveImplementation: async () => { + throw new Error( 'did not boot' ); + }, + } ); + assert.deepEqual( result, { + failures: [ 'Playground boot failed: did not boot' ], + checks: {}, + } ); +} ); From dea4904cef7983a7fe7c64888fae176fb89fc4e6 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 12:33:49 +0400 Subject: [PATCH 09/51] Build/Test Tools: Orchestrate Code Reference previews Workflow YAML must not own build semantics. Resolve the pinned inputs, build or reuse the exact base, parse Core, package once, boot the result, and emit one terminal handoff from a repository-local command. A behavioral failure is never publishable; a fatal build still reports identity and still fails. --- .github/docs-playground-preview/package.json | 1 + .../scripts/docs-playground-preview/build.mjs | 443 ++++++++++++++++++ .../test/build.test.mjs | 292 ++++++++++++ 3 files changed, 736 insertions(+) create mode 100644 .github/scripts/docs-playground-preview/build.mjs create mode 100644 .github/scripts/docs-playground-preview/test/build.test.mjs diff --git a/.github/docs-playground-preview/package.json b/.github/docs-playground-preview/package.json index 0a94f575c3772..63bccaa4d95c8 100644 --- a/.github/docs-playground-preview/package.json +++ b/.github/docs-playground-preview/package.json @@ -8,6 +8,7 @@ "node": "20.20.2" }, "scripts": { + "build": "node ../scripts/docs-playground-preview/build.mjs", "test": "node --test ../scripts/docs-playground-preview/test/*.test.mjs" }, "dependencies": { diff --git a/.github/scripts/docs-playground-preview/build.mjs b/.github/scripts/docs-playground-preview/build.mjs new file mode 100644 index 0000000000000..a28bc9df241ad --- /dev/null +++ b/.github/scripts/docs-playground-preview/build.mjs @@ -0,0 +1,443 @@ +#!/usr/bin/env node + +import { mkdir, writeFile } from 'node:fs/promises'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import path from 'node:path'; + +import { ensureInvariantBase } from './lib/base.mjs'; +import { packageFinalSnapshot } from './lib/final.mjs'; +import { + readResolvedInputs, + resolveBuildInputs, + writeResolvedInputs, +} from './lib/inputs.mjs'; +import { generateParserJson } from './lib/parser.mjs'; +import { run } from './lib/process.mjs'; +import { validateSnapshot } from './lib/validate.mjs'; + +const SCRIPT_ROOT = path.dirname( fileURLToPath( import.meta.url ) ); +const REPOSITORY_ROOT = path.resolve( SCRIPT_ROOT, '../../..' ); +const TOOLING_ROOT = path.join( + REPOSITORY_ROOT, + '.github/docs-playground-preview' +); +const FULL_COMMIT = /^[0-9a-f]{40}$/; +const REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/; + +function argumentValue( args, index, name ) { + const value = args[ index + 1 ]; + if ( ! value || value.startsWith( '--' ) ) { + throw new Error( `${ name } requires a value.` ); + } + return value; +} + +export function parseArguments( args, environment = process.env ) { + const values = {}; + for ( let index = 0; index < args.length; index++ ) { + const name = args[ index ]; + if ( name === '--resolve-only' ) { + values.resolveOnly = true; + continue; + } + if ( ! name.startsWith( '--' ) ) { + throw new Error( `Unexpected argument ${ name }.` ); + } + const key = name + .slice( 2 ) + .replace( /-([a-z])/g, ( _, letter ) => letter.toUpperCase() ); + if ( + ! [ + 'source', + 'sourceRepository', + 'sourceSha', + 'pullRequest', + 'runId', + 'runAttempt', + 'runUrl', + 'runnerImage', + 'cacheRoot', + 'workRoot', + 'output', + 'metadata', + 'inputsOutput', + 'resolvedInputs', + ].includes( key ) + ) { + throw new Error( `Unknown option ${ name }.` ); + } + values[ key ] = argumentValue( args, index, name ); + index++; + } + return { + ...values, + enforce: environment.DOCS_PREVIEW_ENFORCE === 'true', + }; +} + +function positiveInteger( value, label, nullable = true ) { + if ( + ( value === undefined || value === null || value === '' ) && + nullable + ) { + return null; + } + const number = Number( value ); + if ( ! Number.isSafeInteger( number ) || number < 1 ) { + throw new Error( `${ label } must be a positive integer.` ); + } + return number; +} + +function githubRepository( remote ) { + const match = remote + .trim() + .match( + /^(?:git@github\.com:|https:\/\/github\.com\/)([^/]+\/[^/]+?)(?:\.git)?$/ + ); + return match?.[ 1 ] || null; +} + +async function gitIdentity( source, runImplementation ) { + const sha = await runImplementation( 'git', [ 'rev-parse', 'HEAD' ], { + cwd: source, + capture: true, + quiet: true, + label: 'read source commit', + } ); + const remote = await runImplementation( + 'git', + [ 'config', '--get', 'remote.origin.url' ], + { + cwd: source, + capture: true, + quiet: true, + label: 'read source repository', + } + ); + return { + sha: sha.stdout.trim(), + repository: githubRepository( remote.stdout ), + }; +} + +async function normalizeOptions( raw, implementations ) { + const workspace = path.join( + REPOSITORY_ROOT, + '.cache/docs-playground-preview' + ); + const source = path.resolve( raw.source || REPOSITORY_ROOT ); + let sourceSha = raw.sourceSha; + let sourceRepository = raw.sourceRepository; + if ( ! sourceSha || ! sourceRepository ) { + const inferred = await gitIdentity( source, implementations.run ); + sourceSha ||= inferred.sha; + sourceRepository ||= inferred.repository; + } + if ( ! FULL_COMMIT.test( sourceSha || '' ) ) { + throw new Error( 'sourceSha must be a full lowercase commit hash.' ); + } + if ( ! REPOSITORY.test( sourceRepository || '' ) ) { + throw new Error( + 'sourceRepository must be a GitHub owner/repository.' + ); + } + const workRoot = path.resolve( + raw.workRoot || path.join( workspace, 'work' ) + ); + const outputRoot = path.join( workspace, 'output' ); + return { + ...raw, + source, + sourceSha, + sourceRepository, + pullRequestNumber: positiveInteger( raw.pullRequest, 'pullRequest' ), + workflowRunId: raw.runId || null, + workflowRunAttempt: positiveInteger( raw.runAttempt, 'runAttempt' ), + runUrl: raw.runUrl || null, + runnerImage: + raw.runnerImage || `${ process.platform }-${ process.arch }`, + cacheRoot: path.resolve( + raw.cacheRoot || path.join( workspace, 'cache' ) + ), + workRoot, + output: path.resolve( + raw.output || + path.join( outputRoot, `code-reference-${ sourceSha }.zip` ) + ), + metadata: path.resolve( + raw.metadata || path.join( outputRoot, 'build.json' ) + ), + inputsOutput: path.resolve( + raw.inputsOutput || path.join( workRoot, 'resolved-inputs.json' ) + ), + }; +} + +async function ensureNodeTools( implementations ) { + const playgroundCli = path.join( + TOOLING_ROOT, + `node_modules/.bin/wp-playground-cli${ + process.platform === 'win32' ? '.cmd' : '' + }` + ); + await implementations.run( + 'npm', + [ 'ci', '--ignore-scripts', '--no-audit', '--no-fund' ], + { cwd: TOOLING_ROOT, label: 'install pinned preview tools' } + ); + return playgroundCli; +} + +export async function verifyToolchain( inputs, options = {} ) { + const runImplementation = options.runImplementation || run; + const expected = inputs.dependencies.toolchain; + const nodeVersion = options.nodeVersion || process.versions.node; + if ( nodeVersion !== expected.nodeVersion ) { + throw new Error( + `Node ${ expected.nodeVersion } is required; found ${ nodeVersion }.` + ); + } + const npm = await runImplementation( 'npm', [ '--version' ], { + capture: true, + quiet: true, + label: 'read npm version', + } ); + if ( npm.stdout.trim() !== expected.npmVersion ) { + throw new Error( + `npm ${ + expected.npmVersion + } is required; found ${ npm.stdout.trim() }.` + ); + } + const php = await runImplementation( + 'php', + [ '-r', 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;' ], + { + capture: true, + quiet: true, + label: 'read PHP version', + } + ); + if ( php.stdout.trim() !== inputs.dependencies.playground.phpVersion ) { + throw new Error( + `PHP ${ + inputs.dependencies.playground.phpVersion + } is required; found ${ php.stdout.trim() }.` + ); + } +} + +export function createBuildMetadata( + options, + inputs, + base, + parser, + snapshot, + validation, + generationTimestamp +) { + const validationFailures = [ ...parser.failures, ...validation.failures ]; + return { + schemaVersion: 1, + sourceRepository: options.sourceRepository, + pullRequestNumber: options.pullRequestNumber, + sourceSha: options.sourceSha, + workflowRunId: options.workflowRunId, + workflowRunAttempt: options.workflowRunAttempt, + runUrl: options.runUrl, + resolvedWordPressBeta: inputs.wordpress, + phpVersion: inputs.dependencies.playground.phpVersion, + dependencyManifestDigest: inputs.cacheInputs.dependencyDigest, + snapshotFilename: snapshot.filename, + snapshotBytes: snapshot.bytes, + snapshotSha256: snapshot.sha256, + buildStatus: 'success', + validationStatus: validationFailures.length === 0 ? 'passed' : 'failed', + validationFailures, + parser: { + sourceFiles: parser.sourceFiles, + records: parser.records, + counts: parser.counts, + }, + behavior: { checks: validation.checks }, + cache: { key: inputs.cacheKey, hit: base.cacheHit }, + generationTimestamp, + }; +} + +export function createFailureMetadata( + options, + inputs, + error, + generationTimestamp +) { + return { + schemaVersion: 1, + sourceRepository: options.sourceRepository, + pullRequestNumber: options.pullRequestNumber, + sourceSha: options.sourceSha, + workflowRunId: options.workflowRunId, + workflowRunAttempt: options.workflowRunAttempt, + runUrl: options.runUrl, + resolvedWordPressBeta: inputs?.wordpress || null, + phpVersion: inputs?.dependencies?.playground?.phpVersion || null, + dependencyManifestDigest: inputs?.cacheInputs?.dependencyDigest || null, + snapshotFilename: null, + snapshotBytes: null, + snapshotSha256: null, + buildStatus: 'failed', + validationStatus: 'not-run', + validationFailures: [], + generationTimestamp, + buildError: error.message, + }; +} + +async function writeJson( filename, value ) { + await mkdir( path.dirname( filename ), { recursive: true } ); + await writeFile( filename, `${ JSON.stringify( value, null, 2 ) }\n` ); +} + +const DEFAULT_IMPLEMENTATIONS = { + run, + resolveBuildInputs, + readResolvedInputs, + writeResolvedInputs, + ensureInvariantBase, + generateParserJson, + packageFinalSnapshot, + validateSnapshot, + verifyToolchain, +}; + +export async function buildCodeReferencePreview( rawOptions, overrides = {} ) { + const implementations = { ...DEFAULT_IMPLEMENTATIONS, ...overrides }; + const options = await normalizeOptions( rawOptions, implementations ); + let generationTimestamp; + let inputs; + let result; + try { + inputs = options.resolvedInputs + ? await implementations.readResolvedInputs( options.resolvedInputs ) + : await implementations.resolveBuildInputs( { + repositoryRoot: REPOSITORY_ROOT, + cacheRoot: options.cacheRoot, + platform: process.platform, + architecture: process.arch, + runnerImage: options.runnerImage, + } ); + await implementations.verifyToolchain( inputs, { + runImplementation: implementations.run, + } ); + if ( options.resolveOnly ) { + await implementations.writeResolvedInputs( + inputs, + options.inputsOutput + ); + return { inputs, options }; + } + + generationTimestamp = new Date().toISOString(); + const provenance = { + sourceRepository: options.sourceRepository, + sourceSha: options.sourceSha, + generationTimestamp, + runUrl: options.runUrl, + }; + const playgroundCli = await ensureNodeTools( implementations ); + const base = await implementations.ensureInvariantBase( inputs, { + playgroundCli, + } ); + const parser = await implementations.generateParserJson( { + source: options.source, + stagedSource: path.join( options.workRoot, 'source' ), + parser: path.join( inputs.cacheDirectory, 'parser' ), + output: path.join( options.workRoot, 'reference.json' ), + logFile: path.join( options.workRoot, 'parser.log' ), + minimumSymbols: inputs.dependencies.validation.minimumSymbols, + } ); + const snapshot = await implementations.packageFinalSnapshot( inputs, { + workDirectory: path.join( options.workRoot, 'final' ), + output: options.output, + referenceJson: path.join( options.workRoot, 'reference.json' ), + stagedSource: path.join( options.workRoot, 'source' ), + playgroundCli, + provenance, + } ); + const validation = await implementations.validateSnapshot( inputs, { + snapshot: options.output, + workDirectory: path.join( options.workRoot, 'validation' ), + playgroundCli, + provenance, + requireRunUrl: options.workflowRunId !== null, + } ); + const metadata = createBuildMetadata( + options, + inputs, + base, + parser, + snapshot, + validation, + generationTimestamp + ); + await writeJson( options.metadata, metadata ); + result = { + inputs, + base, + parser, + snapshot, + validation, + metadata, + options, + }; + } catch ( error ) { + try { + await writeJson( + options.metadata, + createFailureMetadata( + options, + inputs, + error, + generationTimestamp || new Date().toISOString() + ) + ); + } catch { + // Preserve the build error when its terminal metadata cannot be written. + } + throw error; + } + const { metadata } = result; + if ( metadata.validationStatus === 'failed' && options.enforce ) { + throw new Error( metadata.validationFailures.join( '\n' ) ); + } + return result; +} + +async function main() { + const result = await buildCodeReferencePreview( + parseArguments( process.argv.slice( 2 ) ) + ); + for ( const failure of result.metadata?.validationFailures || [] ) { + process.stderr.write( `::warning::${ failure }\n` ); + } + process.stdout.write( + `${ JSON.stringify( + result.metadata || { + cacheKey: result.inputs.cacheKey, + resolvedWordPressBeta: result.inputs.wordpress, + }, + null, + 2 + ) }\n` + ); +} + +if ( + import.meta.url === pathToFileURL( path.resolve( process.argv[ 1 ] ) ).href +) { + main().catch( ( error ) => { + process.stderr.write( `${ error.message }\n` ); + process.exitCode = 1; + } ); +} diff --git a/.github/scripts/docs-playground-preview/test/build.test.mjs b/.github/scripts/docs-playground-preview/test/build.test.mjs new file mode 100644 index 0000000000000..2bf0a30bb36fe --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/build.test.mjs @@ -0,0 +1,292 @@ +import assert from 'node:assert/strict'; +import { mkdtemp, readFile } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { test } from 'node:test'; + +import { + buildCodeReferencePreview, + createBuildMetadata, + createFailureMetadata, + parseArguments, + verifyToolchain, +} from '../build.mjs'; + +const SHA = 'a'.repeat( 40 ); + +function resolved( cacheDirectory ) { + return { + schemaVersion: 1, + cacheKey: 'docs-preview-base-v1-test', + cacheDirectory, + cacheInputs: { dependencyDigest: 'b'.repeat( 64 ) }, + dependencies: { + toolchain: { nodeVersion: '20.20.2', npmVersion: '10.8.2' }, + playground: { phpVersion: '8.4' }, + validation: { + minimumSymbols: { + classes: 1, + methods: 1, + functions: 1, + hooks: 1, + filters: 1, + }, + }, + }, + wordpress: { + channel: 'beta', + version: '7.2-beta1', + downloadUrl: + 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', + }, + }; +} + +function parser( failures = [] ) { + return { + sourceFiles: 2, + records: 1, + counts: { classes: 1, methods: 1, functions: 1, hooks: 1, filters: 1 }, + failures, + }; +} + +test( 'arguments recognize enforcement only at the exact true value', () => { + assert.equal( + parseArguments( [], { DOCS_PREVIEW_ENFORCE: 'true' } ).enforce, + true + ); + assert.equal( + parseArguments( [], { DOCS_PREVIEW_ENFORCE: 'TRUE' } ).enforce, + false + ); + assert.throws( + () => parseArguments( [ '--unknown', 'value' ] ), + /Unknown option/ + ); +} ); + +test( 'the cache cannot claim a toolchain version that did not run', async () => { + const commands = []; + await verifyToolchain( resolved( '/cache' ), { + nodeVersion: '20.20.2', + runImplementation: async ( command ) => { + commands.push( command ); + return { stdout: command === 'npm' ? '10.8.2\n' : '8.4' }; + }, + } ); + assert.deepEqual( commands, [ 'npm', 'php' ] ); + await assert.rejects( + verifyToolchain( resolved( '/cache' ), { + nodeVersion: '22.23.2', + } ), + /Node 20\.20\.2 is required/ + ); +} ); + +test( 'build metadata contains the complete publisher handoff identity', () => { + const metadata = createBuildMetadata( + { + sourceRepository: 'example/wordpress-develop', + pullRequestNumber: 12, + sourceSha: SHA, + workflowRunId: '123', + workflowRunAttempt: 2, + runUrl: 'https://github.com/example/wordpress-develop/actions/runs/123', + }, + resolved( '/cache' ), + { cacheHit: true }, + parser(), + { filename: 'snapshot.zip', bytes: 42, sha256: 'c'.repeat( 64 ) }, + { failures: [], checks: { index: 200 } }, + '2026-08-09T12:34:56.000Z' + ); + assert.equal( metadata.schemaVersion, 1 ); + assert.equal( metadata.sourceSha, SHA ); + assert.equal( metadata.resolvedWordPressBeta.version, '7.2-beta1' ); + assert.equal( metadata.phpVersion, '8.4' ); + assert.equal( metadata.dependencyManifestDigest, 'b'.repeat( 64 ) ); + assert.equal( metadata.snapshotFilename, 'snapshot.zip' ); + assert.equal( metadata.snapshotBytes, 42 ); + assert.equal( metadata.snapshotSha256, 'c'.repeat( 64 ) ); + assert.equal( metadata.buildStatus, 'success' ); + assert.equal( metadata.validationStatus, 'passed' ); + assert.deepEqual( metadata.behavior.checks, { index: 200 } ); + assert.equal( metadata.generationTimestamp, '2026-08-09T12:34:56.000Z' ); +} ); + +test( 'failed build metadata retains the available trusted handoff identity', () => { + const metadata = createFailureMetadata( + { + sourceRepository: 'example/wordpress-develop', + pullRequestNumber: 12, + sourceSha: SHA, + workflowRunId: '123', + workflowRunAttempt: 2, + runUrl: 'https://github.com/example/wordpress-develop/actions/runs/123', + }, + resolved( '/cache' ), + new Error( 'import broke' ), + '2026-08-09T12:34:56.000Z' + ); + assert.equal( metadata.buildStatus, 'failed' ); + assert.equal( metadata.validationStatus, 'not-run' ); + assert.equal( metadata.sourceSha, SHA ); + assert.equal( metadata.resolvedWordPressBeta.version, '7.2-beta1' ); + assert.equal( metadata.snapshotFilename, null ); + assert.equal( metadata.buildError, 'import broke' ); +} ); + +test( 'resolve-only records the exact cache inputs without building', async () => { + const root = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-resolve-' ) + ); + const expected = resolved( path.join( root, 'cache/exact' ) ); + let written; + const result = await buildCodeReferencePreview( + { + source: process.cwd(), + sourceRepository: 'example/wordpress-develop', + sourceSha: SHA, + cacheRoot: path.join( root, 'cache' ), + workRoot: path.join( root, 'work' ), + resolveOnly: true, + }, + { + resolveBuildInputs: async () => expected, + verifyToolchain: async () => {}, + writeResolvedInputs: async ( inputs, filename ) => { + written = { inputs, filename }; + }, + } + ); + assert.equal( result.inputs, expected ); + assert.equal( written.inputs, expected ); + assert.equal( + written.filename, + path.join( root, 'work/resolved-inputs.json' ) + ); +} ); + +async function fixture( failures = [], enforce = false ) { + const root = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-build-' ) + ); + const cache = path.join( root, 'cache/exact' ); + const calls = []; + const overrides = { + run: async () => {}, + verifyToolchain: async () => {}, + readResolvedInputs: async () => resolved( cache ), + ensureInvariantBase: async () => { + calls.push( 'base' ); + return { cacheHit: true }; + }, + generateParserJson: async ( options ) => { + calls.push( 'parser' ); + assert.equal( options.parser, path.join( cache, 'parser' ) ); + return parser( failures ); + }, + packageFinalSnapshot: async () => { + calls.push( 'package' ); + return { + filename: 'snapshot.zip', + bytes: 42, + sha256: 'c'.repeat( 64 ), + }; + }, + validateSnapshot: async () => { + calls.push( 'validate' ); + return { failures: [], checks: { index: 200 } }; + }, + }; + const options = { + source: process.cwd(), + sourceRepository: 'example/wordpress-develop', + sourceSha: SHA, + resolvedInputs: path.join( root, 'inputs.json' ), + workRoot: path.join( root, 'work' ), + output: path.join( root, 'snapshot.zip' ), + metadata: path.join( root, 'build.json' ), + enforce, + }; + return { root, calls, overrides, options }; +} + +test( 'one command builds the base, parser data, package, and handoff in order', async () => { + const current = await fixture(); + const result = await buildCodeReferencePreview( + current.options, + current.overrides + ); + assert.deepEqual( current.calls, [ + 'base', + 'parser', + 'package', + 'validate', + ] ); + assert.equal( result.metadata.validationStatus, 'passed' ); + const written = JSON.parse( + await readFile( path.join( current.root, 'build.json' ), 'utf8' ) + ); + assert.equal( written.snapshotSha256, 'c'.repeat( 64 ) ); +} ); + +test( 'fatal packaging writes failed terminal metadata and still rejects', async () => { + const current = await fixture(); + current.overrides.packageFinalSnapshot = async () => { + throw new Error( 'snapshot is oversized' ); + }; + await assert.rejects( + buildCodeReferencePreview( current.options, current.overrides ), + /snapshot is oversized/ + ); + const written = JSON.parse( + await readFile( path.join( current.root, 'build.json' ), 'utf8' ) + ); + assert.equal( written.buildStatus, 'failed' ); + assert.equal( written.sourceSha, SHA ); + assert.equal( written.snapshotFilename, null ); + assert.equal( written.buildError, 'snapshot is oversized' ); +} ); + +test( 'advisory failures are recorded without rejecting the build', async () => { + const current = await fixture( [ 'catastrophic undercount' ] ); + const result = await buildCodeReferencePreview( + current.options, + current.overrides + ); + assert.equal( result.metadata.validationStatus, 'failed' ); + assert.deepEqual( result.metadata.validationFailures, [ + 'catastrophic undercount', + ] ); +} ); + +test( 'behavioral failures prevent passed metadata in advisory mode', async () => { + const current = await fixture(); + current.overrides.validateSnapshot = async () => ( { + failures: [ 'class route returned HTTP 404.' ], + checks: { class: 404 }, + } ); + const result = await buildCodeReferencePreview( + current.options, + current.overrides + ); + assert.equal( result.metadata.validationStatus, 'failed' ); + assert.deepEqual( result.metadata.validationFailures, [ + 'class route returned HTTP 404.', + ] ); + assert.deepEqual( result.metadata.behavior.checks, { class: 404 } ); +} ); + +test( 'enforcement rejects only after failed validation metadata is written', async () => { + const current = await fixture( [ 'catastrophic undercount' ], true ); + await assert.rejects( + buildCodeReferencePreview( current.options, current.overrides ), + /catastrophic undercount/ + ); + const written = JSON.parse( + await readFile( path.join( current.root, 'build.json' ), 'utf8' ) + ); + assert.equal( written.validationStatus, 'failed' ); +} ); From b1cdc8119c8806251c2e50192051c978e2597d89 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 12:57:06 +0400 Subject: [PATCH 10/51] Build/Test Tools: Stop executing preview source during import The parser importer includes the Core version file. Pointing every record at the pull request source let untrusted PHP run during snapshot import. Give that one record a trusted empty replacement and create it before import. Route and search checks also accepted generic responses. Require representative symbol content and the expected search result path. Non-blocking: imported WordPress version metadata remains unset. Exact source-link behavior is deferred. --- .../docs-playground-preview/dependencies.json | 33 ++++++++++--- .../docs-playground-preview/lib/final.mjs | 17 +++++++ .../docs-playground-preview/lib/parser.mjs | 48 ++++++++++++++----- .../docs-playground-preview/lib/validate.mjs | 14 ++++-- .../php/safe-version.php | 8 ++++ .../test/final.test.mjs | 15 +++++- .../test/parser.test.mjs | 32 ++++++++++--- .../test/validate.test.mjs | 43 +++++++++++++---- 8 files changed, 169 insertions(+), 41 deletions(-) create mode 100644 .github/scripts/docs-playground-preview/php/safe-version.php diff --git a/.github/docs-playground-preview/dependencies.json b/.github/docs-playground-preview/dependencies.json index 5745399688474..6351a45e3e529 100644 --- a/.github/docs-playground-preview/dependencies.json +++ b/.github/docs-playground-preview/dependencies.json @@ -61,16 +61,35 @@ "filters": 1000 }, "routes": { - "index": "/reference/", - "class": "/reference/classes/wp_html_tag_processor/", - "method": "/reference/classes/wp_html_tag_processor/next_tag/", - "function": "/reference/functions/wp_parse_args/", - "hook": "/reference/hooks/init/", - "filter": "/reference/hooks/the_content/" + "index": { + "path": "/reference/", + "expectedText": "Code Reference" + }, + "class": { + "path": "/reference/classes/wp_html_tag_processor/", + "expectedText": "WP_HTML_Tag_Processor" + }, + "method": { + "path": "/reference/classes/wp_html_tag_processor/next_tag/", + "expectedText": "next_tag" + }, + "function": { + "path": "/reference/functions/wp_parse_args/", + "expectedText": "wp_parse_args" + }, + "hook": { + "path": "/reference/hooks/init/", + "expectedText": "init" + }, + "filter": { + "path": "/reference/hooks/the_content/", + "expectedText": "the_content" + } }, "search": { "path": "/?s=wp_parse_args&post_type=wp-parser-function", - "expectedText": "wp_parse_args" + "expectedText": "wp_parse_args", + "expectedPath": "/reference/functions/wp_parse_args/" } } } diff --git a/.github/scripts/docs-playground-preview/lib/final.mjs b/.github/scripts/docs-playground-preview/lib/final.mjs index 059e64ca69132..cffa6a8d145dd 100644 --- a/.github/scripts/docs-playground-preview/lib/final.mjs +++ b/.github/scripts/docs-playground-preview/lib/final.mjs @@ -36,11 +36,24 @@ export function createFinalBlueprint( inputs ) { zipFile: bundled( 'base.zip' ), extractToPath: '/', }, + { + step: 'mkdir', + path: '/tmp/docs-preview-version', + }, + { + step: 'mkdir', + path: '/tmp/docs-preview-version/wp-includes', + }, { step: 'writeFile', path: '/tmp/reference.json', data: bundled( 'reference.json' ), }, + { + step: 'writeFile', + path: '/tmp/docs-preview-version/wp-includes/version.php', + data: bundled( 'safe-version.php' ), + }, { step: 'wp-cli', command: @@ -97,6 +110,10 @@ export async function packageFinalSnapshot( inputs, options ) { path.join( PHP_ROOT, 'complete-import.php' ), path.join( work, 'complete-import.php' ) ); + await copyFile( + path.join( PHP_ROOT, 'safe-version.php' ), + path.join( work, 'safe-version.php' ) + ); await writeFile( path.join( work, 'runtime.php' ), renderRuntimePlugin( options.provenance ) diff --git a/.github/scripts/docs-playground-preview/lib/parser.mjs b/.github/scripts/docs-playground-preview/lib/parser.mjs index fc9fccef53b26..5f1f346c2e4e3 100644 --- a/.github/scripts/docs-playground-preview/lib/parser.mjs +++ b/.github/scripts/docs-playground-preview/lib/parser.mjs @@ -5,6 +5,8 @@ import { stageCorePhp } from './files.mjs'; import { run } from './process.mjs'; const HOOK_TYPE = /^(?:action|filter)(?:_(?:deprecated|reference))?$/; +const IMPORT_SOURCE_ROOT = '/tmp/docs-preview-source'; +const SAFE_VERSION_ROOT = '/tmp/docs-preview-version'; function hooksIn( value ) { return Array.isArray( value?.hooks ) ? value.hooks : []; @@ -12,8 +14,14 @@ function hooksIn( value ) { function countHooks( hooks, counts ) { for ( const hook of hooks ) { - if ( ! hook || typeof hook !== 'object' || ! HOOK_TYPE.test( hook.type ) ) { - throw new Error( 'Parser JSON contains a hook with an invalid type.' ); + if ( + ! hook || + typeof hook !== 'object' || + ! HOOK_TYPE.test( hook.type ) + ) { + throw new Error( + 'Parser JSON contains a hook with an invalid type.' + ); } if ( hook.type.startsWith( 'action' ) ) { counts.hooks++; @@ -35,16 +43,24 @@ export function inspectParserRecords( records, minimumSymbols ) { filters: 0, }; for ( const record of records ) { - if ( ! record || typeof record !== 'object' || typeof record.path !== 'string' ) { + if ( + ! record || + typeof record !== 'object' || + typeof record.path !== 'string' + ) { throw new Error( 'Parser JSON contains an invalid file record.' ); } if ( record.path.startsWith( 'wp-content/plugins/' ) || record.path.startsWith( 'wp-content/themes/' ) ) { - throw new Error( `Parser JSON contains excluded source: ${ record.path }.` ); + throw new Error( + `Parser JSON contains excluded source: ${ record.path }.` + ); } - const functions = Array.isArray( record.functions ) ? record.functions : []; + const functions = Array.isArray( record.functions ) + ? record.functions + : []; const classes = Array.isArray( record.classes ) ? record.classes : []; counts.functions += functions.length; counts.classes += classes.length; @@ -59,15 +75,21 @@ export function inspectParserRecords( records, minimumSymbols ) { } } countHooks( hooksIn( record ), counts ); - record.root = '/tmp/docs-preview-source'; + record.root = + record.path === 'wp-includes/version.php' + ? SAFE_VERSION_ROOT + : IMPORT_SOURCE_ROOT; } - const failures = records.length === 0 ? [ 'Parser produced no file records.' ] : []; - failures.push( ...Object.entries( minimumSymbols ) - .filter( ( [ type, minimum ] ) => counts[ type ] < minimum ) - .map( - ( [ type, minimum ] ) => - `Parser produced ${ counts[ type ] } ${ type }; expected at least ${ minimum }.` - ) ); + const failures = + records.length === 0 ? [ 'Parser produced no file records.' ] : []; + failures.push( + ...Object.entries( minimumSymbols ) + .filter( ( [ type, minimum ] ) => counts[ type ] < minimum ) + .map( + ( [ type, minimum ] ) => + `Parser produced ${ counts[ type ] } ${ type }; expected at least ${ minimum }.` + ) + ); return { records: records.length, counts, failures }; } diff --git a/.github/scripts/docs-playground-preview/lib/validate.mjs b/.github/scripts/docs-playground-preview/lib/validate.mjs index d9346326b17d0..be2c745bc6be0 100644 --- a/.github/scripts/docs-playground-preview/lib/validate.mjs +++ b/.github/scripts/docs-playground-preview/lib/validate.mjs @@ -235,18 +235,18 @@ export async function inspectSnapshotBehavior( inputs, options ) { } } - for ( const [ name, route ] of Object.entries( + for ( const [ name, target ] of Object.entries( inputs.dependencies.validation.routes ) ) { try { - const response = await request( route ); + const response = await request( target.path ); checks[ name ] = response.status; if ( response.status !== 200 ) { failures.push( `${ name } route returned HTTP ${ response.status }.` ); } - const requested = new URL( route, options.baseUrl ); + const requested = new URL( target.path, options.baseUrl ); if ( ! response.url || new URL( response.url ).pathname !== requested.pathname @@ -255,6 +255,11 @@ export async function inspectSnapshotBehavior( inputs, options ) { `${ name } route redirected away from its stable path.` ); } + if ( ! response.body.includes( target.expectedText ) ) { + failures.push( + `${ name } route is missing its representative symbol.` + ); + } checkBanner( name, response.body, options.provenance, failures ); } catch ( error ) { failures.push( `${ name } route failed: ${ error.message }` ); @@ -267,7 +272,8 @@ export async function inspectSnapshotBehavior( inputs, options ) { checks.search = response.status; if ( response.status !== 200 || - ! response.body.includes( search.expectedText ) + ! response.body.includes( search.expectedText ) || + ! response.body.includes( search.expectedPath ) ) { failures.push( 'Local Code Reference search did not return the expected result.' diff --git a/.github/scripts/docs-playground-preview/php/safe-version.php b/.github/scripts/docs-playground-preview/php/safe-version.php new file mode 100644 index 0000000000000..df80505a83c91 --- /dev/null +++ b/.github/scripts/docs-playground-preview/php/safe-version.php @@ -0,0 +1,8 @@ + blueprint.steps.map( ( step ) => step.step ), [ 'unzip', + 'mkdir', + 'mkdir', + 'writeFile', 'writeFile', 'wp-cli', 'writeFile', @@ -40,7 +43,17 @@ test( 'the final Blueprint restores the base before the complete import', () => ] ); assert.equal( blueprint.steps[ 0 ].extractToPath, '/' ); - assert.match( blueprint.steps[ 2 ].command, /parser import.+--quick/ ); + assert.equal( blueprint.steps[ 1 ].path, '/tmp/docs-preview-version' ); + assert.equal( + blueprint.steps[ 2 ].path, + '/tmp/docs-preview-version/wp-includes' + ); + assert.equal( + blueprint.steps[ 4 ].path, + '/tmp/docs-preview-version/wp-includes/version.php' + ); + assert.equal( blueprint.steps[ 4 ].data.path, 'safe-version.php' ); + assert.match( blueprint.steps[ 5 ].command, /parser import.+--quick/ ); assert.equal( blueprint.steps.at( -3 ).path, '/wordpress/wp-content/plugins/phpdoc-parser' diff --git a/.github/scripts/docs-playground-preview/test/parser.test.mjs b/.github/scripts/docs-playground-preview/test/parser.test.mjs index 187f8a9f8160c..8d4f33e09cb86 100644 --- a/.github/scripts/docs-playground-preview/test/parser.test.mjs +++ b/.github/scripts/docs-playground-preview/test/parser.test.mjs @@ -4,10 +4,7 @@ import os from 'node:os'; import path from 'node:path'; import { test } from 'node:test'; -import { - generateParserJson, - inspectParserRecords, -} from '../lib/parser.mjs'; +import { generateParserJson, inspectParserRecords } from '../lib/parser.mjs'; function records() { return [ @@ -16,14 +13,20 @@ function records() { functions: [ { name: 'example', - hooks: [ { type: 'filter' }, { type: 'action_deprecated' } ], + hooks: [ + { type: 'filter' }, + { type: 'action_deprecated' }, + ], }, ], classes: [ { name: 'Example', methods: [ - { name: 'first', hooks: [ { type: 'filter_reference' } ] }, + { + name: 'first', + hooks: [ { type: 'filter_reference' } ], + }, { name: 'second', hooks: [] }, ], }, @@ -96,8 +99,23 @@ test( 'parser inspection rejects excluded bundled extension source', () => { ); } ); +test( 'the importer never receives pull-request PHP as its version include', () => { + const parsed = records(); + parsed.push( { + path: 'wp-includes/version.php', + functions: [], + classes: [], + hooks: [], + } ); + inspectParserRecords( parsed, {} ); + assert.equal( parsed[ 0 ].root, '/tmp/docs-preview-source' ); + assert.equal( parsed[ 1 ].root, '/tmp/docs-preview-version' ); +} ); + test( 'generateParserJson stages PHP, invokes the pinned parser, and normalizes output', async () => { - const temporary = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-parser-' ) ); + const temporary = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-parser-' ) + ); const source = path.join( temporary, 'wordpress/src' ); await mkdir( path.join( source, 'wp-includes' ), { recursive: true } ); await writeFile( path.join( source, 'wp-includes/version.php' ), '${ provenance.sourceRepository } ${ provenance.sourceSha } 2026-08-09 12:34:56 UTC Build run example_result`, + ` example_result example_function`, 200, url ); } + const target = Object.values( + inputs().dependencies.validation.routes + ).find( ( route ) => url.endsWith( route.path ) ); return response( - ``, + ` ${ target.expectedText }`, 200, url ); From d1fd8a0b33672fe9a200a284d8a856e6815730bb Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 13:12:51 +0400 Subject: [PATCH 11/51] Build/Test Tools: Add the Code Reference pull request build Preview builds must use the exact fork head without write authority. Run only for the one-shot label or a labeled synchronize event, and bind the invariant base to one exact cache key. A repeated SHA must not burn another full import when its public snapshot is healthy. Reuse only after the Playground proxy returns the expected headers, bytes, and digest. A rerun bypasses reuse. A missing handoff is a build failure. Publication remains a separate trusted change; this slice is already at the complexity tripwire. --- .../lib/publication.mjs | 260 ++++++++++++++++++ .../scripts/docs-playground-preview/reuse.mjs | 179 ++++++++++++ .../test/publication.test.mjs | 217 +++++++++++++++ .../docs-playground-preview-build.yml | 137 +++++++++ 4 files changed, 793 insertions(+) create mode 100644 .github/scripts/docs-playground-preview/lib/publication.mjs create mode 100644 .github/scripts/docs-playground-preview/reuse.mjs create mode 100644 .github/scripts/docs-playground-preview/test/publication.test.mjs create mode 100644 .github/workflows/docs-playground-preview-build.yml diff --git a/.github/scripts/docs-playground-preview/lib/publication.mjs b/.github/scripts/docs-playground-preview/lib/publication.mjs new file mode 100644 index 0000000000000..bc37805ff6b44 --- /dev/null +++ b/.github/scripts/docs-playground-preview/lib/publication.mjs @@ -0,0 +1,260 @@ +import { createHash } from 'node:crypto'; + +export const RELEASE_TAG = 'code-reference-playground-preview'; +export const COMMENT_MARKER = ''; +export const PLAYGROUND_ORIGIN = 'https://playground.wordpress.net'; +export const CORS_PROXY = 'https://wordpress-playground-cors-proxy.net/'; + +const FULL_COMMIT = /^[0-9a-f]{40}$/; +const FULL_DIGEST = /^[0-9a-f]{64}$/; +const REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/; + +function positiveInteger( value, label ) { + const number = Number( value ); + if ( ! Number.isSafeInteger( number ) || number < 1 ) { + throw new Error( `${ label } must be a positive integer.` ); + } + return number; +} + +function assertRepository( repository, label ) { + if ( typeof repository !== 'string' || ! REPOSITORY.test( repository ) ) { + throw new Error( `${ label } must be an owner/repository.` ); + } +} + +function assertCommit( sha, label ) { + if ( typeof sha !== 'string' || ! FULL_COMMIT.test( sha ) ) { + throw new Error( `${ label } must be a full lowercase commit hash.` ); + } +} + +export function snapshotAssetName( identity ) { + const pullRequest = positiveInteger( + identity.pullRequestNumber, + 'pullRequestNumber' + ); + const runId = positiveInteger( identity.workflowRunId, 'workflowRunId' ); + const attempt = positiveInteger( + identity.workflowRunAttempt, + 'workflowRunAttempt' + ); + assertCommit( identity.sourceSha, 'sourceSha' ); + return `code-reference-pr-${ pullRequest }-${ identity.sourceSha }-${ runId }-${ attempt }.zip`; +} + +export function metadataAssetName( snapshotName ) { + if ( + typeof snapshotName !== 'string' || + ! snapshotName.endsWith( '.zip' ) + ) { + throw new Error( 'Snapshot asset name must end in .zip.' ); + } + return `${ snapshotName.slice( 0, -4 ) }.json`; +} + +export function releaseAssetUrl( repository, assetName ) { + assertRepository( repository, 'repository' ); + if ( + typeof assetName !== 'string' || + ! /^[A-Za-z0-9_.-]+$/.test( assetName ) + ) { + throw new Error( 'Release asset name is invalid.' ); + } + return `https://github.com/${ repository }/releases/download/${ RELEASE_TAG }/${ assetName }`; +} + +export function corsProxyUrl( publicUrl ) { + const parsed = new URL( publicUrl ); + if ( parsed.protocol !== 'https:' || parsed.hostname !== 'github.com' ) { + throw new Error( 'Only public GitHub asset URLs may use the proxy.' ); + } + return `${ CORS_PROXY }${ publicUrl }`; +} + +export function createLaunchBlueprint( snapshotUrl, runtime ) { + return { + $schema: runtime.blueprintSchema, + meta: { + title: 'WordPress Core Code Reference preview', + author: 'WordPress', + description: + 'Complete Core Code Reference generated from an exact commit.', + }, + preferredVersions: { + php: runtime.phpVersion, + wp: runtime.wordpressVersion, + }, + landingPage: '/reference/', + login: false, + features: { networking: false }, + steps: [ + { + step: 'unzip', + zipFile: { resource: 'url', url: corsProxyUrl( snapshotUrl ) }, + extractToPath: '/', + }, + ], + }; +} + +export function playgroundUrl( snapshotUrl, runtime ) { + const blueprint = JSON.stringify( + createLaunchBlueprint( snapshotUrl, runtime ) + ); + return `${ PLAYGROUND_ORIGIN }/?blueprint-url=data:application/json,${ encodeURIComponent( + blueprint + ) }`; +} + +export async function validatePublicSnapshot( + publicUrl, + expected, + fetchImplementation = globalThis.fetch +) { + if ( + ! Number.isSafeInteger( expected.bytes ) || + expected.bytes < 1 || + expected.bytes > expected.maximumBytes + ) { + throw new Error( 'Published snapshot size is outside its boundary.' ); + } + if ( ! FULL_DIGEST.test( expected.sha256 || '' ) ) { + throw new Error( 'Published snapshot digest is invalid.' ); + } + const response = await fetchImplementation( corsProxyUrl( publicUrl ), { + headers: { Origin: PLAYGROUND_ORIGIN }, + } ); + if ( response.status !== 200 ) { + throw new Error( + `Public snapshot returned HTTP ${ response.status } through the proxy.` + ); + } + if ( response.headers.get( 'x-playground-cors-proxy' ) !== 'true' ) { + throw new Error( + 'Public snapshot response did not come through the proxy.' + ); + } + const allowedOrigin = response.headers.get( 'access-control-allow-origin' ); + if ( allowedOrigin !== PLAYGROUND_ORIGIN && allowedOrigin !== '*' ) { + throw new Error( + 'Public snapshot response does not allow Playground.' + ); + } + const bytes = Buffer.from( await response.arrayBuffer() ); + if ( bytes.byteLength !== expected.bytes ) { + throw new Error( + `Public snapshot has ${ bytes.byteLength } bytes; expected ${ expected.bytes }.` + ); + } + const digest = createHash( 'sha256' ).update( bytes ).digest( 'hex' ); + if ( digest !== expected.sha256 ) { + throw new Error( + 'Public snapshot digest does not match its metadata.' + ); + } + return { bytes: bytes.byteLength, sha256: digest }; +} + +export function validateReusableMetadata( metadata, expected ) { + if ( + ! metadata || + typeof metadata !== 'object' || + Array.isArray( metadata ) + ) { + throw new Error( 'Published metadata must be an object.' ); + } + if ( metadata.schemaVersion !== 1 ) { + throw new Error( 'Published metadata schema is unsupported.' ); + } + assertRepository( metadata.sourceRepository, 'sourceRepository' ); + assertRepository( expected.sourceRepository, 'expected sourceRepository' ); + assertCommit( metadata.sourceSha, 'sourceSha' ); + assertCommit( expected.sourceSha, 'expected sourceSha' ); + if ( + metadata.sourceRepository !== expected.sourceRepository || + metadata.sourceSha !== expected.sourceSha || + metadata.pullRequestNumber !== + positiveInteger( expected.pullRequestNumber, 'pullRequestNumber' ) + ) { + throw new Error( + 'Published metadata does not match the pull request.' + ); + } + if ( + metadata.buildStatus !== 'success' || + metadata.validationStatus !== 'passed' + ) { + throw new Error( + 'Published metadata does not describe a valid snapshot.' + ); + } + if ( + ! Number.isSafeInteger( metadata.snapshotBytes ) || + metadata.snapshotBytes < 1 || + metadata.snapshotBytes > expected.maximumBytes || + ! FULL_DIGEST.test( metadata.snapshotSha256 || '' ) + ) { + throw new Error( 'Published snapshot identity is invalid.' ); + } + const prefix = `code-reference-pr-${ metadata.pullRequestNumber }-${ metadata.sourceSha }-`; + if ( + typeof metadata.snapshotFilename !== 'string' || + ! metadata.snapshotFilename.startsWith( prefix ) || + ! /^code-reference-pr-\d+-[0-9a-f]{40}-\d+-\d+\.zip$/.test( + metadata.snapshotFilename + ) + ) { + throw new Error( 'Published snapshot filename is invalid.' ); + } + if ( + metadata.phpVersion !== '8.4' || + metadata.resolvedWordPressBeta?.channel !== 'beta' || + ! /^\d+\.\d+(?:\.\d+)?-(?:beta\d+|RC\d+)$/.test( + metadata.resolvedWordPressBeta.version || '' + ) || + metadata.resolvedWordPressBeta.downloadUrl !== + `https://downloads.wordpress.org/release/wordpress-${ metadata.resolvedWordPressBeta.version }.zip` + ) { + throw new Error( 'Published runtime identity is invalid.' ); + } + if ( ! FULL_DIGEST.test( metadata.dependencyManifestDigest || '' ) ) { + throw new Error( 'Published dependency identity is invalid.' ); + } + if ( + typeof metadata.generationTimestamp !== 'string' || + Number.isNaN( Date.parse( metadata.generationTimestamp ) ) + ) { + throw new Error( 'Published generation timestamp is invalid.' ); + } + return metadata; +} + +export function createReuseHandoff( published, identity ) { + validateReusableMetadata( published, { + ...identity, + maximumBytes: identity.maximumBytes, + } ); + return { + ...published, + handoffType: 'reuse', + sourceRepository: identity.sourceRepository, + pullRequestNumber: positiveInteger( + identity.pullRequestNumber, + 'pullRequestNumber' + ), + sourceSha: identity.sourceSha, + workflowRunId: String( + positiveInteger( identity.workflowRunId, 'workflowRunId' ) + ), + workflowRunAttempt: positiveInteger( + identity.workflowRunAttempt, + 'workflowRunAttempt' + ), + runUrl: identity.runUrl, + reusedSnapshot: { + assetName: published.snapshotFilename, + metadataAssetName: metadataAssetName( published.snapshotFilename ), + }, + }; +} diff --git a/.github/scripts/docs-playground-preview/reuse.mjs b/.github/scripts/docs-playground-preview/reuse.mjs new file mode 100644 index 0000000000000..da58eb718a71c --- /dev/null +++ b/.github/scripts/docs-playground-preview/reuse.mjs @@ -0,0 +1,179 @@ +#!/usr/bin/env node + +import { mkdir, writeFile } from 'node:fs/promises'; +import { pathToFileURL } from 'node:url'; +import path from 'node:path'; + +import { + RELEASE_TAG, + createReuseHandoff, + metadataAssetName, + validatePublicSnapshot, + validateReusableMetadata, +} from './lib/publication.mjs'; + +function argumentsFrom( values ) { + const options = {}; + for ( let index = 0; index < values.length; index += 2 ) { + const name = values[ index ]; + const value = values[ index + 1 ]; + if ( ! name?.startsWith( '--' ) || ! value ) { + throw new Error( `Invalid argument ${ name || '(missing)' }.` ); + } + options[ + name + .slice( 2 ) + .replace( /-([a-z])/g, ( _, letter ) => letter.toUpperCase() ) + ] = value; + } + return options; +} + +async function apiJson( url, token, fetchImplementation ) { + const response = await fetchImplementation( url, { + headers: { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${ token }`, + 'X-GitHub-Api-Version': '2022-11-28', + }, + } ); + if ( response.status === 404 ) { + return null; + } + if ( ! response.ok ) { + throw new Error( + `GitHub returned HTTP ${ response.status } for ${ url }.` + ); + } + return response.json(); +} + +async function releaseAssets( + repository, + releaseId, + token, + fetchImplementation +) { + const assets = []; + for ( let page = 1; ; page++ ) { + const batch = await apiJson( + `https://api.github.com/repos/${ repository }/releases/${ releaseId }/assets?per_page=100&page=${ page }`, + token, + fetchImplementation + ); + assets.push( ...batch ); + if ( batch.length < 100 ) { + return assets; + } + } +} + +export async function findReusablePreview( options ) { + const fetchImplementation = options.fetchImplementation || globalThis.fetch; + const release = await apiJson( + `https://api.github.com/repos/${ options.repository }/releases/tags/${ RELEASE_TAG }`, + options.token, + fetchImplementation + ); + if ( ! release ) { + return null; + } + const assets = await releaseAssets( + options.repository, + release.id, + options.token, + fetchImplementation + ); + const prefix = `code-reference-pr-${ options.pullRequestNumber }-${ options.sourceSha }-`; + const candidates = assets + .filter( + ( asset ) => + asset.name.startsWith( prefix ) && + asset.name.endsWith( '.json' ) + ) + .sort( ( left, right ) => + right.created_at.localeCompare( left.created_at ) + ); + for ( const candidate of candidates ) { + try { + const metadataResponse = await fetchImplementation( + candidate.browser_download_url + ); + if ( metadataResponse.status !== 200 ) { + throw new Error( + `Published metadata returned HTTP ${ metadataResponse.status }.` + ); + } + const published = validateReusableMetadata( + await metadataResponse.json(), + options + ); + if ( + candidate.name !== + metadataAssetName( published.snapshotFilename ) + ) { + throw new Error( + 'Published metadata asset name does not match.' + ); + } + const snapshot = assets.find( + ( asset ) => asset.name === published.snapshotFilename + ); + if ( ! snapshot ) { + throw new Error( 'Published snapshot asset is missing.' ); + } + await validatePublicSnapshot( + snapshot.browser_download_url, + { + bytes: published.snapshotBytes, + sha256: published.snapshotSha256, + maximumBytes: options.maximumBytes, + }, + fetchImplementation + ); + return createReuseHandoff( published, options ); + } catch ( error ) { + options.warning?.( + `Cannot reuse ${ candidate.name }: ${ error.message }` + ); + } + } + return null; +} + +async function main() { + const options = argumentsFrom( process.argv.slice( 2 ) ); + const handoff = await findReusablePreview( { + ...options, + maximumBytes: Number( options.maximumBytes ), + token: process.env.GITHUB_TOKEN, + warning: ( message ) => console.warn( `::warning::${ message }` ), + } ); + if ( handoff ) { + await mkdir( path.dirname( options.output ), { recursive: true } ); + await writeFile( + options.output, + `${ JSON.stringify( handoff, null, 2 ) }\n` + ); + } + if ( process.env.GITHUB_OUTPUT ) { + await writeFile( + process.env.GITHUB_OUTPUT, + `reused=${ handoff ? 'true' : 'false' }\n`, + { flag: 'a' } + ); + } +} + +if ( import.meta.url === pathToFileURL( process.argv[ 1 ] ).href ) { + main().catch( async ( error ) => { + console.warn( + `::warning::Same-SHA reuse check failed: ${ error.message }` + ); + if ( process.env.GITHUB_OUTPUT ) { + await writeFile( process.env.GITHUB_OUTPUT, 'reused=false\n', { + flag: 'a', + } ).catch( () => {} ); + } + } ); +} diff --git a/.github/scripts/docs-playground-preview/test/publication.test.mjs b/.github/scripts/docs-playground-preview/test/publication.test.mjs new file mode 100644 index 0000000000000..353f8e10f9bc8 --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/publication.test.mjs @@ -0,0 +1,217 @@ +import assert from 'node:assert/strict'; +import { createHash } from 'node:crypto'; +import { test } from 'node:test'; + +import { + PLAYGROUND_ORIGIN, + createLaunchBlueprint, + createReuseHandoff, + metadataAssetName, + playgroundUrl, + releaseAssetUrl, + snapshotAssetName, + validatePublicSnapshot, + validateReusableMetadata, +} from '../lib/publication.mjs'; +import { findReusablePreview } from '../reuse.mjs'; + +const identity = { + repository: 'WordPress/wordpress-develop', + sourceRepository: 'contributor/wordpress-develop', + pullRequestNumber: 123, + sourceSha: 'a'.repeat( 40 ), + workflowRunId: '456', + workflowRunAttempt: 1, + runUrl: 'https://github.com/WordPress/wordpress-develop/actions/runs/456/attempts/1', + maximumBytes: 104857600, +}; + +function published( bytes = Buffer.from( 'snapshot' ) ) { + const snapshotFilename = snapshotAssetName( identity ); + return { + schemaVersion: 1, + sourceRepository: identity.sourceRepository, + pullRequestNumber: identity.pullRequestNumber, + sourceSha: identity.sourceSha, + workflowRunId: identity.workflowRunId, + workflowRunAttempt: identity.workflowRunAttempt, + runUrl: identity.runUrl, + resolvedWordPressBeta: { + channel: 'beta', + version: '7.2-beta1', + downloadUrl: + 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', + }, + phpVersion: '8.4', + dependencyManifestDigest: 'b'.repeat( 64 ), + snapshotFilename, + snapshotBytes: bytes.byteLength, + snapshotSha256: createHash( 'sha256' ).update( bytes ).digest( 'hex' ), + buildStatus: 'success', + validationStatus: 'passed', + validationFailures: [], + generationTimestamp: '2026-08-09T12:34:56.000Z', + }; +} + +function headers( values ) { + return { get: ( name ) => values[ name.toLowerCase() ] || null }; +} + +test( 'immutable asset names bind the PR, SHA, run, and attempt', () => { + const snapshot = snapshotAssetName( identity ); + assert.equal( + snapshot, + `code-reference-pr-123-${ 'a'.repeat( 40 ) }-456-1.zip` + ); + assert.equal( + metadataAssetName( snapshot ), + `code-reference-pr-123-${ 'a'.repeat( 40 ) }-456-1.json` + ); + assert.throws( + () => snapshotAssetName( { ...identity, sourceSha: 'trunk' } ), + /full lowercase commit/ + ); +} ); + +test( 'the launch Blueprint opens the snapshot at the reference index', () => { + const snapshot = snapshotAssetName( identity ); + const publicUrl = releaseAssetUrl( identity.repository, snapshot ); + const runtime = { + blueprintSchema: + 'https://playground.wordpress.net/blueprint-schema.json', + phpVersion: '8.4', + wordpressVersion: '7.2-beta1', + }; + const blueprint = createLaunchBlueprint( publicUrl, runtime ); + assert.equal( blueprint.landingPage, '/reference/' ); + assert.equal( blueprint.login, false ); + assert.equal( blueprint.features.networking, false ); + assert.equal( blueprint.steps[ 0 ].extractToPath, '/' ); + assert.match( + blueprint.steps[ 0 ].zipFile.url, + /^https:\/\/wordpress-playground-cors-proxy\.net\/https:\/\/github\.com\// + ); + assert.match( + decodeURIComponent( playgroundUrl( publicUrl, runtime ) ), + /"landingPage":"\/reference\/"/ + ); +} ); + +test( 'public delivery is checked by status, headers, size, and digest', async () => { + const bytes = Buffer.from( 'snapshot' ); + const expected = published( bytes ); + const result = await validatePublicSnapshot( + releaseAssetUrl( identity.repository, expected.snapshotFilename ), + { + bytes: expected.snapshotBytes, + sha256: expected.snapshotSha256, + maximumBytes: identity.maximumBytes, + }, + async ( url, options ) => { + assert.match( url, /wordpress-playground-cors-proxy/ ); + assert.equal( options.headers.Origin, PLAYGROUND_ORIGIN ); + return { + status: 200, + headers: headers( { + 'x-playground-cors-proxy': 'true', + 'access-control-allow-origin': PLAYGROUND_ORIGIN, + } ), + arrayBuffer: async () => bytes, + }; + } + ); + assert.equal( result.sha256, expected.snapshotSha256 ); + + await assert.rejects( + validatePublicSnapshot( + 'https://github.com/WordPress/wordpress-develop/releases/download/x/y.zip', + { + bytes: bytes.byteLength, + sha256: '0'.repeat( 64 ), + maximumBytes: identity.maximumBytes, + }, + async () => ( { + status: 200, + headers: headers( { + 'x-playground-cors-proxy': 'true', + 'access-control-allow-origin': PLAYGROUND_ORIGIN, + } ), + arrayBuffer: async () => bytes, + } ) + ), + /digest/ + ); +} ); + +test( 'reusable metadata must identify a passed same-PR snapshot', () => { + const metadata = published(); + assert.equal( validateReusableMetadata( metadata, identity ), metadata ); + assert.throws( + () => + validateReusableMetadata( + { ...metadata, validationStatus: 'failed' }, + identity + ), + /valid snapshot/ + ); + assert.throws( + () => + validateReusableMetadata( + { ...metadata, snapshotBytes: 104857601 }, + identity + ), + /identity is invalid/ + ); +} ); + +test( 'same-SHA discovery verifies metadata and the proxied snapshot', async () => { + const bytes = Buffer.from( 'snapshot' ); + const metadata = published( bytes ); + const metadataName = metadataAssetName( metadata.snapshotFilename ); + const assets = [ + { + name: metadataName, + created_at: '2026-08-09T13:00:00Z', + browser_download_url: 'https://github.com/metadata.json', + }, + { + name: metadata.snapshotFilename, + created_at: '2026-08-09T13:00:00Z', + browser_download_url: 'https://github.com/snapshot.zip', + }, + ]; + const fetchImplementation = async ( url ) => { + if ( url.includes( '/releases/tags/' ) ) { + return { ok: true, status: 200, json: async () => ( { id: 9 } ) }; + } + if ( url.includes( '/releases/9/assets' ) ) { + return { ok: true, status: 200, json: async () => assets }; + } + if ( url === 'https://github.com/metadata.json' ) { + return { status: 200, json: async () => metadata }; + } + assert.match( url, /wordpress-playground-cors-proxy/ ); + return { + status: 200, + headers: headers( { + 'x-playground-cors-proxy': 'true', + 'access-control-allow-origin': PLAYGROUND_ORIGIN, + } ), + arrayBuffer: async () => bytes, + }; + }; + const handoff = await findReusablePreview( { + ...identity, + token: 'read-token', + fetchImplementation, + } ); + assert.equal( handoff.handoffType, 'reuse' ); + assert.equal( handoff.workflowRunId, identity.workflowRunId ); + assert.equal( handoff.reusedSnapshot.assetName, metadata.snapshotFilename ); + assert.equal( handoff.reusedSnapshot.metadataAssetName, metadataName ); + assert.deepEqual( + createReuseHandoff( metadata, identity ).reusedSnapshot, + handoff.reusedSnapshot + ); +} ); diff --git a/.github/workflows/docs-playground-preview-build.yml b/.github/workflows/docs-playground-preview-build.yml new file mode 100644 index 0000000000000..a09a4878e7aa0 --- /dev/null +++ b/.github/workflows/docs-playground-preview-build.yml @@ -0,0 +1,137 @@ +name: Code Reference Playground Preview Build + +on: + pull_request: + branches: + - trunk + types: [ labeled, synchronize ] + +# A newer head makes every older build obsolete for this pull request. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +# Pull-request code receives no write authority or repository secrets. +permissions: {} + +jobs: + build: + name: Build Code Reference snapshot + if: >- + ( github.repository == 'WordPress/wordpress-develop' || + ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) && + github.event.pull_request.base.ref == 'trunk' && + ( ( github.event.action == 'labeled' && github.event.label.name == 'docs-preview' ) || + ( github.event.action == 'synchronize' && contains( github.event.pull_request.labels.*.name, 'docs-preview' ) ) ) + runs-on: ubuntu-24.04 + timeout-minutes: 60 + permissions: + contents: read + env: + DEPLOYMENT_REPOSITORY: ${{ github.repository }} + SOURCE_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }} + SOURCE_SHA: ${{ github.event.pull_request.head.sha }} + PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + WORKFLOW_RUN_ID: ${{ github.run_id }} + WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + HANDOFF_DIRECTORY: ${{ github.workspace }}/handoff + RESOLVED_INPUTS: ${{ runner.temp }}/docs-preview-resolved-inputs.json + CACHE_ROOT: ${{ runner.temp }}/docs-preview-cache + WORK_ROOT: ${{ runner.temp }}/docs-preview-work + SNAPSHOT_NAME: code-reference-pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }}-${{ github.run_id }}-${{ github.run_attempt }}.zip + steps: + - name: Check out the exact pull-request head + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + + - name: Set up the pinned Node version + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '20.20.2' + + - name: Reuse a healthy public snapshot for the same SHA + id: reuse + if: ${{ github.run_attempt == 1 }} + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + node .github/scripts/docs-playground-preview/reuse.mjs + --repository "$DEPLOYMENT_REPOSITORY" + --source-repository "$SOURCE_REPOSITORY" + --pull-request-number "$PULL_REQUEST_NUMBER" + --source-sha "$SOURCE_SHA" + --workflow-run-id "$WORKFLOW_RUN_ID" + --workflow-run-attempt "$WORKFLOW_RUN_ATTEMPT" + --run-url "$RUN_URL" + --maximum-bytes 104857600 + --output "$HANDOFF_DIRECTORY/build.json" + + - name: Set up the pinned PHP and Composer versions + if: ${{ steps.reuse.outputs.reused != 'true' }} + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 + with: + php-version: '8.4' + tools: composer:2.8.12 + coverage: none + + - name: Resolve the exact build and cache identity + id: resolve + if: ${{ steps.reuse.outputs.reused != 'true' }} + run: >- + npm --prefix .github/docs-playground-preview run build -- + --resolve-only + --source "$GITHUB_WORKSPACE" + --source-repository "$SOURCE_REPOSITORY" + --source-sha "$SOURCE_SHA" + --pull-request "$PULL_REQUEST_NUMBER" + --run-id "$WORKFLOW_RUN_ID" + --run-attempt "$WORKFLOW_RUN_ATTEMPT" + --run-url "$RUN_URL" + --runner-image ubuntu-24.04 + --cache-root "$CACHE_ROOT" + --work-root "$WORK_ROOT" + --inputs-output "$RESOLVED_INPUTS" + --metadata "$HANDOFF_DIRECTORY/build.json" + + - name: Restore the exact invariant site base + if: ${{ steps.reuse.outputs.reused != 'true' }} + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ${{ steps.resolve.outputs.cache-directory }} + key: ${{ steps.resolve.outputs.cache-key }} + + # Administrator: enable enforcement in Settings > Secrets and variables > + # Actions > Variables by creating or updating DOCS_PREVIEW_ENFORCE to true. + - name: Build and validate the Code Reference snapshot + if: ${{ steps.reuse.outputs.reused != 'true' }} + env: + DOCS_PREVIEW_ENFORCE: ${{ vars.DOCS_PREVIEW_ENFORCE }} + run: >- + npm --prefix .github/docs-playground-preview run build -- + --source "$GITHUB_WORKSPACE" + --source-repository "$SOURCE_REPOSITORY" + --source-sha "$SOURCE_SHA" + --pull-request "$PULL_REQUEST_NUMBER" + --run-id "$WORKFLOW_RUN_ID" + --run-attempt "$WORKFLOW_RUN_ATTEMPT" + --run-url "$RUN_URL" + --runner-image ubuntu-24.04 + --cache-root "$CACHE_ROOT" + --work-root "$WORK_ROOT" + --resolved-inputs "$RESOLVED_INPUTS" + --output "$HANDOFF_DIRECTORY/$SNAPSHOT_NAME" + --metadata "$HANDOFF_DIRECTORY/build.json" + + - name: Upload the terminal publisher handoff + if: ${{ always() && ! cancelled() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: docs-preview-handoff-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ env.HANDOFF_DIRECTORY }} + if-no-files-found: error + retention-days: 1 From 13e0aab266d50a5526caab81f236fb880f40c05e Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 13:21:12 +0400 Subject: [PATCH 12/51] Build/Test Tools: Validate Code Reference publication handoffs The publisher must not infer trust from a successful build job. Bind every handoff to the enabled repository, exact workflow attempt, fetched pull request, trunk base, head repository, head SHA, schema, filename, size, and digest. Fresh and reused candidates share the same runtime and dependency checks. Failed or advisory results are terminal states, never publishable candidates. The snapshot is hashed but never opened. The network layer must refresh the latest run immediately before every mutation. A cached lookup defeats same-SHA supersession. --- .../docs-playground-preview/lib/publisher.mjs | 302 ++++++++++++++++++ .../test/publisher.test.mjs | 287 +++++++++++++++++ 2 files changed, 589 insertions(+) create mode 100644 .github/scripts/docs-playground-preview/lib/publisher.mjs create mode 100644 .github/scripts/docs-playground-preview/test/publisher.test.mjs diff --git a/.github/scripts/docs-playground-preview/lib/publisher.mjs b/.github/scripts/docs-playground-preview/lib/publisher.mjs new file mode 100644 index 0000000000000..074cd99680217 --- /dev/null +++ b/.github/scripts/docs-playground-preview/lib/publisher.mjs @@ -0,0 +1,302 @@ +import { stat } from 'node:fs/promises'; +import path from 'node:path'; + +import { isDeploymentEnabled } from './config.mjs'; +import { sha256File } from './files.mjs'; +import { + COMMENT_MARKER, + corsProxyUrl, + metadataAssetName, + playgroundUrl, + releaseAssetUrl, + snapshotAssetName, + validateReusableMetadata, +} from './publication.mjs'; + +export const BUILD_WORKFLOW_NAME = 'Code Reference Playground Preview Build'; +export const SNAPSHOT_BYTES_LIMIT = 104857600; + +const FULL_COMMIT = /^[0-9a-f]{40}$/; +const FULL_DIGEST = /^[0-9a-f]{64}$/; +const BLUEPRINT_SCHEMA = + 'https://playground.wordpress.net/blueprint-schema.json'; + +function positiveInteger( value, label ) { + const number = Number( value ); + if ( ! Number.isSafeInteger( number ) || number < 1 ) { + throw new Error( `${ label } must be a positive integer.` ); + } + return number; +} + +function timestamp( value, label ) { + if ( + typeof value !== 'string' || + Number.isNaN( Date.parse( value ) ) || + ! value.endsWith( 'Z' ) + ) { + throw new Error( `${ label } must be a UTC timestamp.` ); + } + return value; +} + +function headRepository( value ) { + return value?.full_name || value?.repo?.full_name || null; +} + +export function validatePublicationContext( context ) { + if ( + ! isDeploymentEnabled( context.repository, context.stagingVariable ) + ) { + throw new Error( + 'Docs preview publication is disabled in this repository.' + ); + } + const runId = positiveInteger( context.run?.id, 'Workflow run id' ); + const runAttempt = positiveInteger( + context.run?.run_attempt, + 'Workflow run attempt' + ); + if ( + runId !== positiveInteger( context.triggerRunId, 'Trigger run id' ) || + runAttempt !== + positiveInteger( context.triggerRunAttempt, 'Trigger run attempt' ) + ) { + throw new Error( + 'The triggering workflow attempt is no longer current.' + ); + } + if ( + context.run.name !== BUILD_WORKFLOW_NAME || + context.run.event !== 'pull_request' + ) { + throw new Error( 'The source workflow identity is invalid.' ); + } + if ( context.run.status !== 'completed' ) { + throw new Error( 'The source workflow is not terminal.' ); + } + const pullRequestNumber = positiveInteger( + context.pullRequest?.number, + 'Pull request number' + ); + if ( context.pullRequest.base?.ref !== 'trunk' ) { + throw new Error( 'The pull request does not target trunk.' ); + } + const sourceSha = context.pullRequest.head?.sha; + if ( + ! FULL_COMMIT.test( sourceSha || '' ) || + context.run.head_sha !== sourceSha + ) { + throw new Error( + 'The workflow run does not match the pull request head.' + ); + } + const sourceRepository = headRepository( context.pullRequest.head ); + if ( + ! sourceRepository || + headRepository( context.run.head_repository ) !== sourceRepository + ) { + throw new Error( + 'The workflow run does not match the pull request repository.' + ); + } + return { + ...context, + pullRequestNumber, + sourceRepository, + sourceSha, + workflowRunId: runId, + workflowRunAttempt: runAttempt, + runUrl: `https://github.com/${ context.repository }/actions/runs/${ runId }`, + }; +} + +export function assertLatestAuthorized( context ) { + const current = validatePublicationContext( context ); + if ( + context.latestRun?.id !== current.workflowRunId || + context.latestRun?.run_attempt !== current.workflowRunAttempt + ) { + throw new Error( 'A newer docs preview run superseded this attempt.' ); + } + if ( current.pullRequest.state !== 'open' ) { + throw new Error( 'The pull request is no longer open.' ); + } + const hasLabel = current.pullRequest.labels?.some( + ( label ) => label.name === 'docs-preview' + ); + if ( ! hasLabel && current.workflowRunAttempt === 1 ) { + throw new Error( 'The docs-preview request is no longer authorized.' ); + } + return current; +} + +function validateCommonHandoff( metadata, context ) { + if ( + ! metadata || + typeof metadata !== 'object' || + Array.isArray( metadata ) + ) { + throw new Error( 'Handoff metadata must be an object.' ); + } + if ( metadata.schemaVersion !== 1 ) { + throw new Error( 'Handoff metadata schema is unsupported.' ); + } + if ( + metadata.sourceRepository !== context.sourceRepository || + metadata.pullRequestNumber !== context.pullRequestNumber || + metadata.sourceSha !== context.sourceSha || + String( metadata.workflowRunId ) !== String( context.workflowRunId ) || + metadata.workflowRunAttempt !== context.workflowRunAttempt || + metadata.runUrl !== context.runUrl + ) { + throw new Error( 'Handoff metadata does not match the workflow run.' ); + } + timestamp( metadata.generationTimestamp, 'Generation timestamp' ); +} + +function validateSnapshotIdentity( metadata ) { + if ( + ! Number.isSafeInteger( metadata.snapshotBytes ) || + metadata.snapshotBytes < 1 || + metadata.snapshotBytes > SNAPSHOT_BYTES_LIMIT + ) { + throw new Error( 'Snapshot size is outside the 100 MiB boundary.' ); + } + if ( ! FULL_DIGEST.test( metadata.snapshotSha256 || '' ) ) { + throw new Error( 'Snapshot digest is invalid.' ); + } +} + +export function inspectHandoff( metadata, rawContext ) { + const context = validatePublicationContext( rawContext ); + validateCommonHandoff( metadata, context ); + if ( metadata.buildStatus === 'failed' ) { + return { kind: 'failed', metadata, context }; + } + if ( metadata.buildStatus !== 'success' ) { + throw new Error( 'Handoff build status is invalid.' ); + } + if ( metadata.validationStatus === 'failed' ) { + return { kind: 'invalid', metadata, context }; + } + if ( metadata.validationStatus !== 'passed' ) { + throw new Error( 'Handoff validation status is invalid.' ); + } + validateSnapshotIdentity( metadata ); + validateReusableMetadata( metadata, { + sourceRepository: context.sourceRepository, + pullRequestNumber: context.pullRequestNumber, + sourceSha: context.sourceSha, + maximumBytes: SNAPSHOT_BYTES_LIMIT, + } ); + if ( metadata.handoffType === 'reuse' ) { + if ( + metadata.reusedSnapshot?.assetName !== metadata.snapshotFilename || + metadata.reusedSnapshot.metadataAssetName !== + metadataAssetName( metadata.snapshotFilename ) + ) { + throw new Error( 'Reusable asset identity is invalid.' ); + } + return { kind: 'reuse', metadata, context }; + } + if ( metadata.handoffType !== undefined ) { + throw new Error( 'Handoff type is invalid.' ); + } + const expectedFilename = snapshotAssetName( context ); + if ( metadata.snapshotFilename !== expectedFilename ) { + throw new Error( 'Candidate snapshot filename is invalid.' ); + } + return { kind: 'candidate', metadata, context }; +} + +export async function validateCandidateFile( metadata, filename ) { + if ( path.basename( filename ) !== metadata.snapshotFilename ) { + throw new Error( + 'Candidate file does not match its metadata filename.' + ); + } + const candidate = await stat( filename ); + if ( ! candidate.isFile() || candidate.size !== metadata.snapshotBytes ) { + throw new Error( 'Candidate file size does not match its metadata.' ); + } + const digest = await sha256File( filename ); + if ( digest !== metadata.snapshotSha256 ) { + throw new Error( 'Candidate file digest does not match its metadata.' ); + } + return { bytes: candidate.size, sha256: digest }; +} + +export function createPublishedMetadata( metadata, repository, publishedAt ) { + timestamp( publishedAt, 'Publication timestamp' ); + const snapshotUrl = releaseAssetUrl( + repository, + metadata.snapshotFilename + ); + return { + ...metadata, + publication: { + publishedAt, + snapshotUrl, + snapshotProxyUrl: corsProxyUrl( snapshotUrl ), + playgroundUrl: playgroundUrl( snapshotUrl, { + blueprintSchema: BLUEPRINT_SCHEMA, + phpVersion: metadata.phpVersion, + wordpressVersion: metadata.resolvedWordPressBeta.version, + } ), + }, + }; +} + +function commitLink( repository, sha ) { + return `[${ sha }](https://github.com/${ repository }/commit/${ sha })`; +} + +function previousLink( previous ) { + if ( ! previous ) { + return ''; + } + return `\n\n[Latest successful docs preview](${ + previous.publication.playgroundUrl + }) — built from ${ commitLink( + previous.sourceRepository, + previous.sourceSha + ) }.`; +} + +export function renderPreviewComment( state ) { + const header = `${ COMMENT_MARKER }\n## Code Reference documentation preview`; + if ( state.status === 'ready' ) { + return `${ header }\n\n**Status:** Ready\n\n[Open Code Reference preview](${ + state.preview.publication.playgroundUrl + })\n\nSource: ${ commitLink( + state.preview.sourceRepository, + state.preview.sourceSha + ) } \nPublished: ${ + state.preview.publication.publishedAt + } \n[GitHub Actions run](${ state.runUrl })`; + } + if ( state.status === 'failed' ) { + return `${ header }\n\n**Status:** Latest attempt failed\n\nThe latest attempt for ${ commitLink( + state.sourceRepository, + state.sourceSha + ) } failed at ${ state.at }. [View the GitHub Actions run](${ + state.runUrl + }).${ previousLink( state.previous ) }`; + } + if ( state.status === 'stale' ) { + return `${ header }\n\n**Status:** Stale\n\nThe latest successful preview was built from ${ commitLink( + state.preview.sourceRepository, + state.preview.sourceSha + ) }, but the pull request is now at ${ commitLink( + state.currentRepository, + state.currentSha + ) }.\n\n[Latest successful docs preview](${ + state.preview.publication.playgroundUrl + })\n\nAdd the \`docs-preview\` label again to build the current commit.`; + } + if ( state.status === 'expired' ) { + return `${ header }\n\n**Status:** Expired\n\nThis pull request is closed or merged. Its Code Reference preview expired and is no longer live.`; + } + throw new Error( `Unknown preview comment state ${ state.status }.` ); +} diff --git a/.github/scripts/docs-playground-preview/test/publisher.test.mjs b/.github/scripts/docs-playground-preview/test/publisher.test.mjs new file mode 100644 index 0000000000000..965f2ba7ef81f --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/publisher.test.mjs @@ -0,0 +1,287 @@ +import assert from 'node:assert/strict'; +import { createHash } from 'node:crypto'; +import { mkdtemp, writeFile } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { test } from 'node:test'; + +import { + assertLatestAuthorized, + createPublishedMetadata, + inspectHandoff, + renderPreviewComment, + validateCandidateFile, + validatePublicationContext, +} from '../lib/publisher.mjs'; +import { + COMMENT_MARKER, + metadataAssetName, + snapshotAssetName, +} from '../lib/publication.mjs'; + +const sha = 'a'.repeat( 40 ); +const bytes = Buffer.from( 'snapshot' ); + +function context() { + return { + repository: 'WordPress/wordpress-develop', + stagingVariable: '', + triggerRunId: 456, + triggerRunAttempt: 1, + latestRun: { id: 456, run_attempt: 1 }, + run: { + id: 456, + run_attempt: 1, + name: 'Code Reference Playground Preview Build', + event: 'pull_request', + status: 'completed', + head_sha: sha, + head_repository: { full_name: 'contributor/wordpress-develop' }, + pull_requests: [], + }, + pullRequest: { + number: 123, + state: 'open', + base: { ref: 'trunk' }, + head: { + sha, + repo: { full_name: 'contributor/wordpress-develop' }, + }, + labels: [ { name: 'docs-preview' } ], + }, + }; +} + +function metadata( overrides = {} ) { + const identity = { + pullRequestNumber: 123, + sourceSha: sha, + workflowRunId: 456, + workflowRunAttempt: 1, + }; + return { + schemaVersion: 1, + sourceRepository: 'contributor/wordpress-develop', + pullRequestNumber: 123, + sourceSha: sha, + workflowRunId: '456', + workflowRunAttempt: 1, + runUrl: 'https://github.com/WordPress/wordpress-develop/actions/runs/456', + resolvedWordPressBeta: { + channel: 'beta', + version: '7.2-beta1', + downloadUrl: + 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', + }, + phpVersion: '8.4', + dependencyManifestDigest: 'b'.repeat( 64 ), + snapshotFilename: snapshotAssetName( identity ), + snapshotBytes: bytes.byteLength, + snapshotSha256: createHash( 'sha256' ).update( bytes ).digest( 'hex' ), + buildStatus: 'success', + validationStatus: 'passed', + validationFailures: [], + generationTimestamp: '2026-08-09T12:34:56.000Z', + ...overrides, + }; +} + +test( 'the publisher binds the trigger to an enabled terminal PR run', () => { + const checked = validatePublicationContext( context() ); + assert.equal( checked.sourceSha, sha ); + assert.equal( checked.pullRequestNumber, 123 ); + assert.equal( + checked.runUrl, + 'https://github.com/WordPress/wordpress-develop/actions/runs/456' + ); + assert.throws( + () => + validatePublicationContext( { + ...context(), + repository: 'attacker/wordpress-develop', + } ), + /publication is disabled/ + ); + assert.throws( + () => + validatePublicationContext( { + ...context(), + triggerRunAttempt: 2, + } ), + /no longer current/ + ); + assert.throws( + () => + validatePublicationContext( { + ...context(), + run: { ...context().run, head_sha: 'c'.repeat( 40 ) }, + } ), + /does not match the pull request head/ + ); +} ); + +test( 'latest-wins requires the label except for an explicit rerun', () => { + assert.equal( assertLatestAuthorized( context() ).sourceSha, sha ); + const unlabeled = context(); + unlabeled.pullRequest.labels = []; + assert.throws( () => assertLatestAuthorized( unlabeled ), /authorized/ ); + const rerun = context(); + rerun.triggerRunAttempt = 2; + rerun.run.run_attempt = 2; + rerun.latestRun.run_attempt = 2; + rerun.pullRequest.labels = []; + assert.equal( assertLatestAuthorized( rerun ).workflowRunAttempt, 2 ); + assert.throws( + () => + assertLatestAuthorized( { + ...context(), + latestRun: { id: 789, run_attempt: 1 }, + } ), + /superseded/ + ); +} ); + +test( 'a fresh candidate must pass every publisher identity field', () => { + assert.equal( inspectHandoff( metadata(), context() ).kind, 'candidate' ); + for ( const [ name, value, error ] of [ + [ 'schemaVersion', 2, /schema/ ], + [ 'sourceSha', 'c'.repeat( 40 ), /workflow run/ ], + [ 'workflowRunAttempt', 2, /workflow run/ ], + [ 'phpVersion', '8.5', /runtime identity/ ], + [ 'dependencyManifestDigest', 'broken', /dependency identity/ ], + [ 'handoffType', 'unknown', /Handoff type/ ], + [ 'snapshotFilename', 'snapshot.zip', /filename/ ], + [ 'snapshotBytes', 104857601, /100 MiB/ ], + [ 'snapshotSha256', 'broken', /digest/ ], + ] ) { + assert.throws( + () => inspectHandoff( metadata( { [ name ]: value } ), context() ), + error + ); + } +} ); + +test( 'failed and advisory handoffs are terminal but not candidates', () => { + assert.equal( + inspectHandoff( + metadata( { + buildStatus: 'failed', + validationStatus: 'not-run', + } ), + context() + ).kind, + 'failed' + ); + assert.equal( + inspectHandoff( metadata( { validationStatus: 'failed' } ), context() ) + .kind, + 'invalid' + ); +} ); + +test( 'reuse identifies the exact existing snapshot and metadata assets', () => { + const current = metadata(); + const reused = { + ...current, + handoffType: 'reuse', + reusedSnapshot: { + assetName: current.snapshotFilename, + metadataAssetName: metadataAssetName( current.snapshotFilename ), + }, + }; + assert.equal( inspectHandoff( reused, context() ).kind, 'reuse' ); + assert.throws( + () => + inspectHandoff( + { + ...reused, + reusedSnapshot: { + ...reused.reusedSnapshot, + assetName: 'other.zip', + }, + }, + context() + ), + /Reusable asset identity/ + ); +} ); + +test( 'candidate bytes and digest are checked without opening the snapshot', async () => { + const temporary = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-publisher-' ) + ); + const current = metadata(); + const filename = path.join( temporary, current.snapshotFilename ); + await writeFile( filename, bytes ); + assert.deepEqual( await validateCandidateFile( current, filename ), { + bytes: bytes.byteLength, + sha256: current.snapshotSha256, + } ); + await writeFile( filename, 'changed!' ); + await assert.rejects( + validateCandidateFile( current, filename ), + /digest/ + ); +} ); + +test( 'published metadata contains the immutable public launch identity', () => { + const published = createPublishedMetadata( + metadata(), + 'WordPress/wordpress-develop', + '2026-08-09T13:00:00.000Z' + ); + assert.equal( + published.publication.publishedAt, + '2026-08-09T13:00:00.000Z' + ); + assert.match( published.publication.snapshotUrl, /releases\/download/ ); + assert.match( + published.publication.snapshotProxyUrl, + /wordpress-playground-cors-proxy/ + ); + assert.match( published.publication.playgroundUrl, /blueprint-url=/ ); +} ); + +test( 'the sticky comment has ready, failed, stale, and expired states', () => { + const preview = createPublishedMetadata( + metadata(), + 'WordPress/wordpress-develop', + '2026-08-09T13:00:00.000Z' + ); + const ready = renderPreviewComment( { + status: 'ready', + preview, + runUrl: preview.runUrl, + } ); + assert.match( ready, new RegExp( COMMENT_MARKER ) ); + assert.match( ready, /Status:\*\* Ready/ ); + assert.match( ready, new RegExp( sha ) ); + assert.equal( ready.match( /Open Code Reference preview/g ).length, 1 ); + + const failed = renderPreviewComment( { + status: 'failed', + sourceRepository: preview.sourceRepository, + sourceSha: 'c'.repeat( 40 ), + at: '2026-08-09T14:00:00.000Z', + runUrl: preview.runUrl, + previous: preview, + } ); + assert.match( failed, /Latest attempt failed/ ); + assert.equal( failed.match( /Latest successful docs preview/g ).length, 1 ); + + const stale = renderPreviewComment( { + status: 'stale', + preview, + currentRepository: preview.sourceRepository, + currentSha: 'c'.repeat( 40 ), + } ); + assert.match( stale, /Status:\*\* Stale/ ); + assert.match( stale, /add the `docs-preview` label again/i ); + assert.match( stale, new RegExp( sha ) ); + assert.match( stale, new RegExp( 'c'.repeat( 40 ) ) ); + + assert.match( + renderPreviewComment( { status: 'expired' } ), + /preview expired and is no longer live/ + ); +} ); From 976908d08dda5638c6586be167e9023f108f6363 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 13:26:56 +0400 Subject: [PATCH 13/51] Build/Test Tools: Add the Code Reference publisher transport Fork workflow runs do not reliably carry pull request associations. Resolve the open pull request from the exact head owner, branch, repository, and SHA instead of trusting an empty field. Latest-wins needs a fresh workflow lookup, including rerun attempts. Keep that query and the release, comment, and label calls behind one narrow GitHub API client so transaction ordering remains visible in the publisher. --- .../docs-playground-preview/lib/github.mjs | 216 ++++++++++++++++++ .../test/github.test.mjs | 165 +++++++++++++ 2 files changed, 381 insertions(+) create mode 100644 .github/scripts/docs-playground-preview/lib/github.mjs create mode 100644 .github/scripts/docs-playground-preview/test/github.test.mjs diff --git a/.github/scripts/docs-playground-preview/lib/github.mjs b/.github/scripts/docs-playground-preview/lib/github.mjs new file mode 100644 index 0000000000000..cb104e84bd421 --- /dev/null +++ b/.github/scripts/docs-playground-preview/lib/github.mjs @@ -0,0 +1,216 @@ +import { COMMENT_MARKER, RELEASE_TAG } from './publication.mjs'; + +const API = 'https://api.github.com'; +const UPLOADS = 'https://uploads.github.com'; +const BUILD_WORKFLOW = 'docs-playground-preview-build.yml'; + +function query( values ) { + const parameters = new URLSearchParams(); + for ( const [ name, value ] of Object.entries( values ) ) { + if ( value !== undefined && value !== null ) { + parameters.set( name, String( value ) ); + } + } + return parameters.toString(); +} + +export class GitHubApi { + constructor( repository, token, fetchImplementation = globalThis.fetch ) { + this.repository = repository; + this.token = token; + this.fetch = fetchImplementation; + } + + async request( url, options = {} ) { + const headers = { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${ this.token }`, + 'X-GitHub-Api-Version': '2022-11-28', + ...options.headers, + }; + const response = await this.fetch( + url.startsWith( 'https://' ) ? url : `${ API }${ url }`, + { + ...options, + headers, + body: + options.json === undefined + ? options.body + : JSON.stringify( options.json ), + } + ); + if ( options.allowNotFound && response.status === 404 ) { + return null; + } + if ( ! response.ok ) { + const detail = await response.text(); + throw new Error( + `GitHub API returned HTTP ${ + response.status + } for ${ url }: ${ detail.slice( 0, 300 ) }` + ); + } + if ( response.status === 204 ) { + return null; + } + return response.json(); + } + + async pages( path, field = null ) { + const values = []; + for ( let page = 1; ; page++ ) { + const separator = path.includes( '?' ) ? '&' : '?'; + const response = await this.request( + `${ path }${ separator }per_page=100&page=${ page }` + ); + const batch = field ? response[ field ] : response; + values.push( ...batch ); + if ( batch.length < 100 ) { + return values; + } + } + } + + getRun( runId ) { + return this.request( + `/repos/${ this.repository }/actions/runs/${ runId }` + ); + } + + getPullRequest( number ) { + return this.request( `/repos/${ this.repository }/pulls/${ number }` ); + } + + async findPullRequestForRun( run ) { + const owner = run.head_repository?.owner?.login; + if ( ! owner || ! run.head_branch ) { + throw new Error( 'Workflow run has no fork head identity.' ); + } + const pulls = await this.pages( + `/repos/${ this.repository }/pulls?${ query( { + state: 'open', + base: 'trunk', + head: `${ owner }:${ run.head_branch }`, + } ) }` + ); + const matches = pulls.filter( + ( pullRequest ) => + pullRequest.head.sha === run.head_sha && + pullRequest.head.repo?.full_name === + run.head_repository.full_name + ); + if ( matches.length !== 1 ) { + throw new Error( + `Expected one pull request for the workflow run; found ${ matches.length }.` + ); + } + return matches[ 0 ]; + } + + async latestPreviewRun( run ) { + const runs = await this.pages( + `/repos/${ + this.repository + }/actions/workflows/${ BUILD_WORKFLOW }/runs?${ query( { + event: 'pull_request', + head_sha: run.head_sha, + } ) }`, + 'workflow_runs' + ); + const matching = runs + .filter( + ( candidate ) => + candidate.head_sha === run.head_sha && + candidate.head_branch === run.head_branch && + candidate.head_repository?.full_name === + run.head_repository?.full_name + ) + .sort( ( left, right ) => right.id - left.id ); + if ( ! matching[ 0 ] ) { + throw new Error( 'No current docs preview run matches this head.' ); + } + return this.getRun( matching[ 0 ].id ); + } + + getRelease() { + return this.request( + `/repos/${ this.repository }/releases/tags/${ RELEASE_TAG }`, + { allowNotFound: true } + ); + } + + createRelease() { + return this.request( `/repos/${ this.repository }/releases`, { + method: 'POST', + json: { + tag_name: RELEASE_TAG, + name: 'Code Reference Playground previews', + body: 'Automated prerelease for Code Reference Playground snapshots.', + prerelease: true, + }, + } ); + } + + listReleaseAssets( releaseId ) { + return this.pages( + `/repos/${ this.repository }/releases/${ releaseId }/assets` + ); + } + + uploadReleaseAsset( releaseId, name, bytes, contentType ) { + return this.request( + `${ UPLOADS }/repos/${ + this.repository + }/releases/${ releaseId }/assets?${ query( { name } ) }`, + { + method: 'POST', + headers: { + Accept: 'application/vnd.github+json', + 'Content-Type': contentType, + }, + body: bytes, + } + ); + } + + deleteReleaseAsset( assetId ) { + return this.request( + `/repos/${ this.repository }/releases/assets/${ assetId }`, + { method: 'DELETE' } + ); + } + + async findPreviewComment( pullRequestNumber ) { + const comments = await this.pages( + `/repos/${ this.repository }/issues/${ pullRequestNumber }/comments` + ); + return ( + comments.find( + ( comment ) => + comment.user?.type === 'Bot' && + comment.body.includes( COMMENT_MARKER ) + ) || null + ); + } + + createComment( pullRequestNumber, body ) { + return this.request( + `/repos/${ this.repository }/issues/${ pullRequestNumber }/comments`, + { method: 'POST', json: { body } } + ); + } + + updateComment( commentId, body ) { + return this.request( + `/repos/${ this.repository }/issues/comments/${ commentId }`, + { method: 'PATCH', json: { body } } + ); + } + + removeLabel( pullRequestNumber ) { + return this.request( + `/repos/${ this.repository }/issues/${ pullRequestNumber }/labels/docs-preview`, + { method: 'DELETE', allowNotFound: true } + ); + } +} diff --git a/.github/scripts/docs-playground-preview/test/github.test.mjs b/.github/scripts/docs-playground-preview/test/github.test.mjs new file mode 100644 index 0000000000000..052c0da77b65d --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/github.test.mjs @@ -0,0 +1,165 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; + +import { GitHubApi } from '../lib/github.mjs'; +import { COMMENT_MARKER, RELEASE_TAG } from '../lib/publication.mjs'; + +function response( value, status = 200 ) { + return { + ok: status >= 200 && status < 300, + status, + json: async () => value, + text: async () => JSON.stringify( value ), + }; +} + +function run( overrides = {} ) { + return { + id: 456, + run_attempt: 1, + head_sha: 'a'.repeat( 40 ), + head_branch: 'feature', + head_repository: { + full_name: 'contributor/wordpress-develop', + owner: { login: 'contributor' }, + }, + ...overrides, + }; +} + +function pullRequest( overrides = {} ) { + return { + number: 123, + base: { ref: 'trunk' }, + head: { + sha: 'a'.repeat( 40 ), + ref: 'feature', + repo: { full_name: 'contributor/wordpress-develop' }, + }, + ...overrides, + }; +} + +test( 'API requests use the repository token and report failures', async () => { + const calls = []; + const api = new GitHubApi( + 'WordPress/wordpress-develop', + 'token', + async ( url, options ) => { + calls.push( { url, options } ); + return response( { id: 456 } ); + } + ); + assert.equal( ( await api.getRun( 456 ) ).id, 456 ); + assert.match( calls[ 0 ].url, /actions\/runs\/456$/ ); + assert.equal( calls[ 0 ].options.headers.Authorization, 'Bearer token' ); + + const failing = new GitHubApi( + 'WordPress/wordpress-develop', + 'token', + async () => response( { message: 'no' }, 403 ) + ); + await assert.rejects( failing.getRun( 456 ), /HTTP 403/ ); +} ); + +test( 'a fork run resolves its PR without workflow pull_requests data', async () => { + const api = new GitHubApi( + 'WordPress/wordpress-develop', + 'token', + async ( url ) => { + assert.match( url, /state=open/ ); + assert.match( url, /head=contributor%3Afeature/ ); + return response( [ pullRequest() ] ); + } + ); + assert.equal( ( await api.findPullRequestForRun( run() ) ).number, 123 ); + + const ambiguous = new GitHubApi( + 'WordPress/wordpress-develop', + 'token', + async () => + response( [ pullRequest(), pullRequest( { number: 124 } ) ] ) + ); + await assert.rejects( + ambiguous.findPullRequestForRun( run() ), + /Expected one pull request/ + ); +} ); + +test( 'latest run lookup binds SHA, branch, repository, and current attempt', async () => { + const calls = []; + const api = new GitHubApi( + 'WordPress/wordpress-develop', + 'token', + async ( url ) => { + calls.push( url ); + if ( url.includes( '/actions/workflows/' ) ) { + return response( { + workflow_runs: [ + run( { id: 789, head_branch: 'other' } ), + run( { id: 456 } ), + ], + } ); + } + return response( run( { id: 456, run_attempt: 2 } ) ); + } + ); + const latest = await api.latestPreviewRun( run() ); + assert.equal( latest.run_attempt, 2 ); + assert.match( calls[ 0 ], /head_sha=/ ); + assert.match( calls[ 1 ], /actions\/runs\/456$/ ); +} ); + +test( 'release operations keep immutable names and bytes', async () => { + const calls = []; + const api = new GitHubApi( + 'WordPress/wordpress-develop', + 'token', + async ( url, options ) => { + calls.push( { url, options } ); + if ( options.method === 'DELETE' ) { + return response( null, 204 ); + } + return response( { id: 9, name: 'snapshot.zip' } ); + } + ); + await api.createRelease(); + await api.uploadReleaseAsset( + 9, + 'snapshot.zip', + Buffer.from( 'snapshot' ), + 'application/zip' + ); + await api.deleteReleaseAsset( 10 ); + assert.equal( calls[ 0 ].options.json.tag_name, RELEASE_TAG ); + assert.match( calls[ 1 ].url, /^https:\/\/uploads\.github\.com/ ); + assert.equal( calls[ 1 ].options.body.toString(), 'snapshot' ); + assert.equal( calls[ 2 ].options.method, 'DELETE' ); +} ); + +test( 'only the marked bot comment is updated', async () => { + const calls = []; + const api = new GitHubApi( + 'WordPress/wordpress-develop', + 'token', + async ( url, options ) => { + calls.push( { url, options } ); + if ( url.includes( '/comments?per_page' ) ) { + return response( [ + { id: 1, user: { type: 'User' }, body: COMMENT_MARKER }, + { id: 2, user: { type: 'Bot' }, body: COMMENT_MARKER }, + ] ); + } + if ( options.method === 'DELETE' ) { + return response( null, 204 ); + } + return response( { id: 2 } ); + } + ); + assert.equal( ( await api.findPreviewComment( 123 ) ).id, 2 ); + await api.updateComment( 2, 'updated' ); + await api.removeLabel( 123 ); + assert.equal( calls[ 1 ].options.method, 'PATCH' ); + assert.deepEqual( calls[ 1 ].options.json, { body: 'updated' } ); + assert.match( calls[ 2 ].url, /labels\/docs-preview$/ ); +} ); From 02c2cd2ba7b657a0504fe5297691d5642ffb8192 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 13:37:51 +0400 Subject: [PATCH 14/51] Build/Test Tools: Add published preview validation A release upload is not a usable preview until the public path returns the expected bytes. Validate the public metadata, launch pointer, proxy headers, size, and digest before accepting it. Obsolete fork runs exit without touching current state. Previous-preview discovery fetches accepted metadata twice. That is redundant network work, not a correctness defect. --- .../docs-playground-preview/lib/github.mjs | 3 + .../docs-playground-preview/lib/published.mjs | 132 +++++++++++ .../test/github.test.mjs | 6 + .../test/published.test.mjs | 220 ++++++++++++++++++ 4 files changed, 361 insertions(+) create mode 100644 .github/scripts/docs-playground-preview/lib/published.mjs create mode 100644 .github/scripts/docs-playground-preview/test/published.test.mjs diff --git a/.github/scripts/docs-playground-preview/lib/github.mjs b/.github/scripts/docs-playground-preview/lib/github.mjs index cb104e84bd421..a186c75d9400e 100644 --- a/.github/scripts/docs-playground-preview/lib/github.mjs +++ b/.github/scripts/docs-playground-preview/lib/github.mjs @@ -99,6 +99,9 @@ export class GitHubApi { pullRequest.head.repo?.full_name === run.head_repository.full_name ); + if ( matches.length === 0 ) { + return null; + } if ( matches.length !== 1 ) { throw new Error( `Expected one pull request for the workflow run; found ${ matches.length }.` diff --git a/.github/scripts/docs-playground-preview/lib/published.mjs b/.github/scripts/docs-playground-preview/lib/published.mjs new file mode 100644 index 0000000000000..99e9750fc26b8 --- /dev/null +++ b/.github/scripts/docs-playground-preview/lib/published.mjs @@ -0,0 +1,132 @@ +import { SNAPSHOT_BYTES_LIMIT } from './publisher.mjs'; +import { + corsProxyUrl, + metadataAssetName, + playgroundUrl, + releaseAssetUrl, + validatePublicSnapshot, + validateReusableMetadata, +} from './publication.mjs'; + +const BLUEPRINT_SCHEMA = + 'https://playground.wordpress.net/blueprint-schema.json'; + +async function fetchPublishedMetadata( asset, fetchImplementation ) { + const response = await fetchImplementation( asset.browser_download_url ); + if ( response.status !== 200 ) { + throw new Error( + `Public metadata returned HTTP ${ response.status }.` + ); + } + return response.json(); +} + +function validatePublishedFields( metadata, repository, snapshotAsset ) { + const snapshotUrl = releaseAssetUrl( repository, snapshotAsset.name ); + if ( + snapshotAsset.browser_download_url !== snapshotUrl || + metadata.publication?.snapshotUrl !== snapshotUrl || + metadata.publication?.snapshotProxyUrl !== + corsProxyUrl( snapshotUrl ) || + metadata.publication?.playgroundUrl !== + playgroundUrl( snapshotUrl, { + blueprintSchema: BLUEPRINT_SCHEMA, + phpVersion: metadata.phpVersion, + wordpressVersion: metadata.resolvedWordPressBeta.version, + } ) || + typeof metadata.publication?.publishedAt !== 'string' || + ! metadata.publication.publishedAt.endsWith( 'Z' ) || + Number.isNaN( Date.parse( metadata.publication.publishedAt ) ) + ) { + throw new Error( 'Published preview pointer metadata is invalid.' ); + } +} + +export async function loadPublishedPreview( + repository, + pullRequestNumber, + metadataAsset, + snapshotAsset, + options +) { + const metadata = validateReusableMetadata( + await fetchPublishedMetadata( + metadataAsset, + options.fetchImplementation + ), + { + sourceRepository: options.sourceRepository, + pullRequestNumber, + sourceSha: options.sourceSha, + maximumBytes: SNAPSHOT_BYTES_LIMIT, + } + ); + if ( + metadataAsset.name !== metadataAssetName( metadata.snapshotFilename ) || + snapshotAsset.name !== metadata.snapshotFilename + ) { + throw new Error( 'Published release asset names are invalid.' ); + } + validatePublishedFields( metadata, repository, snapshotAsset ); + await validatePublicSnapshot( + snapshotAsset.browser_download_url, + { + bytes: metadata.snapshotBytes, + sha256: metadata.snapshotSha256, + maximumBytes: SNAPSHOT_BYTES_LIMIT, + }, + options.fetchImplementation + ); + return metadata; +} + +export async function findPreviousPreview( session, excluded = new Set() ) { + const release = await session.api.getRelease(); + if ( ! release ) { + return null; + } + const assets = await session.api.listReleaseAssets( release.id ); + const prefix = `code-reference-pr-${ session.context.pullRequestNumber }-`; + const candidates = assets + .filter( + ( asset ) => + asset.name.startsWith( prefix ) && + asset.name.endsWith( '.json' ) && + ! excluded.has( asset.name ) + ) + .sort( ( left, right ) => + right.created_at.localeCompare( left.created_at ) + ); + for ( const metadataAsset of candidates ) { + try { + const published = await fetchPublishedMetadata( + metadataAsset, + session.fetchImplementation + ); + const snapshotAsset = assets.find( + ( asset ) => + asset.name === published.snapshotFilename && + ! excluded.has( asset.name ) + ); + if ( ! snapshotAsset ) { + throw new Error( 'Published snapshot asset is missing.' ); + } + return await loadPublishedPreview( + session.repository, + session.context.pullRequestNumber, + metadataAsset, + snapshotAsset, + { + sourceRepository: published.sourceRepository, + sourceSha: published.sourceSha, + fetchImplementation: session.fetchImplementation, + } + ); + } catch ( error ) { + session.warning( + `Cannot retain ${ metadataAsset.name }: ${ error.message }` + ); + } + } + return null; +} diff --git a/.github/scripts/docs-playground-preview/test/github.test.mjs b/.github/scripts/docs-playground-preview/test/github.test.mjs index 052c0da77b65d..abbbfbefd9013 100644 --- a/.github/scripts/docs-playground-preview/test/github.test.mjs +++ b/.github/scripts/docs-playground-preview/test/github.test.mjs @@ -73,6 +73,12 @@ test( 'a fork run resolves its PR without workflow pull_requests data', async () } ); assert.equal( ( await api.findPullRequestForRun( run() ) ).number, 123 ); + const obsolete = new GitHubApi( + 'WordPress/wordpress-develop', + 'token', + async () => response( [] ) + ); + assert.equal( await obsolete.findPullRequestForRun( run() ), null ); const ambiguous = new GitHubApi( 'WordPress/wordpress-develop', diff --git a/.github/scripts/docs-playground-preview/test/published.test.mjs b/.github/scripts/docs-playground-preview/test/published.test.mjs new file mode 100644 index 0000000000000..2f094e9fcfdd1 --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/published.test.mjs @@ -0,0 +1,220 @@ +import assert from 'node:assert/strict'; +import { createHash } from 'node:crypto'; +import { test } from 'node:test'; + +import { + findPreviousPreview, + loadPublishedPreview, +} from '../lib/published.mjs'; +import { + PLAYGROUND_ORIGIN, + corsProxyUrl, + metadataAssetName, + playgroundUrl, + releaseAssetUrl, + snapshotAssetName, +} from '../lib/publication.mjs'; + +const repository = 'WordPress/wordpress-develop'; +const sourceRepository = 'contributor/wordpress-develop'; +const pullRequestNumber = 123; +const sourceSha = 'a'.repeat( 40 ); +const bytes = Buffer.from( 'snapshot' ); + +function published() { + const snapshotFilename = snapshotAssetName( { + pullRequestNumber, + sourceSha, + workflowRunId: 456, + workflowRunAttempt: 1, + } ); + const snapshotUrl = releaseAssetUrl( repository, snapshotFilename ); + return { + schemaVersion: 1, + sourceRepository, + pullRequestNumber, + sourceSha, + workflowRunId: '456', + workflowRunAttempt: 1, + runUrl: `https://github.com/${ repository }/actions/runs/456`, + resolvedWordPressBeta: { + channel: 'beta', + version: '7.2-beta1', + downloadUrl: + 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', + }, + phpVersion: '8.4', + dependencyManifestDigest: 'b'.repeat( 64 ), + snapshotFilename, + snapshotBytes: bytes.byteLength, + snapshotSha256: createHash( 'sha256' ).update( bytes ).digest( 'hex' ), + buildStatus: 'success', + validationStatus: 'passed', + generationTimestamp: '2026-08-09T12:34:56.000Z', + publication: { + snapshotUrl, + snapshotProxyUrl: corsProxyUrl( snapshotUrl ), + playgroundUrl: playgroundUrl( snapshotUrl, { + blueprintSchema: + 'https://playground.wordpress.net/blueprint-schema.json', + phpVersion: '8.4', + wordpressVersion: '7.2-beta1', + } ), + publishedAt: '2026-08-09T12:35:00.000Z', + }, + }; +} + +function assets( metadata ) { + return { + metadataAsset: { + name: metadataAssetName( metadata.snapshotFilename ), + browser_download_url: `https://github.com/metadata/${ metadata.snapshotFilename }.json`, + created_at: '2026-08-09T12:35:00Z', + }, + snapshotAsset: { + name: metadata.snapshotFilename, + browser_download_url: releaseAssetUrl( + repository, + metadata.snapshotFilename + ), + created_at: '2026-08-09T12:34:59Z', + }, + }; +} + +function headers() { + return { + get: ( name ) => + ( { + 'x-playground-cors-proxy': 'true', + 'access-control-allow-origin': PLAYGROUND_ORIGIN, + } )[ name.toLowerCase() ] || null, + }; +} + +function publicFetch( metadata, metadataAsset ) { + return async ( url ) => { + if ( url === metadataAsset.browser_download_url ) { + return { status: 200, json: async () => metadata }; + } + assert.equal( url, metadata.publication.snapshotProxyUrl ); + return { + status: 200, + headers: headers(), + arrayBuffer: async () => bytes, + }; + }; +} + +test( 'a published preview is accepted only through its public delivery path', async () => { + const metadata = published(); + const { metadataAsset, snapshotAsset } = assets( metadata ); + assert.equal( + await loadPublishedPreview( + repository, + pullRequestNumber, + metadataAsset, + snapshotAsset, + { + sourceRepository, + sourceSha, + fetchImplementation: publicFetch( metadata, metadataAsset ), + } + ), + metadata + ); +} ); + +test( 'a published preview rejects invalid pointer identity and proxy bytes', async () => { + const metadata = published(); + const { metadataAsset, snapshotAsset } = assets( metadata ); + await assert.rejects( + loadPublishedPreview( + repository, + pullRequestNumber, + metadataAsset, + snapshotAsset, + { + sourceRepository, + sourceSha, + fetchImplementation: publicFetch( + { + ...metadata, + publication: { + ...metadata.publication, + publishedAt: '2026-08-09T12:35:00+04:00', + }, + }, + metadataAsset + ), + } + ), + /pointer metadata/ + ); + + await assert.rejects( + loadPublishedPreview( + repository, + pullRequestNumber, + metadataAsset, + snapshotAsset, + { + sourceRepository, + sourceSha, + fetchImplementation: async ( url ) => { + if ( url === metadataAsset.browser_download_url ) { + return { status: 200, json: async () => metadata }; + } + return { + status: 200, + headers: headers(), + arrayBuffer: async () => Buffer.from( 'changed' ), + }; + }, + } + ), + /bytes/ + ); +} ); + +test( 'previous-preview discovery skips broken and excluded assets', async () => { + const metadata = published(); + const { metadataAsset, snapshotAsset } = assets( metadata ); + const broken = { + name: `code-reference-pr-${ pullRequestNumber }-${ 'c'.repeat( + 40 + ) }-999-1.json`, + browser_download_url: 'https://github.com/metadata/broken.json', + created_at: '2026-08-09T12:36:00Z', + }; + const warnings = []; + const session = { + repository, + context: { pullRequestNumber }, + warning: ( message ) => warnings.push( message ), + fetchImplementation: async ( url ) => { + if ( url === broken.browser_download_url ) { + return { status: 404 }; + } + return publicFetch( metadata, metadataAsset )( url ); + }, + api: { + getRelease: async () => ( { id: 1 } ), + listReleaseAssets: async () => [ + broken, + metadataAsset, + snapshotAsset, + ], + }, + }; + assert.equal( await findPreviousPreview( session ), metadata ); + assert.equal( warnings.length, 1 ); + assert.equal( + await findPreviousPreview( + session, + new Set( [ metadataAsset.name, snapshotAsset.name ] ) + ), + null + ); +} ); From 02155ccf68b547dc6bcafdae307bca5993a90417 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 13:46:44 +0400 Subject: [PATCH 15/51] Build/Test Tools: Publish PR snapshots transactionally Publication is the privileged boundary. Recheck the current head before every mutation, validate public delivery before moving the comment, and upload before deleting the old preview. Failed cleanup touches only asset IDs uploaded by this invocation. Comment, cleanup, and label failures remain visible instead of masquerading as supersession. --- .../docs-playground-preview/publish.mjs | 376 ++++++++++++++++++ 1 file changed, 376 insertions(+) create mode 100644 .github/scripts/docs-playground-preview/publish.mjs diff --git a/.github/scripts/docs-playground-preview/publish.mjs b/.github/scripts/docs-playground-preview/publish.mjs new file mode 100644 index 0000000000000..ef24ae6f29a9b --- /dev/null +++ b/.github/scripts/docs-playground-preview/publish.mjs @@ -0,0 +1,376 @@ +#!/usr/bin/env node + +import { readFile } from 'node:fs/promises'; +import { pathToFileURL } from 'node:url'; +import path from 'node:path'; + +import { GitHubApi } from './lib/github.mjs'; +import { + assertLatestAuthorized, + createPublishedMetadata, + inspectHandoff, + renderPreviewComment, + validateCandidateFile, + validatePublicationContext, +} from './lib/publisher.mjs'; +import { metadataAssetName } from './lib/publication.mjs'; +import { findPreviousPreview, loadPublishedPreview } from './lib/published.mjs'; + +class SupersededRun extends Error {} + +async function readHandoff( directory ) { + return JSON.parse( + await readFile( path.join( directory, 'build.json' ), 'utf8' ) + ); +} + +async function establishSession( options ) { + const api = + options.api || + new GitHubApi( + options.repository, + options.token, + options.fetchImplementation + ); + const run = await api.getRun( options.triggerRunId ); + const pullRequest = await api.findPullRequestForRun( run ); + if ( ! pullRequest ) { + return null; + } + const base = { + repository: options.repository, + stagingVariable: options.stagingVariable, + triggerRunId: options.triggerRunId, + triggerRunAttempt: options.triggerRunAttempt, + run, + pullRequest, + }; + const context = validatePublicationContext( base ); + return { + api, + context, + repository: options.repository, + fetchImplementation: options.fetchImplementation || globalThis.fetch, + warning: options.warning || console.warn, + now: options.now || ( () => new Date().toISOString() ), + handoffDirectory: path.resolve( options.handoffDirectory ), + authorize: async () => { + const currentRun = await api.getRun( options.triggerRunId ); + const currentPullRequest = await api.getPullRequest( + context.pullRequestNumber + ); + const latestRun = await api.latestPreviewRun( currentRun ); + try { + return assertLatestAuthorized( { + ...base, + run: currentRun, + pullRequest: currentPullRequest, + latestRun, + } ); + } catch ( error ) { + throw new SupersededRun( error.message ); + } + }, + }; +} + +async function ensureRelease( session ) { + let release = await session.api.getRelease(); + if ( release ) { + return release; + } + await session.authorize(); + try { + release = await session.api.createRelease(); + } catch ( error ) { + release = await session.api.getRelease(); + if ( ! release ) { + throw error; + } + } + return release; +} + +async function upsertComment( session, body ) { + const comment = await session.api.findPreviewComment( + session.context.pullRequestNumber + ); + await session.authorize(); + if ( comment ) { + await session.api.updateComment( comment.id, body ); + } else { + await session.api.createComment( + session.context.pullRequestNumber, + body + ); + } +} + +async function removeRequestLabel( session ) { + await session.authorize(); + await session.api.removeLabel( session.context.pullRequestNumber ); +} + +async function cleanupOldAssets( session, assets, keep ) { + const prefix = `code-reference-pr-${ session.context.pullRequestNumber }-`; + for ( const asset of assets ) { + if ( asset.name.startsWith( prefix ) && ! keep.has( asset.name ) ) { + await session.authorize(); + await session.api.deleteReleaseAsset( asset.id ); + } + } +} + +async function cleanupFailedCandidate( session, assets ) { + for ( const asset of assets ) { + await session.authorize(); + await session.api.deleteReleaseAsset( asset.id ); + } +} + +async function runIndependently( operations, message ) { + const errors = []; + for ( const operation of operations ) { + try { + await operation(); + } catch ( error ) { + errors.push( error ); + } + } + if ( + errors.length === 1 || + ( errors.length > 1 && + errors.every( ( error ) => error instanceof SupersededRun ) ) + ) { + throw errors[ 0 ]; + } + if ( errors.length > 1 ) { + throw new AggregateError( errors, message ); + } +} + +async function reportFailure( session, excluded = new Set() ) { + const previous = await findPreviousPreview( session, excluded ); + const body = renderPreviewComment( { + status: 'failed', + sourceRepository: session.context.sourceRepository, + sourceSha: session.context.sourceSha, + at: session.now(), + runUrl: session.context.runUrl, + previous, + } ); + await runIndependently( + [ + () => upsertComment( session, body ), + () => removeRequestLabel( session ), + ], + 'Failure comment and label removal both failed.' + ); +} + +async function finishReady( session, assets, keep ) { + try { + await runIndependently( + [ + () => cleanupOldAssets( session, assets, keep ), + () => removeRequestLabel( session ), + ], + 'Old-asset cleanup and label removal both failed.' + ); + } catch ( error ) { + error.preserveReadyComment = true; + throw error; + } +} + +async function publishCandidate( session, metadata, uploadedAssets ) { + const candidateFile = path.join( + session.handoffDirectory, + metadata.snapshotFilename + ); + await validateCandidateFile( metadata, candidateFile ); + const release = await ensureRelease( session ); + const oldAssets = await session.api.listReleaseAssets( release.id ); + const snapshotBytes = await readFile( candidateFile ); + await session.authorize(); + const snapshotAsset = await session.api.uploadReleaseAsset( + release.id, + metadata.snapshotFilename, + snapshotBytes, + 'application/zip' + ); + uploadedAssets.push( snapshotAsset ); + const published = createPublishedMetadata( + metadata, + session.repository, + session.now() + ); + const publishedBytes = Buffer.from( + `${ JSON.stringify( published, null, 2 ) }\n` + ); + await session.authorize(); + const metadataAsset = await session.api.uploadReleaseAsset( + release.id, + metadataAssetName( metadata.snapshotFilename ), + publishedBytes, + 'application/json' + ); + uploadedAssets.push( metadataAsset ); + const preview = await loadPublishedPreview( + session.repository, + session.context.pullRequestNumber, + metadataAsset, + snapshotAsset, + { + sourceRepository: session.context.sourceRepository, + sourceSha: session.context.sourceSha, + fetchImplementation: session.fetchImplementation, + } + ); + await upsertComment( + session, + renderPreviewComment( { + status: 'ready', + preview, + runUrl: session.context.runUrl, + } ) + ); + const keep = new Set( [ snapshotAsset.name, metadataAsset.name ] ); + await finishReady( session, oldAssets, keep ); + return { status: 'ready', preview }; +} + +async function reuseCandidate( session, metadata ) { + const release = await session.api.getRelease(); + if ( ! release ) { + throw new Error( 'Reusable release is missing.' ); + } + const assets = await session.api.listReleaseAssets( release.id ); + const snapshotAsset = assets.find( + ( asset ) => asset.name === metadata.reusedSnapshot.assetName + ); + const metadataAsset = assets.find( + ( asset ) => asset.name === metadata.reusedSnapshot.metadataAssetName + ); + if ( ! snapshotAsset || ! metadataAsset ) { + throw new Error( 'Reusable release assets are missing.' ); + } + const preview = await loadPublishedPreview( + session.repository, + session.context.pullRequestNumber, + metadataAsset, + snapshotAsset, + { + sourceRepository: session.context.sourceRepository, + sourceSha: session.context.sourceSha, + fetchImplementation: session.fetchImplementation, + } + ); + for ( const name of [ + 'snapshotFilename', + 'snapshotBytes', + 'snapshotSha256', + 'generationTimestamp', + 'dependencyManifestDigest', + ] ) { + if ( preview[ name ] !== metadata[ name ] ) { + throw new Error( + `Reusable ${ name } does not match public metadata.` + ); + } + } + await upsertComment( + session, + renderPreviewComment( { + status: 'ready', + preview, + runUrl: session.context.runUrl, + } ) + ); + await finishReady( + session, + assets, + new Set( [ snapshotAsset.name, metadataAsset.name ] ) + ); + return { status: 'reused', preview }; +} + +export async function publishPullRequest( options ) { + const session = await establishSession( options ); + if ( ! session ) { + return { status: 'superseded' }; + } + const uploadedAssets = []; + try { + await session.authorize(); + if ( options.artifactAvailable === false ) { + throw new Error( 'The publisher handoff artifact is unavailable.' ); + } + const metadata = await readHandoff( session.handoffDirectory ); + const handoff = inspectHandoff( metadata, { + ...session.context, + latestRun: session.context.run, + } ); + if ( handoff.kind === 'failed' ) { + throw new Error( metadata.buildError || 'Preview build failed.' ); + } + if ( handoff.kind === 'invalid' ) { + await reportFailure( session ); + return { status: 'invalid' }; + } + if ( handoff.kind === 'reuse' ) { + return await reuseCandidate( session, metadata ); + } + return await publishCandidate( session, metadata, uploadedAssets ); + } catch ( error ) { + if ( error instanceof SupersededRun ) { + return { status: 'superseded' }; + } + if ( error.preserveReadyComment ) { + throw error; + } + const excluded = new Set( + uploadedAssets.map( ( asset ) => asset.name ) + ); + try { + await cleanupFailedCandidate( session, uploadedAssets ); + } catch ( cleanupError ) { + session.warning( + `Cannot remove failed candidate: ${ cleanupError.message }` + ); + } + try { + await reportFailure( session, excluded ); + } catch ( reportError ) { + if ( reportError instanceof SupersededRun ) { + return { status: 'superseded' }; + } + throw new AggregateError( + [ error, reportError ], + 'Publication and failure reporting both failed.' + ); + } + throw error; + } +} + +async function main() { + const event = JSON.parse( await readFile( process.env.GITHUB_EVENT_PATH ) ); + const result = await publishPullRequest( { + repository: process.env.GITHUB_REPOSITORY, + stagingVariable: process.env.DOCS_PREVIEW_STAGING, + triggerRunId: event.workflow_run.id, + triggerRunAttempt: event.workflow_run.run_attempt, + handoffDirectory: process.argv[ 2 ] || 'handoff', + artifactAvailable: process.env.HANDOFF_DOWNLOAD_RESULT === 'success', + token: process.env.GITHUB_TOKEN, + } ); + console.log( `Code Reference publisher: ${ result.status }.` ); +} + +if ( import.meta.url === pathToFileURL( process.argv[ 1 ] ).href ) { + main().catch( ( error ) => { + console.error( error ); + process.exitCode = 1; + } ); +} From d2a18d618417266b8bf7c9e72ba42e4ea4d59511 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 13:47:46 +0400 Subject: [PATCH 16/51] Build/Test Tools: Test PR publication transactions Publication failures must not erase the last working preview or leave the request label stuck behind unrelated cleanup. Exercise fresh publication, advisory failure, missing artifacts, latest-wins, retry collisions, public delivery failure, reuse, and ordinary transport errors. Mutation ordering is inferred from final state and injected failures rather than a separate operation log. The product boundary is still covered. --- .../test/publish.test.mjs | 450 ++++++++++++++++++ 1 file changed, 450 insertions(+) create mode 100644 .github/scripts/docs-playground-preview/test/publish.test.mjs diff --git a/.github/scripts/docs-playground-preview/test/publish.test.mjs b/.github/scripts/docs-playground-preview/test/publish.test.mjs new file mode 100644 index 0000000000000..f68fa13c3cf79 --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/publish.test.mjs @@ -0,0 +1,450 @@ +import assert from 'node:assert/strict'; +import { createHash } from 'node:crypto'; +import { mkdtemp, writeFile } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { test } from 'node:test'; + +import { createPublishedMetadata } from '../lib/publisher.mjs'; +import { + PLAYGROUND_ORIGIN, + createReuseHandoff, + metadataAssetName, + releaseAssetUrl, + snapshotAssetName, +} from '../lib/publication.mjs'; +import { publishPullRequest } from '../publish.mjs'; + +const repository = 'WordPress/wordpress-develop'; +const sourceRepository = 'contributor/wordpress-develop'; +const sha = 'a'.repeat( 40 ); +const snapshotBytes = Buffer.from( 'snapshot' ); + +function run() { + return { + id: 456, + run_attempt: 1, + name: 'Code Reference Playground Preview Build', + event: 'pull_request', + status: 'completed', + head_sha: sha, + head_branch: 'feature', + head_repository: { + full_name: sourceRepository, + owner: { login: 'contributor' }, + }, + }; +} + +function pullRequest() { + return { + number: 123, + state: 'open', + base: { ref: 'trunk' }, + head: { + sha, + ref: 'feature', + repo: { full_name: sourceRepository }, + }, + labels: [ { name: 'docs-preview' } ], + }; +} + +function buildMetadata( overrides = {} ) { + const identity = { + pullRequestNumber: 123, + sourceSha: sha, + workflowRunId: 456, + workflowRunAttempt: 1, + }; + return { + schemaVersion: 1, + sourceRepository, + pullRequestNumber: 123, + sourceSha: sha, + workflowRunId: '456', + workflowRunAttempt: 1, + runUrl: `${ + releaseAssetUrl( repository, 'x' ).split( '/releases/' )[ 0 ] + }/actions/runs/456`, + resolvedWordPressBeta: { + channel: 'beta', + version: '7.2-beta1', + downloadUrl: + 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', + }, + phpVersion: '8.4', + dependencyManifestDigest: 'b'.repeat( 64 ), + snapshotFilename: snapshotAssetName( identity ), + snapshotBytes: snapshotBytes.byteLength, + snapshotSha256: createHash( 'sha256' ) + .update( snapshotBytes ) + .digest( 'hex' ), + buildStatus: 'success', + validationStatus: 'passed', + validationFailures: [], + generationTimestamp: '2026-08-09T12:34:56.000Z', + ...overrides, + }; +} + +function releaseAsset( id, name, createdAt = '2026-08-08T13:00:00.000Z' ) { + return { + id, + name, + created_at: createdAt, + browser_download_url: releaseAssetUrl( repository, name ), + }; +} + +class FakeApi { + constructor() { + this.currentRun = run(); + this.currentPullRequest = pullRequest(); + this.latestRun = this.currentRun; + this.release = { id: 9 }; + this.assets = []; + this.metadata = new Map(); + this.assetBytes = new Map(); + this.uploads = []; + this.deleted = []; + this.comments = []; + this.labelRemovals = 0; + this.runReads = 0; + this.deleteError = null; + this.uploadError = null; + } + + async getRun() { + this.runReads++; + return this.currentRun; + } + + async findPullRequestForRun() { + return this.currentPullRequest; + } + + async getPullRequest() { + return this.currentPullRequest; + } + + async latestPreviewRun() { + return this.latestRun; + } + + async getRelease() { + return this.release; + } + + async createRelease() { + this.release = { id: 9 }; + return this.release; + } + + async listReleaseAssets() { + return [ ...this.assets ]; + } + + async uploadReleaseAsset( releaseId, name, bytes, contentType ) { + assert.equal( releaseId, 9 ); + if ( this.uploadError ) { + throw this.uploadError; + } + const asset = { + id: this.assets.length + 10, + name, + created_at: '2026-08-09T13:00:00.000Z', + browser_download_url: releaseAssetUrl( repository, name ), + }; + this.assets.push( asset ); + this.uploads.push( { name, contentType } ); + if ( contentType === 'application/json' ) { + this.metadata.set( name, JSON.parse( bytes.toString() ) ); + } else { + this.assetBytes.set( name, Buffer.from( bytes ) ); + } + return asset; + } + + async deleteReleaseAsset( id ) { + if ( this.deleteError ) { + throw this.deleteError; + } + this.deleted.push( id ); + } + + async findPreviewComment() { + return this.comments[ 0 ] || null; + } + + async createComment( number, body ) { + this.comments = [ { id: 1, number, body } ]; + } + + async updateComment( id, body ) { + this.comments = [ { id, body } ]; + } + + async removeLabel() { + this.labelRemovals++; + } +} + +function publicFetch( api, corrupt = new Set() ) { + return async ( url ) => { + if ( url.includes( 'wordpress-playground-cors-proxy.net' ) ) { + const name = url.split( '/' ).at( -1 ); + const bytes = corrupt.has( name ) + ? Buffer.from( 'corrupt!' ) + : api.assetBytes.get( name ); + return { + status: 200, + headers: { + get: ( header ) => + ( { + 'x-playground-cors-proxy': 'true', + 'access-control-allow-origin': PLAYGROUND_ORIGIN, + } )[ header.toLowerCase() ] || null, + }, + arrayBuffer: async () => bytes, + }; + } + const name = url.split( '/' ).at( -1 ); + return { + status: api.metadata.has( name ) ? 200 : 404, + json: async () => api.metadata.get( name ), + }; + }; +} + +async function handoffDirectory( metadata, includeSnapshot = true ) { + const directory = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-publish-' ) + ); + await writeFile( + path.join( directory, 'build.json' ), + `${ JSON.stringify( metadata ) }\n` + ); + if ( includeSnapshot ) { + await writeFile( + path.join( directory, metadata.snapshotFilename ), + snapshotBytes + ); + } + return directory; +} + +function options( api, directory, extra = {} ) { + return { + repository, + stagingVariable: '', + triggerRunId: 456, + triggerRunAttempt: 1, + handoffDirectory: directory, + artifactAvailable: true, + api, + fetchImplementation: publicFetch( api ), + now: () => '2026-08-09T13:00:00.000Z', + warning: () => {}, + ...extra, + }; +} + +function installPublishedPreview( + api, + build, + content = snapshotBytes, + publishedAt = '2026-08-08T13:00:00.000Z' +) { + const published = createPublishedMetadata( build, repository, publishedAt ); + const metadataName = metadataAssetName( build.snapshotFilename ); + api.assets.push( + releaseAsset( 1, build.snapshotFilename, publishedAt ), + releaseAsset( 2, metadataName, publishedAt ) + ); + api.metadata.set( metadataName, published ); + api.assetBytes.set( build.snapshotFilename, content ); + return published; +} + +test( 'a candidate is public before the comment moves and old assets delete', async () => { + const api = new FakeApi(); + const metadata = buildMetadata(); + const directory = await handoffDirectory( metadata ); + const result = await publishPullRequest( options( api, directory ) ); + assert.equal( result.status, 'ready' ); + assert.deepEqual( + api.uploads.map( ( upload ) => upload.contentType ), + [ 'application/zip', 'application/json' ] + ); + assert.match( api.comments[ 0 ].body, /Status:\*\* Ready/ ); + assert.match( api.comments[ 0 ].body, /blueprint-url=/ ); + assert.equal( api.labelRemovals, 1 ); + assert.ok( api.runReads >= 5 ); +} ); + +test( 'an advisory validation failure updates the comment but publishes nothing', async () => { + const api = new FakeApi(); + const metadata = buildMetadata( { validationStatus: 'failed' } ); + const directory = await handoffDirectory( metadata, false ); + const result = await publishPullRequest( options( api, directory ) ); + assert.equal( result.status, 'invalid' ); + assert.equal( api.uploads.length, 0 ); + assert.match( api.comments[ 0 ].body, /Latest attempt failed/ ); + assert.equal( api.labelRemovals, 1 ); +} ); + +test( 'a missing handoff always fails after its terminal comment', async () => { + const api = new FakeApi(); + const directory = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-missing-' ) + ); + await assert.rejects( + publishPullRequest( + options( api, directory, { artifactAvailable: false } ) + ), + /artifact is unavailable/ + ); + assert.match( api.comments[ 0 ].body, /Latest attempt failed/ ); + assert.equal( api.labelRemovals, 1 ); +} ); + +test( 'a superseded run makes no mutation', async () => { + const api = new FakeApi(); + api.latestRun = { ...api.currentRun, id: 789 }; + const directory = await handoffDirectory( buildMetadata() ); + const result = await publishPullRequest( options( api, directory ) ); + assert.equal( result.status, 'superseded' ); + assert.equal( api.uploads.length, 0 ); + assert.equal( api.comments.length, 0 ); + assert.equal( api.labelRemovals, 0 ); +} ); + +test( 'public delivery failure keeps the previous healthy preview', async () => { + const api = new FakeApi(); + const oldBytes = Buffer.from( 'old snapshot' ); + const oldBuild = buildMetadata( { + sourceSha: 'c'.repeat( 40 ), + workflowRunId: '300', + snapshotFilename: snapshotAssetName( { + pullRequestNumber: 123, + sourceSha: 'c'.repeat( 40 ), + workflowRunId: 300, + workflowRunAttempt: 1, + } ), + snapshotBytes: oldBytes.byteLength, + snapshotSha256: createHash( 'sha256' ) + .update( oldBytes ) + .digest( 'hex' ), + } ); + installPublishedPreview( api, oldBuild, oldBytes ); + + const current = buildMetadata(); + const directory = await handoffDirectory( current ); + await assert.rejects( + publishPullRequest( + options( api, directory, { + fetchImplementation: publicFetch( + api, + new Set( [ current.snapshotFilename ] ) + ), + } ) + ), + /digest/ + ); + assert.deepEqual( + api.deleted.sort( ( left, right ) => left - right ), + [ 12, 13 ] + ); + assert.match( api.comments[ 0 ].body, /Latest successful docs preview/ ); + assert.match( api.comments[ 0 ].body, new RegExp( 'c'.repeat( 40 ) ) ); +} ); + +test( 'a retry cannot delete a same-name preview it did not upload', async () => { + const api = new FakeApi(); + const current = buildMetadata(); + installPublishedPreview( api, current ); + api.uploadError = new Error( 'already_exists' ); + const directory = await handoffDirectory( current ); + await assert.rejects( + publishPullRequest( options( api, directory ) ), + /already_exists/ + ); + assert.deepEqual( api.deleted, [] ); + assert.match( api.comments[ 0 ].body, /Latest successful docs preview/ ); +} ); + +test( 'a healthy same-SHA preview is reused without uploading it again', async () => { + const api = new FakeApi(); + const oldBuild = buildMetadata( { + workflowRunId: '300', + workflowRunAttempt: 1, + runUrl: `https://github.com/${ repository }/actions/runs/300`, + snapshotFilename: snapshotAssetName( { + pullRequestNumber: 123, + sourceSha: sha, + workflowRunId: 300, + workflowRunAttempt: 1, + } ), + } ); + const oldPublished = installPublishedPreview( api, oldBuild ); + api.assets.push( + releaseAsset( + 3, + `code-reference-pr-123-${ 'c'.repeat( 40 ) }-200-1.zip`, + '2026-08-07T13:00:00.000Z' + ) + ); + const handoff = createReuseHandoff( oldPublished, { + sourceRepository, + pullRequestNumber: 123, + sourceSha: sha, + workflowRunId: 456, + workflowRunAttempt: 1, + runUrl: `https://github.com/${ repository }/actions/runs/456`, + maximumBytes: 104857600, + } ); + const directory = await handoffDirectory( handoff, false ); + const result = await publishPullRequest( options( api, directory ) ); + assert.equal( result.status, 'reused' ); + assert.equal( api.uploads.length, 0 ); + assert.deepEqual( api.deleted, [ 3 ] ); + assert.match( api.comments[ 0 ].body, /Status:\*\* Ready/ ); + assert.equal( api.labelRemovals, 1 ); +} ); + +test( 'cleanup failure cannot replace an already-ready comment', async () => { + const api = new FakeApi(); + api.assets.push( + releaseAsset( + 1, + `code-reference-pr-123-${ 'c'.repeat( 40 ) }-300-1.zip` + ) + ); + api.deleteError = new Error( 'cleanup failed' ); + const directory = await handoffDirectory( buildMetadata() ); + await assert.rejects( + publishPullRequest( options( api, directory ) ), + /cleanup failed/ + ); + assert.match( api.comments[ 0 ].body, /Status:\*\* Ready/ ); + assert.doesNotMatch( api.comments[ 0 ].body, /Latest attempt failed/ ); + assert.equal( api.labelRemovals, 1 ); +} ); + +test( 'GitHub read failures remain visible publication failures', async () => { + const api = new FakeApi(); + api.getPullRequest = async () => { + throw new Error( 'GitHub is unavailable' ); + }; + const directory = await handoffDirectory( buildMetadata() ); + await assert.rejects( + publishPullRequest( options( api, directory ) ), + ( error ) => { + assert.ok( error instanceof AggregateError ); + assert.match( error.errors[ 0 ].message, /GitHub is unavailable/ ); + return true; + } + ); + assert.equal( api.comments.length, 0 ); +} ); From 7242c105cca63f7defc96d7ec85e808913c59eb0 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 13:53:39 +0400 Subject: [PATCH 17/51] Build/Test Tools: Publish Code Reference PR snapshots The build job cannot publish with repository authority. Run the publisher from default-branch code, bind the handoff to the exact workflow attempt and pull request, and validate public delivery before moving the sticky comment. GitHub still emits workflow runs when the build job is skipped. Ignore those exact attempts and exclude them from latest-wins selection or an unrelated label consumes the real preview request. --- .../docs-playground-preview/lib/github.mjs | 24 +++++++- .../docs-playground-preview/publish.mjs | 3 + .../test/github.test.mjs | 42 +++++++++++++ .../test/publish.test.mjs | 15 +++++ .../docs-playground-preview-publish.yml | 61 +++++++++++++++++++ 5 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/docs-playground-preview-publish.yml diff --git a/.github/scripts/docs-playground-preview/lib/github.mjs b/.github/scripts/docs-playground-preview/lib/github.mjs index a186c75d9400e..ec9fb544055c2 100644 --- a/.github/scripts/docs-playground-preview/lib/github.mjs +++ b/.github/scripts/docs-playground-preview/lib/github.mjs @@ -3,6 +3,7 @@ import { COMMENT_MARKER, RELEASE_TAG } from './publication.mjs'; const API = 'https://api.github.com'; const UPLOADS = 'https://uploads.github.com'; const BUILD_WORKFLOW = 'docs-playground-preview-build.yml'; +const BUILD_JOB = 'Build Code Reference snapshot'; function query( values ) { const parameters = new URLSearchParams(); @@ -129,10 +130,27 @@ export class GitHubApi { run.head_repository?.full_name ) .sort( ( left, right ) => right.id - left.id ); - if ( ! matching[ 0 ] ) { - throw new Error( 'No current docs preview run matches this head.' ); + for ( const candidate of matching ) { + const current = await this.getRun( candidate.id ); + if ( ! ( await this.isSkippedPreviewBuild( current ) ) ) { + return current; + } + } + throw new Error( 'No current docs preview build matches this head.' ); + } + + async isSkippedPreviewBuild( run ) { + const jobs = await this.pages( + `/repos/${ this.repository }/actions/runs/${ run.id }/attempts/${ run.run_attempt }/jobs`, + 'jobs' + ); + const matches = jobs.filter( ( job ) => job.name === BUILD_JOB ); + if ( matches.length !== 1 ) { + throw new Error( + `Expected one Code Reference build job; found ${ matches.length }.` + ); } - return this.getRun( matching[ 0 ].id ); + return matches[ 0 ].conclusion === 'skipped'; } getRelease() { diff --git a/.github/scripts/docs-playground-preview/publish.mjs b/.github/scripts/docs-playground-preview/publish.mjs index ef24ae6f29a9b..1f6529cd2edaa 100644 --- a/.github/scripts/docs-playground-preview/publish.mjs +++ b/.github/scripts/docs-playground-preview/publish.mjs @@ -300,6 +300,9 @@ export async function publishPullRequest( options ) { if ( ! session ) { return { status: 'superseded' }; } + if ( await session.api.isSkippedPreviewBuild( session.context.run ) ) { + return { status: 'ignored' }; + } const uploadedAssets = []; try { await session.authorize(); diff --git a/.github/scripts/docs-playground-preview/test/github.test.mjs b/.github/scripts/docs-playground-preview/test/github.test.mjs index abbbfbefd9013..76aabd956a6b2 100644 --- a/.github/scripts/docs-playground-preview/test/github.test.mjs +++ b/.github/scripts/docs-playground-preview/test/github.test.mjs @@ -107,6 +107,16 @@ test( 'latest run lookup binds SHA, branch, repository, and current attempt', as ], } ); } + if ( url.includes( '/jobs?' ) ) { + return response( { + jobs: [ + { + name: 'Build Code Reference snapshot', + conclusion: 'success', + }, + ], + } ); + } return response( run( { id: 456, run_attempt: 2 } ) ); } ); @@ -114,6 +124,38 @@ test( 'latest run lookup binds SHA, branch, repository, and current attempt', as assert.equal( latest.run_attempt, 2 ); assert.match( calls[ 0 ], /head_sha=/ ); assert.match( calls[ 1 ], /actions\/runs\/456$/ ); + assert.match( calls[ 2 ], /runs\/456\/attempts\/2\/jobs/ ); +} ); + +test( 'skipped trigger runs neither publish nor supersede a build', async () => { + const api = new GitHubApi( + 'WordPress/wordpress-develop', + 'token', + async ( url ) => { + if ( url.includes( '/actions/workflows/' ) ) { + return response( { + workflow_runs: [ run( { id: 789 } ), run() ], + } ); + } + if ( url.includes( '/jobs?' ) ) { + return response( { + jobs: [ + { + name: 'Build Code Reference snapshot', + conclusion: url.includes( '/789/' ) + ? 'skipped' + : 'success', + }, + ], + } ); + } + return response( + url.endsWith( '/789' ) ? run( { id: 789 } ) : run() + ); + } + ); + assert.equal( ( await api.latestPreviewRun( run() ) ).id, 456 ); + assert.equal( await api.isSkippedPreviewBuild( run( { id: 789 } ) ), true ); } ); test( 'release operations keep immutable names and bytes', async () => { diff --git a/.github/scripts/docs-playground-preview/test/publish.test.mjs b/.github/scripts/docs-playground-preview/test/publish.test.mjs index f68fa13c3cf79..db94c1e4c536e 100644 --- a/.github/scripts/docs-playground-preview/test/publish.test.mjs +++ b/.github/scripts/docs-playground-preview/test/publish.test.mjs @@ -132,6 +132,10 @@ class FakeApi { return this.latestRun; } + async isSkippedPreviewBuild() { + return false; + } + async getRelease() { return this.release; } @@ -320,6 +324,17 @@ test( 'a superseded run makes no mutation', async () => { assert.equal( api.labelRemovals, 0 ); } ); +test( 'a skipped trigger run makes no mutation', async () => { + const api = new FakeApi(); + api.isSkippedPreviewBuild = async () => true; + const directory = await handoffDirectory( buildMetadata() ); + const result = await publishPullRequest( options( api, directory ) ); + assert.equal( result.status, 'ignored' ); + assert.equal( api.uploads.length, 0 ); + assert.equal( api.comments.length, 0 ); + assert.equal( api.labelRemovals, 0 ); +} ); + test( 'public delivery failure keeps the previous healthy preview', async () => { const api = new FakeApi(); const oldBytes = Buffer.from( 'old snapshot' ); diff --git a/.github/workflows/docs-playground-preview-publish.yml b/.github/workflows/docs-playground-preview-publish.yml new file mode 100644 index 0000000000000..f17070aa916f2 --- /dev/null +++ b/.github/workflows/docs-playground-preview-publish.yml @@ -0,0 +1,61 @@ +name: Code Reference Playground Preview Publish + +on: # zizmor: ignore[dangerous-triggers] Only default-branch code validates and publishes the untrusted handoff. + workflow_run: + workflows: + - 'Code Reference Playground Preview Build' + types: + - completed + +# A fork head branch identifies one trunk pull request even when GitHub omits the +# pull_requests array. Runs queue instead of interrupting a publication halfway. +concurrency: + group: ${{ github.workflow }}-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} + cancel-in-progress: false + +permissions: {} + +jobs: + publish: + name: Publish Code Reference snapshot + if: >- + github.event.workflow_run.event == 'pull_request' && + ( github.repository == 'WordPress/wordpress-develop' || + ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + actions: read + contents: write + issues: write + pull-requests: read + steps: + - name: Check out trusted default-branch publisher code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.repository.default_branch }} + persist-credentials: false + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + + - name: Set up the pinned Node version + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '20.20.2' + + - name: Download the exact untrusted handoff + id: download + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: docs-preview-handoff-${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }} + path: handoff + github-token: ${{ github.token }} + run-id: ${{ github.event.workflow_run.id }} + digest-mismatch: error + + - name: Validate and publish the handoff + env: + DOCS_PREVIEW_STAGING: ${{ vars.DOCS_PREVIEW_STAGING }} + GITHUB_TOKEN: ${{ github.token }} + HANDOFF_DOWNLOAD_RESULT: ${{ steps.download.outcome }} + run: node .github/scripts/docs-playground-preview/publish.mjs handoff From 36f133904ef3fa1e5a3ffc4e90a4b3732a5a8720 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 13:56:12 +0400 Subject: [PATCH 18/51] Build/Test Tools: Add PR cache cleanup transport Pull request caches live under refs/pull//merge. List that exact scope and delete only returned cache IDs; a broad cache deletion can destroy the shared trunk base. The cache assertions share the release transport test. That is test organization, not a boundary gap. --- .../scripts/docs-playground-preview/lib/github.mjs | 14 ++++++++++++++ .../docs-playground-preview/test/github.test.mjs | 7 +++++++ 2 files changed, 21 insertions(+) diff --git a/.github/scripts/docs-playground-preview/lib/github.mjs b/.github/scripts/docs-playground-preview/lib/github.mjs index ec9fb544055c2..916730f808079 100644 --- a/.github/scripts/docs-playground-preview/lib/github.mjs +++ b/.github/scripts/docs-playground-preview/lib/github.mjs @@ -201,6 +201,20 @@ export class GitHubApi { ); } + listActionCaches( ref ) { + return this.pages( + `/repos/${ this.repository }/actions/caches?${ query( { ref } ) }`, + 'actions_caches' + ); + } + + deleteActionCache( cacheId ) { + return this.request( + `/repos/${ this.repository }/actions/caches/${ cacheId }`, + { method: 'DELETE' } + ); + } + async findPreviewComment( pullRequestNumber ) { const comments = await this.pages( `/repos/${ this.repository }/issues/${ pullRequestNumber }/comments` diff --git a/.github/scripts/docs-playground-preview/test/github.test.mjs b/.github/scripts/docs-playground-preview/test/github.test.mjs index 76aabd956a6b2..21e6334c53ef7 100644 --- a/.github/scripts/docs-playground-preview/test/github.test.mjs +++ b/.github/scripts/docs-playground-preview/test/github.test.mjs @@ -168,6 +168,9 @@ test( 'release operations keep immutable names and bytes', async () => { if ( options.method === 'DELETE' ) { return response( null, 204 ); } + if ( url.includes( '/actions/caches?' ) ) { + return response( { actions_caches: [] } ); + } return response( { id: 9, name: 'snapshot.zip' } ); } ); @@ -179,10 +182,14 @@ test( 'release operations keep immutable names and bytes', async () => { 'application/zip' ); await api.deleteReleaseAsset( 10 ); + await api.listActionCaches( 'refs/pull/123/merge' ); + await api.deleteActionCache( 11 ); assert.equal( calls[ 0 ].options.json.tag_name, RELEASE_TAG ); assert.match( calls[ 1 ].url, /^https:\/\/uploads\.github\.com/ ); assert.equal( calls[ 1 ].options.body.toString(), 'snapshot' ); assert.equal( calls[ 2 ].options.method, 'DELETE' ); + assert.match( calls[ 3 ].url, /ref=refs%2Fpull%2F123%2Fmerge/ ); + assert.equal( calls[ 4 ].options.method, 'DELETE' ); } ); test( 'only the marked bot comment is updated', async () => { From 744e374d7b9000c4f26cdb112bd05b8f521a2018 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 13:58:42 +0400 Subject: [PATCH 19/51] Build/Test Tools: Manage Code Reference PR lifecycle A new unlabeled commit makes the published preview stale; closing the pull request removes only that PR's release assets and merge-ref docs caches. Recheck the exact head or closed state before every mutation. Do not mark the comment expired until every preview asset is gone. The tests assert final deletion sets rather than a separate ordering log; the transaction order remains explicit in the driver. --- .../docs-playground-preview/lifecycle.mjs | 237 +++++++++++++++ .../test/lifecycle.test.mjs | 271 ++++++++++++++++++ 2 files changed, 508 insertions(+) create mode 100644 .github/scripts/docs-playground-preview/lifecycle.mjs create mode 100644 .github/scripts/docs-playground-preview/test/lifecycle.test.mjs diff --git a/.github/scripts/docs-playground-preview/lifecycle.mjs b/.github/scripts/docs-playground-preview/lifecycle.mjs new file mode 100644 index 0000000000000..f5a5dbacd53bb --- /dev/null +++ b/.github/scripts/docs-playground-preview/lifecycle.mjs @@ -0,0 +1,237 @@ +#!/usr/bin/env node + +import { readFile } from 'node:fs/promises'; +import { pathToFileURL } from 'node:url'; + +import { isDeploymentEnabled } from './lib/config.mjs'; +import { GitHubApi } from './lib/github.mjs'; +import { findPreviousPreview } from './lib/published.mjs'; +import { renderPreviewComment } from './lib/publisher.mjs'; + +const FULL_COMMIT = /^[0-9a-f]{40}$/; +const CACHE_PREFIX = 'docs-preview-base-'; + +class LifecycleSuperseded extends Error {} + +function positiveInteger( value, label ) { + const number = Number( value ); + if ( ! Number.isSafeInteger( number ) || number < 1 ) { + throw new Error( `${ label } must be a positive integer.` ); + } + return number; +} + +function lifecycleContext( options ) { + if ( + ! isDeploymentEnabled( options.repository, options.stagingVariable ) + ) { + throw new Error( + 'Docs preview lifecycle is disabled in this repository.' + ); + } + const action = options.event?.action; + if ( action !== 'synchronize' && action !== 'closed' ) { + throw new Error( `Unsupported pull request action ${ action }.` ); + } + const pullRequest = options.event.pull_request; + const pullRequestNumber = positiveInteger( + pullRequest?.number, + 'Pull request number' + ); + if ( + pullRequest.base?.ref !== 'trunk' || + ! FULL_COMMIT.test( pullRequest.head?.sha || '' ) + ) { + throw new Error( 'Pull request lifecycle identity is invalid.' ); + } + const sourceRepository = pullRequest.head.repo?.full_name || null; + if ( action === 'synchronize' && ! sourceRepository ) { + throw new Error( 'Open pull request head repository is missing.' ); + } + return { + action, + pullRequestNumber, + sourceRepository, + sourceSha: pullRequest.head.sha, + }; +} + +function assertCurrent( context, pullRequest, state, labelMustBeAbsent ) { + if ( + pullRequest.number !== context.pullRequestNumber || + pullRequest.state !== state || + pullRequest.base?.ref !== 'trunk' || + pullRequest.head?.sha !== context.sourceSha || + ( context.sourceRepository && + pullRequest.head.repo?.full_name !== context.sourceRepository ) || + ( labelMustBeAbsent && + pullRequest.labels?.some( + ( label ) => label.name === 'docs-preview' + ) ) + ) { + throw new Error( 'A newer pull request state superseded this event.' ); + } + return pullRequest; +} + +function sessionFrom( options ) { + const context = lifecycleContext( options ); + const api = + options.api || + new GitHubApi( + options.repository, + options.token, + options.fetchImplementation + ); + const authorize = async ( state, labelMustBeAbsent = false ) => { + const pullRequest = await api.getPullRequest( + context.pullRequestNumber + ); + try { + return assertCurrent( + context, + pullRequest, + state, + labelMustBeAbsent + ); + } catch ( error ) { + throw new LifecycleSuperseded( error.message ); + } + }; + return { + api, + context, + repository: options.repository, + fetchImplementation: options.fetchImplementation || globalThis.fetch, + warning: options.warning || console.warn, + authorize, + }; +} + +async function markStale( session ) { + try { + await session.authorize( 'open', true ); + } catch ( error ) { + if ( error instanceof LifecycleSuperseded ) { + return { status: 'ignored' }; + } + throw error; + } + const comment = await session.api.findPreviewComment( + session.context.pullRequestNumber + ); + if ( ! comment ) { + return { status: 'ignored' }; + } + const preview = await findPreviousPreview( session ); + if ( ! preview ) { + return { status: 'unavailable' }; + } + await session.authorize( 'open', true ); + await session.api.updateComment( + comment.id, + renderPreviewComment( { + status: 'stale', + preview, + currentRepository: session.context.sourceRepository, + currentSha: session.context.sourceSha, + } ) + ); + return { status: 'stale' }; +} + +async function settle( operations ) { + const errors = []; + for ( const operation of operations ) { + try { + await operation(); + } catch ( error ) { + errors.push( error ); + } + } + return errors; +} + +async function expire( session ) { + try { + await session.authorize( 'closed' ); + } catch ( error ) { + if ( error instanceof LifecycleSuperseded ) { + return { status: 'superseded' }; + } + throw error; + } + const release = await session.api.getRelease(); + const prefix = `code-reference-pr-${ session.context.pullRequestNumber }-`; + const assets = release + ? ( await session.api.listReleaseAssets( release.id ) ).filter( + ( asset ) => asset.name.startsWith( prefix ) + ) + : []; + const cacheRef = `refs/pull/${ session.context.pullRequestNumber }/merge`; + const caches = ( await session.api.listActionCaches( cacheRef ) ).filter( + ( cache ) => + cache.ref === cacheRef && cache.key.startsWith( CACHE_PREFIX ) + ); + const comment = await session.api.findPreviewComment( + session.context.pullRequestNumber + ); + const assetErrors = await settle( + assets.map( ( asset ) => async () => { + await session.authorize( 'closed' ); + await session.api.deleteReleaseAsset( asset.id ); + } ) + ); + const cacheErrors = await settle( + caches.map( ( cache ) => async () => { + await session.authorize( 'closed' ); + await session.api.deleteActionCache( cache.id ); + } ) + ); + let commentErrors = []; + if ( comment && assetErrors.length === 0 ) { + commentErrors = await settle( [ + async () => { + await session.authorize( 'closed' ); + await session.api.updateComment( + comment.id, + renderPreviewComment( { status: 'expired' } ) + ); + }, + ] ); + } + const errors = [ ...assetErrors, ...cacheErrors, ...commentErrors ]; + if ( errors.length > 0 ) { + throw new AggregateError( errors, 'Pull request cleanup failed.' ); + } + return { + status: 'expired', + deletedAssets: assets.length, + deletedCaches: caches.length, + }; +} + +export async function managePullRequest( options ) { + const session = sessionFrom( options ); + return session.context.action === 'synchronize' + ? markStale( session ) + : expire( session ); +} + +async function main() { + const event = JSON.parse( await readFile( process.env.GITHUB_EVENT_PATH ) ); + const result = await managePullRequest( { + repository: process.env.GITHUB_REPOSITORY, + stagingVariable: process.env.DOCS_PREVIEW_STAGING, + token: process.env.GITHUB_TOKEN, + event, + } ); + console.log( `Code Reference lifecycle: ${ result.status }.` ); +} + +if ( import.meta.url === pathToFileURL( process.argv[ 1 ] ).href ) { + main().catch( ( error ) => { + console.error( error ); + process.exitCode = 1; + } ); +} diff --git a/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs b/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs new file mode 100644 index 0000000000000..363f1a9cf4a4a --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs @@ -0,0 +1,271 @@ +import assert from 'node:assert/strict'; +import { createHash } from 'node:crypto'; +import { test } from 'node:test'; + +import { managePullRequest } from '../lifecycle.mjs'; +import { createPublishedMetadata } from '../lib/publisher.mjs'; +import { + COMMENT_MARKER, + PLAYGROUND_ORIGIN, + metadataAssetName, + releaseAssetUrl, + snapshotAssetName, +} from '../lib/publication.mjs'; + +const repository = 'WordPress/wordpress-develop'; +const sourceRepository = 'contributor/wordpress-develop'; +const currentSha = 'a'.repeat( 40 ); +const previewSha = 'c'.repeat( 40 ); +const snapshotBytes = Buffer.from( 'snapshot' ); + +function pullRequest( state = 'open', labels = [] ) { + return { + number: 123, + state, + base: { ref: 'trunk' }, + head: { + sha: currentSha, + repo: { full_name: sourceRepository }, + }, + labels, + }; +} + +function event( action, state = action === 'closed' ? 'closed' : 'open' ) { + return { action, pull_request: pullRequest( state ) }; +} + +function previewMetadata() { + const snapshotFilename = snapshotAssetName( { + pullRequestNumber: 123, + sourceSha: previewSha, + workflowRunId: 300, + workflowRunAttempt: 1, + } ); + return createPublishedMetadata( + { + schemaVersion: 1, + sourceRepository, + pullRequestNumber: 123, + sourceSha: previewSha, + workflowRunId: '300', + workflowRunAttempt: 1, + runUrl: `https://github.com/${ repository }/actions/runs/300`, + resolvedWordPressBeta: { + channel: 'beta', + version: '7.2-beta1', + downloadUrl: + 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', + }, + phpVersion: '8.4', + dependencyManifestDigest: 'b'.repeat( 64 ), + snapshotFilename, + snapshotBytes: snapshotBytes.byteLength, + snapshotSha256: createHash( 'sha256' ) + .update( snapshotBytes ) + .digest( 'hex' ), + buildStatus: 'success', + validationStatus: 'passed', + generationTimestamp: '2026-08-08T12:00:00.000Z', + }, + repository, + '2026-08-08T13:00:00.000Z' + ); +} + +function releaseAsset( id, name ) { + return { + id, + name, + created_at: '2026-08-08T13:00:00.000Z', + browser_download_url: releaseAssetUrl( repository, name ), + }; +} + +class FakeApi { + constructor() { + this.currentPullRequest = pullRequest(); + this.release = { id: 9 }; + this.assets = []; + this.caches = []; + this.metadata = new Map(); + this.assetBytes = new Map(); + this.comment = { id: 20, body: `${ COMMENT_MARKER }\nReady` }; + this.deletedAssets = []; + this.deletedCaches = []; + this.commentBodies = []; + this.failedAsset = null; + this.cacheRef = null; + } + + async getPullRequest() { + return this.currentPullRequest; + } + + async getRelease() { + return this.release; + } + + async listReleaseAssets() { + return [ ...this.assets ]; + } + + async listActionCaches( ref ) { + this.cacheRef = ref; + return [ ...this.caches ]; + } + + async findPreviewComment() { + return this.comment; + } + + async updateComment( id, body ) { + assert.equal( id, this.comment.id ); + this.commentBodies.push( body ); + this.comment = { id, body }; + } + + async deleteReleaseAsset( id ) { + if ( id === this.failedAsset ) { + throw new Error( 'release deletion failed' ); + } + this.deletedAssets.push( id ); + } + + async deleteActionCache( id ) { + this.deletedCaches.push( id ); + } +} + +function installPreview( api ) { + const metadata = previewMetadata(); + const metadataName = metadataAssetName( metadata.snapshotFilename ); + api.assets.push( + releaseAsset( 1, metadata.snapshotFilename ), + releaseAsset( 2, metadataName ) + ); + api.metadata.set( metadataName, metadata ); + api.assetBytes.set( metadata.snapshotFilename, snapshotBytes ); + return metadata; +} + +function publicFetch( api ) { + return async ( url ) => { + if ( url.includes( 'wordpress-playground-cors-proxy.net' ) ) { + const name = url.split( '/' ).at( -1 ); + return { + status: 200, + headers: { + get: ( header ) => + ( { + 'x-playground-cors-proxy': 'true', + 'access-control-allow-origin': PLAYGROUND_ORIGIN, + } )[ header.toLowerCase() ] || null, + }, + arrayBuffer: async () => api.assetBytes.get( name ), + }; + } + const name = url.split( '/' ).at( -1 ); + return { + status: api.metadata.has( name ) ? 200 : 404, + json: async () => api.metadata.get( name ), + }; + }; +} + +function options( api, lifecycleEvent ) { + return { + repository, + stagingVariable: '', + event: lifecycleEvent, + api, + fetchImplementation: publicFetch( api ), + warning: () => {}, + }; +} + +test( 'a later unlabeled commit marks the healthy preview stale', async () => { + const api = new FakeApi(); + installPreview( api ); + const result = await managePullRequest( + options( api, event( 'synchronize' ) ) + ); + assert.equal( result.status, 'stale' ); + assert.match( api.comment.body, /Status:\*\* Stale/ ); + assert.match( api.comment.body, new RegExp( previewSha ) ); + assert.match( api.comment.body, new RegExp( currentSha ) ); + assert.match( api.comment.body, /add the `docs-preview` label again/i ); +} ); + +test( 'a labeled synchronize event leaves the publisher in charge', async () => { + const api = new FakeApi(); + api.currentPullRequest = pullRequest( 'open', [ + { name: 'docs-preview' }, + ] ); + const result = await managePullRequest( + options( api, event( 'synchronize' ) ) + ); + assert.equal( result.status, 'ignored' ); + assert.equal( api.commentBodies.length, 0 ); +} ); + +test( 'closing a PR deletes only its preview assets and scoped docs caches', async () => { + const api = new FakeApi(); + api.currentPullRequest = pullRequest( 'closed' ); + installPreview( api ); + api.assets.push( + releaseAsset( 3, `code-reference-pr-124-${ previewSha }-300-1.zip` ), + releaseAsset( 4, 'code-reference-trunk-snapshot.zip' ) + ); + api.caches.push( + { + id: 10, + ref: 'refs/pull/123/merge', + key: `docs-preview-base-v1-${ 'd'.repeat( 64 ) }`, + }, + { + id: 11, + ref: 'refs/pull/123/merge', + key: 'unrelated-cache', + }, + { + id: 12, + ref: 'refs/heads/trunk', + key: `docs-preview-base-v1-${ 'e'.repeat( 64 ) }`, + } + ); + const result = await managePullRequest( options( api, event( 'closed' ) ) ); + assert.equal( result.status, 'expired' ); + assert.deepEqual( api.deletedAssets, [ 1, 2 ] ); + assert.deepEqual( api.deletedCaches, [ 10 ] ); + assert.equal( api.cacheRef, 'refs/pull/123/merge' ); + assert.match( api.comment.body, /Status:\*\* Expired/ ); +} ); + +test( 'asset cleanup failure preserves a comment that still links live assets', async () => { + const api = new FakeApi(); + api.currentPullRequest = pullRequest( 'closed' ); + installPreview( api ); + api.failedAsset = 1; + api.caches.push( { + id: 10, + ref: 'refs/pull/123/merge', + key: `docs-preview-base-v1-${ 'd'.repeat( 64 ) }`, + } ); + await assert.rejects( + managePullRequest( options( api, event( 'closed' ) ) ), + /Pull request cleanup failed/ + ); + assert.deepEqual( api.deletedAssets, [ 2 ] ); + assert.deepEqual( api.deletedCaches, [ 10 ] ); + assert.equal( api.commentBodies.length, 0 ); +} ); + +test( 'a reopened PR supersedes close cleanup before any deletion', async () => { + const api = new FakeApi(); + installPreview( api ); + const result = await managePullRequest( options( api, event( 'closed' ) ) ); + assert.equal( result.status, 'superseded' ); + assert.deepEqual( api.deletedAssets, [] ); + assert.deepEqual( api.deletedCaches, [] ); +} ); From 785e45cc06cb85f00a3c37fcba25cc2c9de84009 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 13:59:27 +0400 Subject: [PATCH 20/51] Build/Test Tools: Activate Code Reference PR lifecycle Fork pull requests need trusted stale and cleanup mutations without ever running their code. Use default-branch lifecycle code, separate stale permissions from destructive cleanup, and cancel obsolete events per pull request. --- .../docs-playground-preview-lifecycle.yml | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 .github/workflows/docs-playground-preview-lifecycle.yml diff --git a/.github/workflows/docs-playground-preview-lifecycle.yml b/.github/workflows/docs-playground-preview-lifecycle.yml new file mode 100644 index 0000000000000..09a2834fde15e --- /dev/null +++ b/.github/workflows/docs-playground-preview-lifecycle.yml @@ -0,0 +1,79 @@ +name: Code Reference Playground Preview Lifecycle + +on: # zizmor: ignore[dangerous-triggers] Both jobs check out and run only trusted default-branch code. + pull_request_target: + branches: + - trunk + types: + - synchronize + - closed + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: {} + +jobs: + stale: + name: Mark Code Reference preview stale + if: >- + github.event.action == 'synchronize' && + ( github.repository == 'WordPress/wordpress-develop' || + ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + issues: write + pull-requests: read + steps: + - name: Check out trusted default-branch lifecycle code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.repository.default_branch }} + persist-credentials: false + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + + - name: Set up the pinned Node version + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '20.20.2' + + - name: Mark the published preview stale + env: + DOCS_PREVIEW_STAGING: ${{ vars.DOCS_PREVIEW_STAGING }} + GITHUB_TOKEN: ${{ github.token }} + run: node .github/scripts/docs-playground-preview/lifecycle.mjs + + cleanup: + name: Expire Code Reference preview + if: >- + github.event.action == 'closed' && + ( github.repository == 'WordPress/wordpress-develop' || + ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) + runs-on: ubuntu-24.04 + timeout-minutes: 15 + permissions: + actions: write + contents: write + issues: write + pull-requests: read + steps: + - name: Check out trusted default-branch lifecycle code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.repository.default_branch }} + persist-credentials: false + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + + - name: Set up the pinned Node version + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '20.20.2' + + - name: Delete the pull request preview and caches + env: + DOCS_PREVIEW_STAGING: ${{ vars.DOCS_PREVIEW_STAGING }} + GITHUB_TOKEN: ${{ github.token }} + run: node .github/scripts/docs-playground-preview/lifecycle.mjs From 60970c1e1f9000c25d695de9cc6ce434d9920555 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 14:15:36 +0400 Subject: [PATCH 21/51] Build/Test Tools: Close PR lifecycle races A missing label is not proof that an old stale run still owns the comment. Bind stale mutation to the absence of a non-skipped current-SHA build, and retain the prior attempted SHA when no healthy preview exists. Close cleanup must run after both the cache-writing build and trusted publisher. Cancel the build, queue cleanup durably behind publication, then enumerate assets and PR-scoped caches. Public proxy retries and reuse-validator deduplication remain follow-ups. Partial asset cleanup deliberately leaves the historical comment unchanged until a rerun completes deletion. --- .../docs-playground-preview/lib/github.mjs | 10 +++- .../docs-playground-preview/lib/publisher.mjs | 44 ++++++++++++++- .../docs-playground-preview/lifecycle.mjs | 54 ++++++++++++++++--- .../test/lifecycle.test.mjs | 42 ++++++++++++++- .../test/publisher.test.mjs | 14 +++++ .../docs-playground-preview-build.yml | 2 +- .../docs-playground-preview-lifecycle.yml | 27 ++++++++-- .../docs-playground-preview-publish.yml | 4 +- 8 files changed, 179 insertions(+), 18 deletions(-) diff --git a/.github/scripts/docs-playground-preview/lib/github.mjs b/.github/scripts/docs-playground-preview/lib/github.mjs index 916730f808079..c9f154bdabc7f 100644 --- a/.github/scripts/docs-playground-preview/lib/github.mjs +++ b/.github/scripts/docs-playground-preview/lib/github.mjs @@ -111,7 +111,7 @@ export class GitHubApi { return matches[ 0 ]; } - async latestPreviewRun( run ) { + async findLatestPreviewRun( run ) { const runs = await this.pages( `/repos/${ this.repository @@ -136,6 +136,14 @@ export class GitHubApi { return current; } } + return null; + } + + async latestPreviewRun( run ) { + const latest = await this.findLatestPreviewRun( run ); + if ( latest ) { + return latest; + } throw new Error( 'No current docs preview build matches this head.' ); } diff --git a/.github/scripts/docs-playground-preview/lib/publisher.mjs b/.github/scripts/docs-playground-preview/lib/publisher.mjs index 074cd99680217..ea3c4c97c6476 100644 --- a/.github/scripts/docs-playground-preview/lib/publisher.mjs +++ b/.github/scripts/docs-playground-preview/lib/publisher.mjs @@ -264,9 +264,29 @@ function previousLink( previous ) { ) }.`; } +function commentHeader( source ) { + const identity = source + ? `\n` + : ''; + return `${ COMMENT_MARKER }${ identity }\n## Code Reference documentation preview`; +} + +export function readPreviewCommentSource( body ) { + if ( typeof body !== 'string' ) { + return null; + } + const match = body.match( + // + ); + return match ? { repository: match[ 1 ], sha: match[ 2 ] } : null; +} + export function renderPreviewComment( state ) { - const header = `${ COMMENT_MARKER }\n## Code Reference documentation preview`; if ( state.status === 'ready' ) { + const header = commentHeader( { + repository: state.preview.sourceRepository, + sha: state.preview.sourceSha, + } ); return `${ header }\n\n**Status:** Ready\n\n[Open Code Reference preview](${ state.preview.publication.playgroundUrl })\n\nSource: ${ commitLink( @@ -277,6 +297,10 @@ export function renderPreviewComment( state ) { } \n[GitHub Actions run](${ state.runUrl })`; } if ( state.status === 'failed' ) { + const header = commentHeader( { + repository: state.sourceRepository, + sha: state.sourceSha, + } ); return `${ header }\n\n**Status:** Latest attempt failed\n\nThe latest attempt for ${ commitLink( state.sourceRepository, state.sourceSha @@ -285,6 +309,10 @@ export function renderPreviewComment( state ) { }).${ previousLink( state.previous ) }`; } if ( state.status === 'stale' ) { + const header = commentHeader( { + repository: state.currentRepository, + sha: state.currentSha, + } ); return `${ header }\n\n**Status:** Stale\n\nThe latest successful preview was built from ${ commitLink( state.preview.sourceRepository, state.preview.sourceSha @@ -295,7 +323,21 @@ export function renderPreviewComment( state ) { state.preview.publication.playgroundUrl })\n\nAdd the \`docs-preview\` label again to build the current commit.`; } + if ( state.status === 'stale-unavailable' ) { + const header = commentHeader( { + repository: state.currentRepository, + sha: state.currentSha, + } ); + return `${ header }\n\n**Status:** Stale\n\nThe latest preview attempt was for ${ commitLink( + state.previousRepository, + state.previousSha + ) }, but no healthy docs preview is available. The pull request is now at ${ commitLink( + state.currentRepository, + state.currentSha + ) }.\n\nAdd the \`docs-preview\` label again to build the current commit.`; + } if ( state.status === 'expired' ) { + const header = commentHeader(); return `${ header }\n\n**Status:** Expired\n\nThis pull request is closed or merged. Its Code Reference preview expired and is no longer live.`; } throw new Error( `Unknown preview comment state ${ state.status }.` ); diff --git a/.github/scripts/docs-playground-preview/lifecycle.mjs b/.github/scripts/docs-playground-preview/lifecycle.mjs index f5a5dbacd53bb..6f2c44bcf33b0 100644 --- a/.github/scripts/docs-playground-preview/lifecycle.mjs +++ b/.github/scripts/docs-playground-preview/lifecycle.mjs @@ -6,7 +6,10 @@ import { pathToFileURL } from 'node:url'; import { isDeploymentEnabled } from './lib/config.mjs'; import { GitHubApi } from './lib/github.mjs'; import { findPreviousPreview } from './lib/published.mjs'; -import { renderPreviewComment } from './lib/publisher.mjs'; +import { + readPreviewCommentSource, + renderPreviewComment, +} from './lib/publisher.mjs'; const FULL_COMMIT = /^[0-9a-f]{40}$/; const CACHE_PREFIX = 'docs-preview-base-'; @@ -50,13 +53,20 @@ function lifecycleContext( options ) { } return { action, + headBranch: pullRequest.head.ref, pullRequestNumber, sourceRepository, sourceSha: pullRequest.head.sha, }; } -function assertCurrent( context, pullRequest, state, labelMustBeAbsent ) { +function assertCurrent( + context, + pullRequest, + state, + labelMustBeAbsent, + currentBuild +) { if ( pullRequest.number !== context.pullRequestNumber || pullRequest.state !== state || @@ -67,7 +77,8 @@ function assertCurrent( context, pullRequest, state, labelMustBeAbsent ) { ( labelMustBeAbsent && pullRequest.labels?.some( ( label ) => label.name === 'docs-preview' - ) ) + ) ) || + currentBuild ) { throw new Error( 'A newer pull request state superseded this event.' ); } @@ -83,16 +94,28 @@ function sessionFrom( options ) { options.token, options.fetchImplementation ); - const authorize = async ( state, labelMustBeAbsent = false ) => { + const authorize = async ( + state, + labelMustBeAbsent = false, + buildMustBeAbsent = false + ) => { const pullRequest = await api.getPullRequest( context.pullRequestNumber ); + const currentBuild = buildMustBeAbsent + ? await api.findLatestPreviewRun( { + head_sha: context.sourceSha, + head_branch: context.headBranch, + head_repository: { full_name: context.sourceRepository }, + } ) + : null; try { return assertCurrent( context, pullRequest, state, - labelMustBeAbsent + labelMustBeAbsent, + currentBuild ); } catch ( error ) { throw new LifecycleSuperseded( error.message ); @@ -110,7 +133,7 @@ function sessionFrom( options ) { async function markStale( session ) { try { - await session.authorize( 'open', true ); + await session.authorize( 'open', true, true ); } catch ( error ) { if ( error instanceof LifecycleSuperseded ) { return { status: 'ignored' }; @@ -125,9 +148,24 @@ async function markStale( session ) { } const preview = await findPreviousPreview( session ); if ( ! preview ) { - return { status: 'unavailable' }; + const previous = readPreviewCommentSource( comment.body ); + if ( ! previous ) { + return { status: 'unavailable' }; + } + await session.authorize( 'open', true, true ); + await session.api.updateComment( + comment.id, + renderPreviewComment( { + status: 'stale-unavailable', + previousRepository: previous.repository, + previousSha: previous.sha, + currentRepository: session.context.sourceRepository, + currentSha: session.context.sourceSha, + } ) + ); + return { status: 'stale' }; } - await session.authorize( 'open', true ); + await session.authorize( 'open', true, true ); await session.api.updateComment( comment.id, renderPreviewComment( { diff --git a/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs b/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs index 363f1a9cf4a4a..d4e68e6c8eb4a 100644 --- a/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs +++ b/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs @@ -3,7 +3,10 @@ import { createHash } from 'node:crypto'; import { test } from 'node:test'; import { managePullRequest } from '../lifecycle.mjs'; -import { createPublishedMetadata } from '../lib/publisher.mjs'; +import { + createPublishedMetadata, + renderPreviewComment, +} from '../lib/publisher.mjs'; import { COMMENT_MARKER, PLAYGROUND_ORIGIN, @@ -25,6 +28,7 @@ function pullRequest( state = 'open', labels = [] ) { base: { ref: 'trunk' }, head: { sha: currentSha, + ref: 'feature', repo: { full_name: sourceRepository }, }, labels, @@ -96,12 +100,17 @@ class FakeApi { this.commentBodies = []; this.failedAsset = null; this.cacheRef = null; + this.previewBuilds = []; } async getPullRequest() { return this.currentPullRequest; } + async findLatestPreviewRun() { + return this.previewBuilds.shift() || null; + } + async getRelease() { return this.release; } @@ -209,6 +218,37 @@ test( 'a labeled synchronize event leaves the publisher in charge', async () => assert.equal( api.commentBodies.length, 0 ); } ); +test( 'a completed current-SHA build supersedes an older stale event', async () => { + const api = new FakeApi(); + installPreview( api ); + api.previewBuilds.push( null, { id: 456 } ); + await assert.rejects( + managePullRequest( options( api, event( 'synchronize' ) ) ), + /superseded/ + ); + assert.equal( api.commentBodies.length, 0 ); +} ); + +test( 'a stale failed attempt still tells the maintainer how to rebuild', async () => { + const api = new FakeApi(); + api.comment.body = renderPreviewComment( { + status: 'failed', + sourceRepository, + sourceSha: previewSha, + at: '2026-08-08T13:00:00.000Z', + runUrl: `https://github.com/${ repository }/actions/runs/300`, + previous: null, + } ); + const result = await managePullRequest( + options( api, event( 'synchronize' ) ) + ); + assert.equal( result.status, 'stale' ); + assert.match( api.comment.body, /no healthy docs preview is available/ ); + assert.match( api.comment.body, new RegExp( previewSha ) ); + assert.match( api.comment.body, new RegExp( currentSha ) ); + assert.match( api.comment.body, /add the `docs-preview` label again/i ); +} ); + test( 'closing a PR deletes only its preview assets and scoped docs caches', async () => { const api = new FakeApi(); api.currentPullRequest = pullRequest( 'closed' ); diff --git a/.github/scripts/docs-playground-preview/test/publisher.test.mjs b/.github/scripts/docs-playground-preview/test/publisher.test.mjs index 965f2ba7ef81f..36e3a6b6ee157 100644 --- a/.github/scripts/docs-playground-preview/test/publisher.test.mjs +++ b/.github/scripts/docs-playground-preview/test/publisher.test.mjs @@ -9,6 +9,7 @@ import { assertLatestAuthorized, createPublishedMetadata, inspectHandoff, + readPreviewCommentSource, renderPreviewComment, validateCandidateFile, validatePublicationContext, @@ -256,6 +257,10 @@ test( 'the sticky comment has ready, failed, stale, and expired states', () => { assert.match( ready, new RegExp( COMMENT_MARKER ) ); assert.match( ready, /Status:\*\* Ready/ ); assert.match( ready, new RegExp( sha ) ); + assert.deepEqual( readPreviewCommentSource( ready ), { + repository: preview.sourceRepository, + sha, + } ); assert.equal( ready.match( /Open Code Reference preview/g ).length, 1 ); const failed = renderPreviewComment( { @@ -279,6 +284,15 @@ test( 'the sticky comment has ready, failed, stale, and expired states', () => { assert.match( stale, /add the `docs-preview` label again/i ); assert.match( stale, new RegExp( sha ) ); assert.match( stale, new RegExp( 'c'.repeat( 40 ) ) ); + const unavailable = renderPreviewComment( { + status: 'stale-unavailable', + previousRepository: preview.sourceRepository, + previousSha: sha, + currentRepository: preview.sourceRepository, + currentSha: 'c'.repeat( 40 ), + } ); + assert.match( unavailable, /no healthy docs preview is available/ ); + assert.match( unavailable, /add the `docs-preview` label again/i ); assert.match( renderPreviewComment( { status: 'expired' } ), diff --git a/.github/workflows/docs-playground-preview-build.yml b/.github/workflows/docs-playground-preview-build.yml index a09a4878e7aa0..9c701b5ff40f8 100644 --- a/.github/workflows/docs-playground-preview-build.yml +++ b/.github/workflows/docs-playground-preview-build.yml @@ -8,7 +8,7 @@ on: # A newer head makes every older build obsolete for this pull request. concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + group: docs-preview-build-pr-${{ github.event.pull_request.number }} cancel-in-progress: true # Pull-request code receives no write authority or repository secrets. diff --git a/.github/workflows/docs-playground-preview-lifecycle.yml b/.github/workflows/docs-playground-preview-lifecycle.yml index 09a2834fde15e..1bb0a238319c4 100644 --- a/.github/workflows/docs-playground-preview-lifecycle.yml +++ b/.github/workflows/docs-playground-preview-lifecycle.yml @@ -8,10 +8,6 @@ on: # zizmor: ignore[dangerous-triggers] Both jobs check out and run only truste - synchronize - closed -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number }} - cancel-in-progress: true - permissions: {} jobs: @@ -23,7 +19,11 @@ jobs: ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) runs-on: ubuntu-24.04 timeout-minutes: 10 + concurrency: + group: docs-preview-stale-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true permissions: + actions: read contents: read issues: write pull-requests: read @@ -46,14 +46,33 @@ jobs: GITHUB_TOKEN: ${{ github.token }} run: node .github/scripts/docs-playground-preview/lifecycle.mjs + quiesce-build: + name: Stop the obsolete Code Reference build + if: >- + github.event.action == 'closed' && + ( github.repository == 'WordPress/wordpress-develop' || + ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) + runs-on: ubuntu-24.04 + timeout-minutes: 5 + concurrency: + group: docs-preview-build-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + steps: + - name: Wait for build cancellation + run: ':' + cleanup: name: Expire Code Reference preview + needs: quiesce-build if: >- github.event.action == 'closed' && ( github.repository == 'WordPress/wordpress-develop' || ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) runs-on: ubuntu-24.04 timeout-minutes: 15 + concurrency: + group: docs-preview-publish-${{ github.event.pull_request.head.repo.full_name }}-${{ github.event.pull_request.head.ref }} + queue: max permissions: actions: write contents: write diff --git a/.github/workflows/docs-playground-preview-publish.yml b/.github/workflows/docs-playground-preview-publish.yml index f17070aa916f2..3254bc4a736da 100644 --- a/.github/workflows/docs-playground-preview-publish.yml +++ b/.github/workflows/docs-playground-preview-publish.yml @@ -10,8 +10,8 @@ on: # zizmor: ignore[dangerous-triggers] Only default-branch code validates and # A fork head branch identifies one trunk pull request even when GitHub omits the # pull_requests array. Runs queue instead of interrupting a publication halfway. concurrency: - group: ${{ github.workflow }}-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} - cancel-in-progress: false + group: docs-preview-publish-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} + queue: max permissions: {} From 15f223780b852336677e4be25ce10961e2ec4ec4 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 14:24:25 +0400 Subject: [PATCH 22/51] Build/Test Tools: Bind stale docs comments to exact state Label absence is not ownership. Compare the exact marked comment before mutation so a newer publisher state wins. Keep the original attempted SHA across stale transitions. Enter build concurrency only from the eligible open-PR job so closed events cannot cancel cleanup. Non-blocking follow-up: the stale lifecycle job retains an unused actions: read permission. --- .../docs-playground-preview/lib/publisher.mjs | 4 +- .../docs-playground-preview/lifecycle.mjs | 49 +++++++++---------- .../test/lifecycle.test.mjs | 25 +++++++--- .../test/publisher.test.mjs | 4 ++ .../docs-playground-preview-build.yml | 10 ++-- 5 files changed, 51 insertions(+), 41 deletions(-) diff --git a/.github/scripts/docs-playground-preview/lib/publisher.mjs b/.github/scripts/docs-playground-preview/lib/publisher.mjs index ea3c4c97c6476..21a3c829aacff 100644 --- a/.github/scripts/docs-playground-preview/lib/publisher.mjs +++ b/.github/scripts/docs-playground-preview/lib/publisher.mjs @@ -325,8 +325,8 @@ export function renderPreviewComment( state ) { } if ( state.status === 'stale-unavailable' ) { const header = commentHeader( { - repository: state.currentRepository, - sha: state.currentSha, + repository: state.previousRepository, + sha: state.previousSha, } ); return `${ header }\n\n**Status:** Stale\n\nThe latest preview attempt was for ${ commitLink( state.previousRepository, diff --git a/.github/scripts/docs-playground-preview/lifecycle.mjs b/.github/scripts/docs-playground-preview/lifecycle.mjs index 6f2c44bcf33b0..ed0b2872a1576 100644 --- a/.github/scripts/docs-playground-preview/lifecycle.mjs +++ b/.github/scripts/docs-playground-preview/lifecycle.mjs @@ -53,20 +53,13 @@ function lifecycleContext( options ) { } return { action, - headBranch: pullRequest.head.ref, pullRequestNumber, sourceRepository, sourceSha: pullRequest.head.sha, }; } -function assertCurrent( - context, - pullRequest, - state, - labelMustBeAbsent, - currentBuild -) { +function assertCurrent( context, pullRequest, state, labelMustBeAbsent ) { if ( pullRequest.number !== context.pullRequestNumber || pullRequest.state !== state || @@ -77,8 +70,7 @@ function assertCurrent( ( labelMustBeAbsent && pullRequest.labels?.some( ( label ) => label.name === 'docs-preview' - ) ) || - currentBuild + ) ) ) { throw new Error( 'A newer pull request state superseded this event.' ); } @@ -94,28 +86,16 @@ function sessionFrom( options ) { options.token, options.fetchImplementation ); - const authorize = async ( - state, - labelMustBeAbsent = false, - buildMustBeAbsent = false - ) => { + const authorize = async ( state, labelMustBeAbsent = false ) => { const pullRequest = await api.getPullRequest( context.pullRequestNumber ); - const currentBuild = buildMustBeAbsent - ? await api.findLatestPreviewRun( { - head_sha: context.sourceSha, - head_branch: context.headBranch, - head_repository: { full_name: context.sourceRepository }, - } ) - : null; try { return assertCurrent( context, pullRequest, state, - labelMustBeAbsent, - currentBuild + labelMustBeAbsent ); } catch ( error ) { throw new LifecycleSuperseded( error.message ); @@ -131,9 +111,24 @@ function sessionFrom( options ) { }; } +async function authorizeStaleMutation( session, expectedComment ) { + await session.authorize( 'open', true ); + const currentComment = await session.api.findPreviewComment( + session.context.pullRequestNumber + ); + if ( + currentComment?.id !== expectedComment.id || + currentComment.body !== expectedComment.body + ) { + throw new LifecycleSuperseded( + 'A newer preview state superseded this stale event.' + ); + } +} + async function markStale( session ) { try { - await session.authorize( 'open', true, true ); + await session.authorize( 'open', true ); } catch ( error ) { if ( error instanceof LifecycleSuperseded ) { return { status: 'ignored' }; @@ -152,7 +147,7 @@ async function markStale( session ) { if ( ! previous ) { return { status: 'unavailable' }; } - await session.authorize( 'open', true, true ); + await authorizeStaleMutation( session, comment ); await session.api.updateComment( comment.id, renderPreviewComment( { @@ -165,7 +160,7 @@ async function markStale( session ) { ); return { status: 'stale' }; } - await session.authorize( 'open', true, true ); + await authorizeStaleMutation( session, comment ); await session.api.updateComment( comment.id, renderPreviewComment( { diff --git a/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs b/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs index d4e68e6c8eb4a..3ebe58fd79cc5 100644 --- a/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs +++ b/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs @@ -100,17 +100,14 @@ class FakeApi { this.commentBodies = []; this.failedAsset = null; this.cacheRef = null; - this.previewBuilds = []; + this.replacementComment = null; + this.commentReads = 0; } async getPullRequest() { return this.currentPullRequest; } - async findLatestPreviewRun() { - return this.previewBuilds.shift() || null; - } - async getRelease() { return this.release; } @@ -125,6 +122,10 @@ class FakeApi { } async findPreviewComment() { + this.commentReads++; + if ( this.commentReads > 1 && this.replacementComment ) { + return this.replacementComment; + } return this.comment; } @@ -218,10 +219,20 @@ test( 'a labeled synchronize event leaves the publisher in charge', async () => assert.equal( api.commentBodies.length, 0 ); } ); -test( 'a completed current-SHA build supersedes an older stale event', async () => { +test( 'a newer terminal comment supersedes an older stale event', async () => { const api = new FakeApi(); installPreview( api ); - api.previewBuilds.push( null, { id: 456 } ); + api.replacementComment = { + id: api.comment.id, + body: renderPreviewComment( { + status: 'failed', + sourceRepository, + sourceSha: currentSha, + at: '2026-08-09T13:00:00.000Z', + runUrl: `https://github.com/${ repository }/actions/runs/456`, + previous: previewMetadata(), + } ), + }; await assert.rejects( managePullRequest( options( api, event( 'synchronize' ) ) ), /superseded/ diff --git a/.github/scripts/docs-playground-preview/test/publisher.test.mjs b/.github/scripts/docs-playground-preview/test/publisher.test.mjs index 36e3a6b6ee157..ba905833445e7 100644 --- a/.github/scripts/docs-playground-preview/test/publisher.test.mjs +++ b/.github/scripts/docs-playground-preview/test/publisher.test.mjs @@ -293,6 +293,10 @@ test( 'the sticky comment has ready, failed, stale, and expired states', () => { } ); assert.match( unavailable, /no healthy docs preview is available/ ); assert.match( unavailable, /add the `docs-preview` label again/i ); + assert.deepEqual( readPreviewCommentSource( unavailable ), { + repository: preview.sourceRepository, + sha, + } ); assert.match( renderPreviewComment( { status: 'expired' } ), diff --git a/.github/workflows/docs-playground-preview-build.yml b/.github/workflows/docs-playground-preview-build.yml index 9c701b5ff40f8..e765c34c1d418 100644 --- a/.github/workflows/docs-playground-preview-build.yml +++ b/.github/workflows/docs-playground-preview-build.yml @@ -6,11 +6,6 @@ on: - trunk types: [ labeled, synchronize ] -# A newer head makes every older build obsolete for this pull request. -concurrency: - group: docs-preview-build-pr-${{ github.event.pull_request.number }} - cancel-in-progress: true - # Pull-request code receives no write authority or repository secrets. permissions: {} @@ -21,8 +16,13 @@ jobs: ( github.repository == 'WordPress/wordpress-develop' || ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) && github.event.pull_request.base.ref == 'trunk' && + github.event.pull_request.state == 'open' && ( ( github.event.action == 'labeled' && github.event.label.name == 'docs-preview' ) || ( github.event.action == 'synchronize' && contains( github.event.pull_request.labels.*.name, 'docs-preview' ) ) ) + # A newer eligible head makes every older build obsolete for this pull request. + concurrency: + group: docs-preview-build-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true runs-on: ubuntu-24.04 timeout-minutes: 60 permissions: From 4c44ebdcca349cdda738e13649f1255fe675b4f1 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 14:36:12 +0400 Subject: [PATCH 23/51] Build/Test Tools: Define trunk docs publication identity The stable link needs an immutable candidate identity before it can move. Bind trunk snapshots to the exact SHA, run, attempt, runtime, and size boundary, then validate the Blueprint through the public proxy. Non-blocking follow-ups: consolidate the duplicated snapshot-size constant and document the public-Blueprint validator's boundary-4 justification. --- .../lib/publication.mjs | 41 ++- .../docs-playground-preview/lib/trunk.mjs | 299 ++++++++++++++++++ .../test/trunk.test.mjs | 271 ++++++++++++++++ 3 files changed, 595 insertions(+), 16 deletions(-) create mode 100644 .github/scripts/docs-playground-preview/lib/trunk.mjs create mode 100644 .github/scripts/docs-playground-preview/test/trunk.test.mjs diff --git a/.github/scripts/docs-playground-preview/lib/publication.mjs b/.github/scripts/docs-playground-preview/lib/publication.mjs index bc37805ff6b44..4dc02eab9e7f7 100644 --- a/.github/scripts/docs-playground-preview/lib/publication.mjs +++ b/.github/scripts/docs-playground-preview/lib/publication.mjs @@ -4,6 +4,7 @@ export const RELEASE_TAG = 'code-reference-playground-preview'; export const COMMENT_MARKER = ''; export const PLAYGROUND_ORIGIN = 'https://playground.wordpress.net'; export const CORS_PROXY = 'https://wordpress-playground-cors-proxy.net/'; +export const SNAPSHOT_BYTES_LIMIT = 104857600; const FULL_COMMIT = /^[0-9a-f]{40}$/; const FULL_DIGEST = /^[0-9a-f]{64}$/; @@ -156,7 +157,7 @@ export async function validatePublicSnapshot( return { bytes: bytes.byteLength, sha256: digest }; } -export function validateReusableMetadata( metadata, expected ) { +export function validatePublishedSnapshotMetadata( metadata, expected ) { if ( ! metadata || typeof metadata !== 'object' || @@ -174,12 +175,9 @@ export function validateReusableMetadata( metadata, expected ) { if ( metadata.sourceRepository !== expected.sourceRepository || metadata.sourceSha !== expected.sourceSha || - metadata.pullRequestNumber !== - positiveInteger( expected.pullRequestNumber, 'pullRequestNumber' ) + metadata.pullRequestNumber !== expected.pullRequestNumber ) { - throw new Error( - 'Published metadata does not match the pull request.' - ); + throw new Error( 'Published metadata does not match its source.' ); } if ( metadata.buildStatus !== 'success' || @@ -197,16 +195,6 @@ export function validateReusableMetadata( metadata, expected ) { ) { throw new Error( 'Published snapshot identity is invalid.' ); } - const prefix = `code-reference-pr-${ metadata.pullRequestNumber }-${ metadata.sourceSha }-`; - if ( - typeof metadata.snapshotFilename !== 'string' || - ! metadata.snapshotFilename.startsWith( prefix ) || - ! /^code-reference-pr-\d+-[0-9a-f]{40}-\d+-\d+\.zip$/.test( - metadata.snapshotFilename - ) - ) { - throw new Error( 'Published snapshot filename is invalid.' ); - } if ( metadata.phpVersion !== '8.4' || metadata.resolvedWordPressBeta?.channel !== 'beta' || @@ -230,6 +218,27 @@ export function validateReusableMetadata( metadata, expected ) { return metadata; } +export function validateReusableMetadata( metadata, expected ) { + validatePublishedSnapshotMetadata( metadata, { + ...expected, + pullRequestNumber: positiveInteger( + expected.pullRequestNumber, + 'pullRequestNumber' + ), + } ); + const prefix = `code-reference-pr-${ metadata.pullRequestNumber }-${ metadata.sourceSha }-`; + if ( + typeof metadata.snapshotFilename !== 'string' || + ! metadata.snapshotFilename.startsWith( prefix ) || + ! /^code-reference-pr-\d+-[0-9a-f]{40}-\d+-\d+\.zip$/.test( + metadata.snapshotFilename + ) + ) { + throw new Error( 'Published snapshot filename is invalid.' ); + } + return metadata; +} + export function createReuseHandoff( published, identity ) { validateReusableMetadata( published, { ...identity, diff --git a/.github/scripts/docs-playground-preview/lib/trunk.mjs b/.github/scripts/docs-playground-preview/lib/trunk.mjs new file mode 100644 index 0000000000000..e04c5ac0a7008 --- /dev/null +++ b/.github/scripts/docs-playground-preview/lib/trunk.mjs @@ -0,0 +1,299 @@ +import { isDeepStrictEqual } from 'node:util'; + +import { isDeploymentEnabled } from './config.mjs'; +import { + PLAYGROUND_ORIGIN, + SNAPSHOT_BYTES_LIMIT, + corsProxyUrl, + createLaunchBlueprint, + playgroundUrl, + releaseAssetUrl, + validatePublishedSnapshotMetadata, +} from './publication.mjs'; + +export const TRUNK_BUILD_WORKFLOW_NAME = + 'Code Reference Playground Preview Build'; +export const TRUNK_POINTER_ASSET = 'code-reference-trunk.json'; + +const BLUEPRINT_SCHEMA = + 'https://playground.wordpress.net/blueprint-schema.json'; +const FULL_COMMIT = /^[0-9a-f]{40}$/; +const MAXIMUM_BLUEPRINT_BYTES = 65536; + +function positiveInteger( value, label ) { + const number = Number( value ); + if ( ! Number.isSafeInteger( number ) || number < 1 ) { + throw new Error( `${ label } must be a positive integer.` ); + } + return number; +} + +function timestamp( value, label ) { + if ( + typeof value !== 'string' || + Number.isNaN( Date.parse( value ) ) || + ! value.endsWith( 'Z' ) + ) { + throw new Error( `${ label } must be a UTC timestamp.` ); + } + return value; +} + +export function trunkSnapshotAssetName( identity ) { + if ( ! FULL_COMMIT.test( identity.sourceSha || '' ) ) { + throw new Error( 'sourceSha must be a full lowercase commit hash.' ); + } + const runId = positiveInteger( identity.workflowRunId, 'workflowRunId' ); + const attempt = positiveInteger( + identity.workflowRunAttempt, + 'workflowRunAttempt' + ); + return `code-reference-trunk-${ identity.sourceSha }-${ runId }-${ attempt }.zip`; +} + +export function trunkPointerCandidateName( identity ) { + return trunkSnapshotAssetName( identity ).replace( + /^code-reference-trunk-(.+)\.zip$/, + 'code-reference-trunk-pointer-$1.json' + ); +} + +export function trunkStableBlueprintUrl( repository ) { + return releaseAssetUrl( repository, TRUNK_POINTER_ASSET ); +} + +export function trunkPlaygroundUrl( repository ) { + return `${ PLAYGROUND_ORIGIN }/?blueprint-url=${ encodeURIComponent( + corsProxyUrl( trunkStableBlueprintUrl( repository ) ) + ) }`; +} + +export function validateTrunkPublicationContext( context ) { + if ( + ! isDeploymentEnabled( context.repository, context.stagingVariable ) + ) { + throw new Error( + 'Docs preview publication is disabled in this repository.' + ); + } + const runId = positiveInteger( context.run?.id, 'Workflow run id' ); + const runAttempt = positiveInteger( + context.run?.run_attempt, + 'Workflow run attempt' + ); + if ( + runId !== positiveInteger( context.triggerRunId, 'Trigger run id' ) || + runAttempt !== + positiveInteger( context.triggerRunAttempt, 'Trigger run attempt' ) + ) { + throw new Error( + 'The triggering workflow attempt is no longer current.' + ); + } + if ( + context.run.name !== TRUNK_BUILD_WORKFLOW_NAME || + context.run.event !== 'push' || + context.run.status !== 'completed' || + context.run.head_branch !== 'trunk' + ) { + throw new Error( 'The trunk source workflow identity is invalid.' ); + } + const sourceSha = context.run.head_sha; + if ( + ! FULL_COMMIT.test( sourceSha || '' ) || + context.run.head_repository?.full_name !== context.repository + ) { + throw new Error( 'The trunk workflow source identity is invalid.' ); + } + return { + ...context, + sourceRepository: context.repository, + sourceSha, + workflowRunId: runId, + workflowRunAttempt: runAttempt, + runUrl: `https://github.com/${ context.repository }/actions/runs/${ runId }`, + }; +} + +export function assertLatestTrunkAuthorized( context ) { + const current = validateTrunkPublicationContext( context ); + if ( + context.latestRun?.id !== current.workflowRunId || + context.latestRun?.run_attempt !== current.workflowRunAttempt || + context.trunkHeadSha !== current.sourceSha + ) { + throw new Error( 'A newer trunk build superseded this attempt.' ); + } + return current; +} + +function validateCommonHandoff( metadata, context ) { + if ( + ! metadata || + typeof metadata !== 'object' || + Array.isArray( metadata ) + ) { + throw new Error( 'Handoff metadata must be an object.' ); + } + if ( + metadata.schemaVersion !== 1 || + metadata.sourceRepository !== context.sourceRepository || + metadata.pullRequestNumber !== null || + metadata.sourceSha !== context.sourceSha || + String( metadata.workflowRunId ) !== String( context.workflowRunId ) || + metadata.workflowRunAttempt !== context.workflowRunAttempt || + metadata.runUrl !== context.runUrl + ) { + throw new Error( 'Handoff metadata does not match the trunk run.' ); + } + timestamp( metadata.generationTimestamp, 'Generation timestamp' ); +} + +export function inspectTrunkHandoff( metadata, rawContext ) { + const context = validateTrunkPublicationContext( rawContext ); + validateCommonHandoff( metadata, context ); + if ( metadata.buildStatus === 'failed' ) { + return { kind: 'failed', metadata, context }; + } + if ( metadata.buildStatus !== 'success' ) { + throw new Error( 'Handoff build status is invalid.' ); + } + if ( metadata.validationStatus === 'failed' ) { + return { kind: 'invalid', metadata, context }; + } + if ( metadata.validationStatus !== 'passed' ) { + throw new Error( 'Handoff validation status is invalid.' ); + } + validatePublishedSnapshotMetadata( metadata, { + sourceRepository: context.sourceRepository, + sourceSha: context.sourceSha, + pullRequestNumber: null, + maximumBytes: SNAPSHOT_BYTES_LIMIT, + } ); + if ( + metadata.handoffType !== undefined || + metadata.snapshotFilename !== trunkSnapshotAssetName( context ) + ) { + throw new Error( 'Candidate trunk snapshot filename is invalid.' ); + } + return { kind: 'candidate', metadata, context }; +} + +export function createTrunkPublishedMetadata( + metadata, + repository, + publishedAt +) { + timestamp( publishedAt, 'Publication timestamp' ); + const snapshotUrl = releaseAssetUrl( + repository, + metadata.snapshotFilename + ); + return { + ...metadata, + publication: { + publishedAt, + snapshotUrl, + snapshotProxyUrl: corsProxyUrl( snapshotUrl ), + playgroundUrl: playgroundUrl( snapshotUrl, { + blueprintSchema: BLUEPRINT_SCHEMA, + phpVersion: metadata.phpVersion, + wordpressVersion: metadata.resolvedWordPressBeta.version, + } ), + stableBlueprintUrl: trunkStableBlueprintUrl( repository ), + stablePlaygroundUrl: trunkPlaygroundUrl( repository ), + }, + }; +} + +export function validatePublishedTrunkMetadata( + metadata, + repository, + sourceSha +) { + validatePublishedSnapshotMetadata( metadata, { + sourceRepository: repository, + sourceSha, + pullRequestNumber: null, + maximumBytes: SNAPSHOT_BYTES_LIMIT, + } ); + if ( + metadata.snapshotFilename !== + trunkSnapshotAssetName( { + sourceSha, + workflowRunId: metadata.workflowRunId, + workflowRunAttempt: metadata.workflowRunAttempt, + } ) + ) { + throw new Error( 'Published trunk snapshot filename is invalid.' ); + } + const snapshotUrl = releaseAssetUrl( + repository, + metadata.snapshotFilename + ); + if ( + metadata.publication?.snapshotUrl !== snapshotUrl || + metadata.publication?.snapshotProxyUrl !== + corsProxyUrl( snapshotUrl ) || + metadata.publication?.playgroundUrl !== + playgroundUrl( snapshotUrl, { + blueprintSchema: BLUEPRINT_SCHEMA, + phpVersion: metadata.phpVersion, + wordpressVersion: metadata.resolvedWordPressBeta.version, + } ) || + metadata.publication?.stableBlueprintUrl !== + trunkStableBlueprintUrl( repository ) || + metadata.publication?.stablePlaygroundUrl !== + trunkPlaygroundUrl( repository ) + ) { + throw new Error( 'Published trunk pointer metadata is invalid.' ); + } + timestamp( metadata.publication?.publishedAt, 'Publication timestamp' ); + return metadata; +} + +export function createTrunkBlueprint( metadata ) { + const blueprint = createLaunchBlueprint( metadata.publication.snapshotUrl, { + blueprintSchema: BLUEPRINT_SCHEMA, + phpVersion: metadata.phpVersion, + wordpressVersion: metadata.resolvedWordPressBeta.version, + } ); + blueprint.meta.description = `Latest successful Core Code Reference from ${ metadata.sourceRepository }@${ metadata.sourceSha }. Generated ${ metadata.generationTimestamp }. ${ metadata.runUrl }`; + return blueprint; +} + +export async function validatePublicBlueprint( + publicUrl, + expected, + fetchImplementation = globalThis.fetch +) { + const response = await fetchImplementation( corsProxyUrl( publicUrl ), { + headers: { Origin: PLAYGROUND_ORIGIN }, + } ); + if ( response.status !== 200 ) { + throw new Error( + `Public Blueprint returned HTTP ${ response.status } through the proxy.` + ); + } + if ( response.headers.get( 'x-playground-cors-proxy' ) !== 'true' ) { + throw new Error( 'Public Blueprint did not come through the proxy.' ); + } + const allowedOrigin = response.headers.get( 'access-control-allow-origin' ); + if ( allowedOrigin !== PLAYGROUND_ORIGIN && allowedOrigin !== '*' ) { + throw new Error( 'Public Blueprint does not allow Playground.' ); + } + const bytes = Buffer.from( await response.arrayBuffer() ); + if ( bytes.byteLength < 1 || bytes.byteLength > MAXIMUM_BLUEPRINT_BYTES ) { + throw new Error( 'Public Blueprint size is outside its boundary.' ); + } + let blueprint; + try { + blueprint = JSON.parse( bytes.toString( 'utf8' ) ); + } catch { + throw new Error( 'Public Blueprint is not JSON.' ); + } + if ( ! isDeepStrictEqual( blueprint, expected ) ) { + throw new Error( 'Public Blueprint does not identify the candidate.' ); + } + return blueprint; +} diff --git a/.github/scripts/docs-playground-preview/test/trunk.test.mjs b/.github/scripts/docs-playground-preview/test/trunk.test.mjs new file mode 100644 index 0000000000000..05c332189e3f8 --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/trunk.test.mjs @@ -0,0 +1,271 @@ +import assert from 'node:assert/strict'; +import { createHash } from 'node:crypto'; +import { test } from 'node:test'; + +import { + PLAYGROUND_ORIGIN, + corsProxyUrl, + releaseAssetUrl, +} from '../lib/publication.mjs'; +import { + TRUNK_POINTER_ASSET, + assertLatestTrunkAuthorized, + createTrunkBlueprint, + createTrunkPublishedMetadata, + inspectTrunkHandoff, + trunkPlaygroundUrl, + trunkPointerCandidateName, + trunkSnapshotAssetName, + trunkStableBlueprintUrl, + validatePublicBlueprint, + validatePublishedTrunkMetadata, + validateTrunkPublicationContext, +} from '../lib/trunk.mjs'; + +const repository = 'WordPress/wordpress-develop'; +const sha = 'a'.repeat( 40 ); +const snapshot = Buffer.from( 'snapshot' ); + +function context( overrides = {} ) { + const run = { + id: 456, + run_attempt: 2, + name: 'Code Reference Playground Preview Build', + event: 'push', + status: 'completed', + head_branch: 'trunk', + head_sha: sha, + head_repository: { full_name: repository }, + }; + return { + repository, + stagingVariable: '', + triggerRunId: 456, + triggerRunAttempt: 2, + run, + latestRun: run, + trunkHeadSha: sha, + ...overrides, + }; +} + +function metadata( overrides = {} ) { + const identity = { + sourceSha: sha, + workflowRunId: 456, + workflowRunAttempt: 2, + }; + return { + schemaVersion: 1, + sourceRepository: repository, + pullRequestNumber: null, + sourceSha: sha, + workflowRunId: '456', + workflowRunAttempt: 2, + runUrl: `https://github.com/${ repository }/actions/runs/456`, + resolvedWordPressBeta: { + channel: 'beta', + version: '7.2-beta1', + downloadUrl: + 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', + }, + phpVersion: '8.4', + dependencyManifestDigest: 'b'.repeat( 64 ), + snapshotFilename: trunkSnapshotAssetName( identity ), + snapshotBytes: snapshot.byteLength, + snapshotSha256: createHash( 'sha256' ) + .update( snapshot ) + .digest( 'hex' ), + buildStatus: 'success', + validationStatus: 'passed', + validationFailures: [], + generationTimestamp: '2026-08-09T12:34:56.000Z', + ...overrides, + }; +} + +function headers( overrides = {} ) { + const values = { + 'x-playground-cors-proxy': 'true', + 'access-control-allow-origin': PLAYGROUND_ORIGIN, + ...overrides, + }; + return { get: ( name ) => values[ name.toLowerCase() ] || null }; +} + +test( 'trunk assets bind the exact SHA, run, and attempt', () => { + const identity = { + sourceSha: sha, + workflowRunId: 456, + workflowRunAttempt: 2, + }; + assert.equal( + trunkSnapshotAssetName( identity ), + `code-reference-trunk-${ sha }-456-2.zip` + ); + assert.equal( + trunkPointerCandidateName( identity ), + `code-reference-trunk-pointer-${ sha }-456-2.json` + ); + assert.throws( + () => trunkSnapshotAssetName( { ...identity, sourceSha: 'short' } ), + /full lowercase commit/ + ); +} ); + +test( 'the stable trunk entry point uses the fixed public Blueprint asset', () => { + const blueprintUrl = releaseAssetUrl( repository, TRUNK_POINTER_ASSET ); + assert.equal( trunkStableBlueprintUrl( repository ), blueprintUrl ); + const launch = new URL( trunkPlaygroundUrl( repository ) ); + assert.equal( launch.origin, PLAYGROUND_ORIGIN ); + assert.equal( + launch.searchParams.get( 'blueprint-url' ), + corsProxyUrl( blueprintUrl ) + ); +} ); + +test( 'trunk publication binds the exact terminal push and latest head', () => { + const checked = validateTrunkPublicationContext( context() ); + assert.equal( checked.sourceSha, sha ); + assert.equal( checked.sourceRepository, repository ); + assert.equal( checked.workflowRunAttempt, 2 ); + assert.equal( assertLatestTrunkAuthorized( context() ).workflowRunId, 456 ); + assert.throws( + () => + validateTrunkPublicationContext( + context( { repository: 'attacker/wordpress-develop' } ) + ), + /publication is disabled/ + ); + assert.throws( + () => + validateTrunkPublicationContext( { + ...context(), + run: { ...context().run, event: 'pull_request' }, + } ), + /source workflow identity/ + ); + assert.throws( + () => + assertLatestTrunkAuthorized( { + ...context(), + trunkHeadSha: 'c'.repeat( 40 ), + } ), + /superseded/ + ); +} ); + +test( 'the trunk handoff accepts only a passed exact-run candidate', () => { + assert.equal( + inspectTrunkHandoff( metadata(), context() ).kind, + 'candidate' + ); + assert.equal( + inspectTrunkHandoff( + metadata( { validationStatus: 'failed' } ), + context() + ).kind, + 'invalid' + ); + assert.equal( + inspectTrunkHandoff( + metadata( { + buildStatus: 'failed', + validationStatus: 'not-run', + } ), + context() + ).kind, + 'failed' + ); + for ( const [ name, value, error ] of [ + [ 'pullRequestNumber', 123, /trunk run/ ], + [ 'sourceSha', 'c'.repeat( 40 ), /trunk run/ ], + [ 'workflowRunAttempt', 1, /trunk run/ ], + [ 'phpVersion', '8.5', /runtime identity/ ], + [ 'dependencyManifestDigest', 'broken', /dependency identity/ ], + [ 'snapshotFilename', 'snapshot.zip', /filename/ ], + [ 'snapshotBytes', 104857601, /snapshot identity/ ], + ] ) { + assert.throws( + () => + inspectTrunkHandoff( + metadata( { [ name ]: value } ), + context() + ), + error + ); + } +} ); + +test( 'published trunk metadata produces an exact immutable Blueprint', () => { + const published = createTrunkPublishedMetadata( + metadata(), + repository, + '2026-08-09T13:00:00.000Z' + ); + assert.equal( + validatePublishedTrunkMetadata( published, repository, sha ), + published + ); + const blueprint = createTrunkBlueprint( published ); + assert.equal( blueprint.landingPage, '/reference/' ); + assert.equal( blueprint.login, false ); + assert.equal( blueprint.features.networking, false ); + assert.equal( + blueprint.steps[ 0 ].zipFile.url, + published.publication.snapshotProxyUrl + ); + assert.match( blueprint.meta.description, new RegExp( sha ) ); + assert.match( blueprint.meta.description, /actions\/runs\/456/ ); + const broken = structuredClone( published ); + broken.publication.stableBlueprintUrl = 'https://example.test/broken'; + assert.throws( + () => validatePublishedTrunkMetadata( broken, repository, sha ), + /pointer metadata/ + ); +} ); + +test( 'the public Blueprint must arrive intact through the Playground proxy', async () => { + const expected = createTrunkBlueprint( + createTrunkPublishedMetadata( + metadata(), + repository, + '2026-08-09T13:00:00.000Z' + ) + ); + const publicUrl = trunkStableBlueprintUrl( repository ); + const fetchImplementation = async ( url, options ) => { + assert.equal( url, corsProxyUrl( publicUrl ) ); + assert.equal( options.headers.Origin, PLAYGROUND_ORIGIN ); + return { + status: 200, + headers: headers(), + arrayBuffer: async () => + Buffer.from( `${ JSON.stringify( expected ) }\n` ), + }; + }; + assert.deepEqual( + await validatePublicBlueprint( + publicUrl, + expected, + fetchImplementation + ), + expected + ); + await assert.rejects( + validatePublicBlueprint( publicUrl, expected, async () => ( { + status: 200, + headers: headers(), + arrayBuffer: async () => Buffer.from( '{}' ), + } ) ), + /does not identify/ + ); + await assert.rejects( + validatePublicBlueprint( publicUrl, expected, async () => ( { + status: 200, + headers: headers( { 'x-playground-cors-proxy': 'false' } ), + arrayBuffer: async () => Buffer.from( '{}' ), + } ) ), + /did not come through/ + ); +} ); From 92db86630006e546c3038e434346d165ff9f9865 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 14:37:50 +0400 Subject: [PATCH 24/51] Build/Test Tools: Add trunk publication transport The publisher cannot trust its trigger once trunk moves. Read the current trunk ref and newest workflow attempt before each mutation, and use GitHub's asset rename operation for the stable pointer transaction. Non-blocking follow-up: add direct coverage for ordering two valid trunk runs. --- .../docs-playground-preview/lib/github.mjs | 38 ++++++++++++ .../test/github.test.mjs | 61 ++++++++++++++++++- 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/.github/scripts/docs-playground-preview/lib/github.mjs b/.github/scripts/docs-playground-preview/lib/github.mjs index c9f154bdabc7f..4be19f8cc74b6 100644 --- a/.github/scripts/docs-playground-preview/lib/github.mjs +++ b/.github/scripts/docs-playground-preview/lib/github.mjs @@ -78,6 +78,13 @@ export class GitHubApi { ); } + async getTrunkHeadSha() { + const reference = await this.request( + `/repos/${ this.repository }/git/ref/heads/trunk` + ); + return reference.object?.sha || null; + } + getPullRequest( number ) { return this.request( `/repos/${ this.repository }/pulls/${ number }` ); } @@ -147,6 +154,30 @@ export class GitHubApi { throw new Error( 'No current docs preview build matches this head.' ); } + async latestTrunkPreviewRun() { + const runs = await this.pages( + `/repos/${ + this.repository + }/actions/workflows/${ BUILD_WORKFLOW }/runs?${ query( { + event: 'push', + branch: 'trunk', + } ) }`, + 'workflow_runs' + ); + const latest = runs + .filter( + ( run ) => + run.event === 'push' && + run.head_branch === 'trunk' && + run.head_repository?.full_name === this.repository + ) + .sort( ( left, right ) => right.id - left.id )[ 0 ]; + if ( ! latest ) { + throw new Error( 'No current trunk docs preview build exists.' ); + } + return this.getRun( latest.id ); + } + async isSkippedPreviewBuild( run ) { const jobs = await this.pages( `/repos/${ this.repository }/actions/runs/${ run.id }/attempts/${ run.run_attempt }/jobs`, @@ -202,6 +233,13 @@ export class GitHubApi { ); } + updateReleaseAsset( assetId, name ) { + return this.request( + `/repos/${ this.repository }/releases/assets/${ assetId }`, + { method: 'PATCH', json: { name } } + ); + } + deleteReleaseAsset( assetId ) { return this.request( `/repos/${ this.repository }/releases/assets/${ assetId }`, diff --git a/.github/scripts/docs-playground-preview/test/github.test.mjs b/.github/scripts/docs-playground-preview/test/github.test.mjs index 21e6334c53ef7..bbdca42be9e96 100644 --- a/.github/scripts/docs-playground-preview/test/github.test.mjs +++ b/.github/scripts/docs-playground-preview/test/github.test.mjs @@ -127,6 +127,58 @@ test( 'latest run lookup binds SHA, branch, repository, and current attempt', as assert.match( calls[ 2 ], /runs\/456\/attempts\/2\/jobs/ ); } ); +test( 'trunk lookup binds the workflow, branch, repository, and current attempt', async () => { + const calls = []; + const api = new GitHubApi( + 'WordPress/wordpress-develop', + 'token', + async ( url ) => { + calls.push( url ); + if ( url.includes( '/git/ref/heads/trunk' ) ) { + return response( { object: { sha: 'a'.repeat( 40 ) } } ); + } + if ( url.includes( '/actions/workflows/' ) ) { + return response( { + workflow_runs: [ + run( { + id: 789, + event: 'push', + head_branch: 'trunk', + head_repository: { + full_name: 'other/wordpress-develop', + }, + } ), + run( { + id: 456, + event: 'push', + head_branch: 'trunk', + head_repository: { + full_name: 'WordPress/wordpress-develop', + }, + } ), + ], + } ); + } + return response( + run( { + id: 456, + run_attempt: 2, + event: 'push', + head_branch: 'trunk', + head_repository: { + full_name: 'WordPress/wordpress-develop', + }, + } ) + ); + } + ); + assert.equal( await api.getTrunkHeadSha(), 'a'.repeat( 40 ) ); + assert.equal( ( await api.latestTrunkPreviewRun() ).run_attempt, 2 ); + assert.match( calls[ 1 ], /event=push/ ); + assert.match( calls[ 1 ], /branch=trunk/ ); + assert.match( calls[ 2 ], /actions\/runs\/456$/ ); +} ); + test( 'skipped trigger runs neither publish nor supersede a build', async () => { const api = new GitHubApi( 'WordPress/wordpress-develop', @@ -181,15 +233,18 @@ test( 'release operations keep immutable names and bytes', async () => { Buffer.from( 'snapshot' ), 'application/zip' ); + await api.updateReleaseAsset( 10, 'renamed.json' ); await api.deleteReleaseAsset( 10 ); await api.listActionCaches( 'refs/pull/123/merge' ); await api.deleteActionCache( 11 ); assert.equal( calls[ 0 ].options.json.tag_name, RELEASE_TAG ); assert.match( calls[ 1 ].url, /^https:\/\/uploads\.github\.com/ ); assert.equal( calls[ 1 ].options.body.toString(), 'snapshot' ); - assert.equal( calls[ 2 ].options.method, 'DELETE' ); - assert.match( calls[ 3 ].url, /ref=refs%2Fpull%2F123%2Fmerge/ ); - assert.equal( calls[ 4 ].options.method, 'DELETE' ); + assert.equal( calls[ 2 ].options.method, 'PATCH' ); + assert.deepEqual( calls[ 2 ].options.json, { name: 'renamed.json' } ); + assert.equal( calls[ 3 ].options.method, 'DELETE' ); + assert.match( calls[ 4 ].url, /ref=refs%2Fpull%2F123%2Fmerge/ ); + assert.equal( calls[ 5 ].options.method, 'DELETE' ); } ); test( 'only the marked bot comment is updated', async () => { From d67ad638655df15311a5ada9b9f9231a2901a804 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 14:54:30 +0400 Subject: [PATCH 25/51] Build/Test Tools: Publish trunk docs transactionally Two asset renames can strand the stable URL between names. Validate an immutable Blueprint commit publicly, move one Git ref atomically, and re-read its SHA before deleting the previous snapshot. Unknown ref state retains both asset generations. The pointer cannot outlive the snapshot it names. Non-blocking follow-ups: exercise the stable branch URL in staging and tolerate unreachable pointer commits from failed attempts. --- .../docs-playground-preview/lib/github.mjs | 54 ++- .../lib/publication.mjs | 7 +- .../docs-playground-preview/lib/trunk.mjs | 30 +- .../docs-playground-preview/publish-trunk.mjs | 337 ++++++++++++++++++ 4 files changed, 415 insertions(+), 13 deletions(-) create mode 100644 .github/scripts/docs-playground-preview/publish-trunk.mjs diff --git a/.github/scripts/docs-playground-preview/lib/github.mjs b/.github/scripts/docs-playground-preview/lib/github.mjs index 4be19f8cc74b6..f4078a40fb1eb 100644 --- a/.github/scripts/docs-playground-preview/lib/github.mjs +++ b/.github/scripts/docs-playground-preview/lib/github.mjs @@ -233,10 +233,58 @@ export class GitHubApi { ); } - updateReleaseAsset( assetId, name ) { + getGitReference( reference ) { return this.request( - `/repos/${ this.repository }/releases/assets/${ assetId }`, - { method: 'PATCH', json: { name } } + `/repos/${ this.repository }/git/ref/${ reference }`, + { allowNotFound: true } + ); + } + + createGitBlob( content ) { + return this.request( `/repos/${ this.repository }/git/blobs`, { + method: 'POST', + json: { content, encoding: 'utf-8' }, + } ); + } + + createGitTree( path, blobSha ) { + return this.request( `/repos/${ this.repository }/git/trees`, { + method: 'POST', + json: { + tree: [ + { + path, + mode: '100644', + type: 'blob', + sha: blobSha, + }, + ], + }, + } ); + } + + createGitCommit( message, treeSha, parentSha = null ) { + return this.request( `/repos/${ this.repository }/git/commits`, { + method: 'POST', + json: { + message, + tree: treeSha, + parents: parentSha ? [ parentSha ] : [], + }, + } ); + } + + createGitReference( reference, sha ) { + return this.request( `/repos/${ this.repository }/git/refs`, { + method: 'POST', + json: { ref: `refs/${ reference }`, sha }, + } ); + } + + updateGitReference( reference, sha ) { + return this.request( + `/repos/${ this.repository }/git/refs/${ reference }`, + { method: 'PATCH', json: { sha, force: true } } ); } diff --git a/.github/scripts/docs-playground-preview/lib/publication.mjs b/.github/scripts/docs-playground-preview/lib/publication.mjs index 4dc02eab9e7f7..24e89063c7501 100644 --- a/.github/scripts/docs-playground-preview/lib/publication.mjs +++ b/.github/scripts/docs-playground-preview/lib/publication.mjs @@ -67,7 +67,12 @@ export function releaseAssetUrl( repository, assetName ) { export function corsProxyUrl( publicUrl ) { const parsed = new URL( publicUrl ); - if ( parsed.protocol !== 'https:' || parsed.hostname !== 'github.com' ) { + if ( + parsed.protocol !== 'https:' || + ! [ 'github.com', 'raw.githubusercontent.com' ].includes( + parsed.hostname + ) + ) { throw new Error( 'Only public GitHub asset URLs may use the proxy.' ); } return `${ CORS_PROXY }${ publicUrl }`; diff --git a/.github/scripts/docs-playground-preview/lib/trunk.mjs b/.github/scripts/docs-playground-preview/lib/trunk.mjs index e04c5ac0a7008..86638cd5cc82d 100644 --- a/.github/scripts/docs-playground-preview/lib/trunk.mjs +++ b/.github/scripts/docs-playground-preview/lib/trunk.mjs @@ -14,6 +14,7 @@ import { export const TRUNK_BUILD_WORKFLOW_NAME = 'Code Reference Playground Preview Build'; export const TRUNK_POINTER_ASSET = 'code-reference-trunk.json'; +export const TRUNK_POINTER_REF = 'heads/docs-preview-code-reference'; const BLUEPRINT_SCHEMA = 'https://playground.wordpress.net/blueprint-schema.json'; @@ -51,15 +52,15 @@ export function trunkSnapshotAssetName( identity ) { return `code-reference-trunk-${ identity.sourceSha }-${ runId }-${ attempt }.zip`; } -export function trunkPointerCandidateName( identity ) { - return trunkSnapshotAssetName( identity ).replace( - /^code-reference-trunk-(.+)\.zip$/, - 'code-reference-trunk-pointer-$1.json' - ); +export function trunkStableBlueprintUrl( repository ) { + return `https://raw.githubusercontent.com/${ repository }/docs-preview-code-reference/${ TRUNK_POINTER_ASSET }`; } -export function trunkStableBlueprintUrl( repository ) { - return releaseAssetUrl( repository, TRUNK_POINTER_ASSET ); +export function trunkBlueprintCommitUrl( repository, commitSha ) { + if ( ! FULL_COMMIT.test( commitSha || '' ) ) { + throw new Error( 'commitSha must be a full lowercase commit hash.' ); + } + return `https://raw.githubusercontent.com/${ repository }/${ commitSha }/${ TRUNK_POINTER_ASSET }`; } export function trunkPlaygroundUrl( repository ) { @@ -262,9 +263,8 @@ export function createTrunkBlueprint( metadata ) { return blueprint; } -export async function validatePublicBlueprint( +export async function readPublicBlueprint( publicUrl, - expected, fetchImplementation = globalThis.fetch ) { const response = await fetchImplementation( corsProxyUrl( publicUrl ), { @@ -292,6 +292,18 @@ export async function validatePublicBlueprint( } catch { throw new Error( 'Public Blueprint is not JSON.' ); } + return blueprint; +} + +export async function validatePublicBlueprint( + publicUrl, + expected, + fetchImplementation = globalThis.fetch +) { + const blueprint = await readPublicBlueprint( + publicUrl, + fetchImplementation + ); if ( ! isDeepStrictEqual( blueprint, expected ) ) { throw new Error( 'Public Blueprint does not identify the candidate.' ); } diff --git a/.github/scripts/docs-playground-preview/publish-trunk.mjs b/.github/scripts/docs-playground-preview/publish-trunk.mjs new file mode 100644 index 0000000000000..f30b737dc6c17 --- /dev/null +++ b/.github/scripts/docs-playground-preview/publish-trunk.mjs @@ -0,0 +1,337 @@ +#!/usr/bin/env node + +import { isDeepStrictEqual } from 'node:util'; +import { readFile } from 'node:fs/promises'; +import { pathToFileURL } from 'node:url'; +import path from 'node:path'; + +import { GitHubApi } from './lib/github.mjs'; +import { + metadataAssetName, + releaseAssetUrl, + validatePublicSnapshot, +} from './lib/publication.mjs'; +import { validateCandidateFile } from './lib/publisher.mjs'; +import { + TRUNK_POINTER_ASSET, + TRUNK_POINTER_REF, + assertLatestTrunkAuthorized, + createTrunkBlueprint, + createTrunkPublishedMetadata, + inspectTrunkHandoff, + trunkBlueprintCommitUrl, + validatePublicBlueprint, + validatePublishedTrunkMetadata, + validateTrunkPublicationContext, +} from './lib/trunk.mjs'; + +class SupersededTrunkRun extends Error {} + +async function readHandoff( directory ) { + return JSON.parse( + await readFile( path.join( directory, 'build.json' ), 'utf8' ) + ); +} + +async function establishSession( options ) { + const api = + options.api || + new GitHubApi( + options.repository, + options.token, + options.fetchImplementation + ); + const run = await api.getRun( options.triggerRunId ); + const base = { + repository: options.repository, + stagingVariable: options.stagingVariable, + triggerRunId: options.triggerRunId, + triggerRunAttempt: options.triggerRunAttempt, + run, + }; + const context = validateTrunkPublicationContext( base ); + return { + api, + context, + repository: options.repository, + fetchImplementation: options.fetchImplementation || globalThis.fetch, + warning: options.warning || console.warn, + now: options.now || ( () => new Date().toISOString() ), + handoffDirectory: path.resolve( options.handoffDirectory ), + authorize: async () => { + const currentRun = await api.getRun( options.triggerRunId ); + const latestRun = await api.latestTrunkPreviewRun(); + const trunkHeadSha = await api.getTrunkHeadSha(); + try { + return assertLatestTrunkAuthorized( { + ...base, + run: currentRun, + latestRun, + trunkHeadSha, + } ); + } catch ( error ) { + throw new SupersededTrunkRun( error.message ); + } + }, + }; +} + +async function ensureRelease( session ) { + let release = await session.api.getRelease(); + if ( release ) { + return release; + } + await session.authorize(); + try { + release = await session.api.createRelease(); + } catch ( error ) { + release = await session.api.getRelease(); + if ( ! release ) { + throw error; + } + } + return release; +} + +async function validatePublicMetadata( session, asset, expected ) { + const response = await session.fetchImplementation( + asset.browser_download_url + ); + if ( response.status !== 200 ) { + throw new Error( + `Public trunk metadata returned HTTP ${ response.status }.` + ); + } + const metadata = validatePublishedTrunkMetadata( + await response.json(), + session.repository, + session.context.sourceSha + ); + if ( ! isDeepStrictEqual( metadata, expected ) ) { + throw new Error( + 'Public trunk metadata does not match the candidate.' + ); + } + return metadata; +} + +async function cleanupUploaded( session, uploaded ) { + for ( const asset of uploaded ) { + try { + await session.api.deleteReleaseAsset( asset.id ); + } catch ( error ) { + session.warning( + `Cannot remove failed trunk candidate ${ asset.name }: ${ error.message }` + ); + } + } +} + +function referenceSha( reference ) { + return reference?.object?.sha || null; +} + +async function createPointerCommit( session, previousRef, blueprint ) { + const content = `${ JSON.stringify( blueprint, null, 2 ) }\n`; + await session.authorize(); + const blob = await session.api.createGitBlob( content ); + await session.authorize(); + const tree = await session.api.createGitTree( + TRUNK_POINTER_ASSET, + blob.sha + ); + await session.authorize(); + const commit = await session.api.createGitCommit( + `Code Reference preview for ${ session.context.sourceSha }`, + tree.sha, + referenceSha( previousRef ) + ); + await validatePublicBlueprint( + trunkBlueprintCommitUrl( session.repository, commit.sha ), + blueprint, + session.fetchImplementation + ); + return commit; +} + +async function resolvePointerMutation( session, previousSha, candidateSha ) { + let current; + try { + current = referenceSha( + await session.api.getGitReference( TRUNK_POINTER_REF ) + ); + } catch ( error ) { + error.pointerStateUnknown = true; + throw error; + } + if ( current === candidateSha ) { + return true; + } + if ( current === previousSha ) { + return false; + } + const error = new Error( 'The stable trunk pointer has unknown state.' ); + error.pointerStateUnknown = true; + throw error; +} + +async function moveStablePointer( session, previousRef, commit, transaction ) { + const previousSha = referenceSha( previousRef ); + let mutationError = null; + try { + await session.authorize(); + if ( previousSha ) { + await session.api.updateGitReference( + TRUNK_POINTER_REF, + commit.sha + ); + } else { + await session.api.createGitReference( + TRUNK_POINTER_REF, + commit.sha + ); + } + } catch ( error ) { + mutationError = error; + } + const moved = await resolvePointerMutation( + session, + previousSha, + commit.sha + ); + if ( ! moved ) { + throw mutationError || new Error( 'The trunk pointer did not move.' ); + } + transaction.pointerPublished = true; +} + +async function cleanupOldTrunkAssets( session, release, keepIds ) { + const assets = await session.api.listReleaseAssets( release.id ); + for ( const asset of assets ) { + if ( + asset.name.startsWith( 'code-reference-trunk-' ) && + ! keepIds.has( asset.id ) + ) { + await session.authorize(); + await session.api.deleteReleaseAsset( asset.id ); + } + } +} + +async function publishCandidate( session, metadata, uploaded, transaction ) { + const candidateFile = path.join( + session.handoffDirectory, + metadata.snapshotFilename + ); + await validateCandidateFile( metadata, candidateFile ); + const release = await ensureRelease( session ); + const previousRef = await session.api.getGitReference( TRUNK_POINTER_REF ); + + const snapshotBytes = await readFile( candidateFile ); + await session.authorize(); + const snapshotAsset = await session.api.uploadReleaseAsset( + release.id, + metadata.snapshotFilename, + snapshotBytes, + 'application/zip' + ); + uploaded.push( snapshotAsset ); + + const published = createTrunkPublishedMetadata( + metadata, + session.repository, + session.now() + ); + const metadataBytes = Buffer.from( + `${ JSON.stringify( published, null, 2 ) }\n` + ); + await session.authorize(); + const metadataAsset = await session.api.uploadReleaseAsset( + release.id, + metadataAssetName( metadata.snapshotFilename ), + metadataBytes, + 'application/json' + ); + uploaded.push( metadataAsset ); + + const blueprint = createTrunkBlueprint( published ); + await validatePublicSnapshot( + releaseAssetUrl( session.repository, snapshotAsset.name ), + { + bytes: metadata.snapshotBytes, + sha256: metadata.snapshotSha256, + maximumBytes: 104857600, + }, + session.fetchImplementation + ); + await validatePublicMetadata( session, metadataAsset, published ); + const pointerCommit = await createPointerCommit( + session, + previousRef, + blueprint + ); + await moveStablePointer( session, previousRef, pointerCommit, transaction ); + await cleanupOldTrunkAssets( + session, + release, + new Set( [ snapshotAsset.id, metadataAsset.id ] ) + ); + return { status: 'ready', published, pointerCommit: pointerCommit.sha }; +} + +export async function publishTrunk( options ) { + const session = await establishSession( options ); + const uploaded = []; + const transaction = { pointerPublished: false }; + try { + await session.authorize(); + if ( options.artifactAvailable === false ) { + throw new Error( 'The trunk publisher handoff is unavailable.' ); + } + const metadata = await readHandoff( session.handoffDirectory ); + const handoff = inspectTrunkHandoff( metadata, session.context ); + if ( handoff.kind === 'failed' ) { + throw new Error( + metadata.buildError || 'Trunk preview build failed.' + ); + } + if ( handoff.kind === 'invalid' ) { + return { status: 'invalid' }; + } + return await publishCandidate( + session, + metadata, + uploaded, + transaction + ); + } catch ( error ) { + if ( ! transaction.pointerPublished && ! error.pointerStateUnknown ) { + await cleanupUploaded( session, uploaded ); + } + if ( error instanceof SupersededTrunkRun ) { + return { status: 'superseded' }; + } + throw error; + } +} + +async function main() { + const event = JSON.parse( await readFile( process.env.GITHUB_EVENT_PATH ) ); + const result = await publishTrunk( { + repository: process.env.GITHUB_REPOSITORY, + stagingVariable: process.env.DOCS_PREVIEW_STAGING, + triggerRunId: event.workflow_run.id, + triggerRunAttempt: event.workflow_run.run_attempt, + handoffDirectory: process.argv[ 2 ] || 'handoff', + artifactAvailable: process.env.HANDOFF_DOWNLOAD_RESULT === 'success', + token: process.env.GITHUB_TOKEN, + } ); + console.log( `Code Reference trunk publisher: ${ result.status }.` ); +} + +if ( import.meta.url === pathToFileURL( process.argv[ 1 ] ).href ) { + main().catch( ( error ) => { + console.error( error ); + process.exitCode = 1; + } ); +} From 00c09d5fa95326bfdf5280550004ea238a296ed9 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 14:55:18 +0400 Subject: [PATCH 26/51] Build/Test Tools: Test trunk publication recovery The pointer transaction is only useful if failures cannot erase its target. Cover public candidate rejection, atomic-ref ambiguity, unknown ref state, cleanup failure, and strict mutation ordering. Non-blocking follow-ups: assert metadata-fetch ordering and unchanged ref identity in invalid terminal cases directly. --- .../test/github.test.mjs | 55 +- .../test/publish-trunk.test.mjs | 486 ++++++++++++++++++ .../test/trunk.test.mjs | 16 +- 3 files changed, 542 insertions(+), 15 deletions(-) create mode 100644 .github/scripts/docs-playground-preview/test/publish-trunk.test.mjs diff --git a/.github/scripts/docs-playground-preview/test/github.test.mjs b/.github/scripts/docs-playground-preview/test/github.test.mjs index bbdca42be9e96..0c445548cb38e 100644 --- a/.github/scripts/docs-playground-preview/test/github.test.mjs +++ b/.github/scripts/docs-playground-preview/test/github.test.mjs @@ -233,18 +233,61 @@ test( 'release operations keep immutable names and bytes', async () => { Buffer.from( 'snapshot' ), 'application/zip' ); - await api.updateReleaseAsset( 10, 'renamed.json' ); await api.deleteReleaseAsset( 10 ); await api.listActionCaches( 'refs/pull/123/merge' ); await api.deleteActionCache( 11 ); assert.equal( calls[ 0 ].options.json.tag_name, RELEASE_TAG ); assert.match( calls[ 1 ].url, /^https:\/\/uploads\.github\.com/ ); assert.equal( calls[ 1 ].options.body.toString(), 'snapshot' ); - assert.equal( calls[ 2 ].options.method, 'PATCH' ); - assert.deepEqual( calls[ 2 ].options.json, { name: 'renamed.json' } ); - assert.equal( calls[ 3 ].options.method, 'DELETE' ); - assert.match( calls[ 4 ].url, /ref=refs%2Fpull%2F123%2Fmerge/ ); - assert.equal( calls[ 5 ].options.method, 'DELETE' ); + assert.equal( calls[ 2 ].options.method, 'DELETE' ); + assert.match( calls[ 3 ].url, /ref=refs%2Fpull%2F123%2Fmerge/ ); + assert.equal( calls[ 4 ].options.method, 'DELETE' ); +} ); + +test( 'Git object operations create and atomically move a pointer ref', async () => { + const calls = []; + const api = new GitHubApi( + 'WordPress/wordpress-develop', + 'token', + async ( url, options ) => { + calls.push( { url, options } ); + return response( { + sha: 'a'.repeat( 40 ), + object: { sha: 'b'.repeat( 40 ) }, + } ); + } + ); + await api.getGitReference( 'heads/docs-preview-code-reference' ); + await api.createGitBlob( '{"blueprint":true}\n' ); + await api.createGitTree( 'code-reference-trunk.json', 'a'.repeat( 40 ) ); + await api.createGitCommit( + 'Update Code Reference preview', + 'b'.repeat( 40 ), + 'c'.repeat( 40 ) + ); + await api.createGitReference( + 'heads/docs-preview-code-reference', + 'd'.repeat( 40 ) + ); + await api.updateGitReference( + 'heads/docs-preview-code-reference', + 'e'.repeat( 40 ) + ); + assert.match( calls[ 0 ].url, /git\/ref\/heads\/docs-preview/ ); + assert.deepEqual( calls[ 1 ].options.json, { + content: '{"blueprint":true}\n', + encoding: 'utf-8', + } ); + assert.equal( calls[ 2 ].options.json.tree[ 0 ].mode, '100644' ); + assert.deepEqual( calls[ 3 ].options.json.parents, [ 'c'.repeat( 40 ) ] ); + assert.equal( + calls[ 4 ].options.json.ref, + 'refs/heads/docs-preview-code-reference' + ); + assert.deepEqual( calls[ 5 ].options.json, { + sha: 'e'.repeat( 40 ), + force: true, + } ); } ); test( 'only the marked bot comment is updated', async () => { diff --git a/.github/scripts/docs-playground-preview/test/publish-trunk.test.mjs b/.github/scripts/docs-playground-preview/test/publish-trunk.test.mjs new file mode 100644 index 0000000000000..7e873c8682efb --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/publish-trunk.test.mjs @@ -0,0 +1,486 @@ +import assert from 'node:assert/strict'; +import { createHash } from 'node:crypto'; +import { mkdtemp, writeFile } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { test } from 'node:test'; + +import { + PLAYGROUND_ORIGIN, + metadataAssetName, + releaseAssetUrl, +} from '../lib/publication.mjs'; +import { + TRUNK_POINTER_REF, + createTrunkBlueprint, + createTrunkPublishedMetadata, + trunkSnapshotAssetName, +} from '../lib/trunk.mjs'; +import { publishTrunk } from '../publish-trunk.mjs'; + +const repository = 'WordPress/wordpress-develop'; +const sha = 'a'.repeat( 40 ); +const snapshotBytes = Buffer.from( 'snapshot' ); + +function run( overrides = {} ) { + return { + id: 456, + run_attempt: 1, + name: 'Code Reference Playground Preview Build', + event: 'push', + status: 'completed', + head_branch: 'trunk', + head_sha: sha, + head_repository: { full_name: repository }, + ...overrides, + }; +} + +function buildMetadata( overrides = {} ) { + const sourceSha = overrides.sourceSha || sha; + const workflowRunId = overrides.workflowRunId || '456'; + const workflowRunAttempt = overrides.workflowRunAttempt || 1; + const bytes = overrides.bytes || snapshotBytes; + return { + schemaVersion: 1, + sourceRepository: repository, + pullRequestNumber: null, + sourceSha, + workflowRunId, + workflowRunAttempt, + runUrl: `https://github.com/${ repository }/actions/runs/${ workflowRunId }`, + resolvedWordPressBeta: { + channel: 'beta', + version: '7.2-beta1', + downloadUrl: + 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', + }, + phpVersion: '8.4', + dependencyManifestDigest: 'b'.repeat( 64 ), + snapshotFilename: trunkSnapshotAssetName( { + sourceSha, + workflowRunId, + workflowRunAttempt, + } ), + snapshotBytes: bytes.byteLength, + snapshotSha256: createHash( 'sha256' ).update( bytes ).digest( 'hex' ), + buildStatus: 'success', + validationStatus: 'passed', + validationFailures: [], + generationTimestamp: '2026-08-09T12:34:56.000Z', + ...overrides, + }; +} + +class FakeApi { + constructor() { + this.currentRun = run(); + this.latestRun = this.currentRun; + this.trunkHeadSha = sha; + this.release = { id: 9 }; + this.assets = []; + this.assetBytes = new Map(); + this.deleted = []; + this.events = []; + this.nextId = 10; + this.deleteErrorId = null; + this.refSha = null; + this.previousRefSha = null; + this.commits = new Map(); + this.pendingBlueprint = null; + this.refReads = 0; + this.refUpdateFailure = null; + this.failRefResolution = false; + } + + async getRun() { + return this.currentRun; + } + + async latestTrunkPreviewRun() { + return this.latestRun; + } + + async getTrunkHeadSha() { + return this.trunkHeadSha; + } + + async getRelease() { + return this.release; + } + + async createRelease() { + this.release = { id: 9 }; + return this.release; + } + + async listReleaseAssets() { + return this.assets.map( ( asset ) => ( { ...asset } ) ); + } + + async uploadReleaseAsset( releaseId, name, bytes, contentType ) { + assert.equal( releaseId, 9 ); + const asset = { + id: this.nextId++, + name, + content_type: contentType, + browser_download_url: releaseAssetUrl( repository, name ), + }; + this.assets.push( asset ); + this.assetBytes.set( asset.id, Buffer.from( bytes ) ); + this.events.push( `upload:${ name }` ); + return { ...asset }; + } + + async getGitReference( reference ) { + assert.equal( reference, TRUNK_POINTER_REF ); + this.refReads++; + this.events.push( `git:read-ref:${ this.refSha }` ); + if ( this.failRefResolution && this.refReads > 1 ) { + throw new Error( 'ref resolution failed' ); + } + return this.refSha ? { object: { sha: this.refSha } } : null; + } + + async createGitBlob( content ) { + this.pendingBlueprint = content; + this.events.push( 'git:blob' ); + return { sha: 'd'.repeat( 40 ) }; + } + + async createGitTree() { + this.events.push( 'git:tree' ); + return { sha: 'e'.repeat( 40 ) }; + } + + async createGitCommit() { + const sha = 'f'.repeat( 40 ); + this.commits.set( sha, Buffer.from( this.pendingBlueprint ) ); + this.events.push( 'git:commit' ); + return { sha }; + } + + async createGitReference( reference, sha ) { + assert.equal( reference, TRUNK_POINTER_REF ); + this.refSha = sha; + this.events.push( `git:create-ref:${ sha }` ); + } + + async updateGitReference( reference, sha ) { + assert.equal( reference, TRUNK_POINTER_REF ); + if ( this.refUpdateFailure === 'before' ) { + throw new Error( 'ref update failed' ); + } + this.refSha = sha; + this.events.push( `git:update-ref:${ sha }` ); + if ( this.refUpdateFailure === 'after' ) { + throw new Error( 'ref update response failed' ); + } + } + + async deleteReleaseAsset( id ) { + if ( id === this.deleteErrorId ) { + throw new Error( 'cleanup failed' ); + } + this.deleted.push( id ); + this.events.push( `delete:${ id }` ); + this.assets = this.assets.filter( ( asset ) => asset.id !== id ); + this.assetBytes.delete( id ); + } +} + +function publicFetch( api, options = {} ) { + return async ( url ) => { + const throughProxy = url.includes( + 'wordpress-playground-cors-proxy.net' + ); + const rawStart = url.indexOf( 'https://raw.githubusercontent.com/' ); + if ( rawStart !== -1 ) { + const rawUrl = new URL( url.slice( rawStart ) ); + const parts = rawUrl.pathname.split( '/' ).filter( Boolean ); + const reference = parts[ 2 ]; + const commitSha = + reference === 'docs-preview-code-reference' + ? api.refSha + : reference; + api.events.push( + `fetch:${ + reference === 'docs-preview-code-reference' + ? 'stable-pointer' + : 'candidate-pointer' + }` + ); + const bytes = api.commits.get( commitSha ); + return { + status: bytes ? 200 : 404, + headers: { + get: ( header ) => + ( { + 'x-playground-cors-proxy': 'true', + 'access-control-allow-origin': PLAYGROUND_ORIGIN, + } )[ header.toLowerCase() ] || null, + }, + arrayBuffer: async () => bytes, + }; + } + const name = new URL( + throughProxy + ? url.slice( url.indexOf( 'https://github.com/' ) ) + : url + ).pathname + .split( '/' ) + .at( -1 ); + const asset = api.assets.find( + ( candidate ) => candidate.name === name + ); + api.events.push( `fetch:${ name }` ); + if ( ! asset ) { + return { status: 404, headers: { get: () => null } }; + } + let bytes = api.assetBytes.get( asset.id ); + if ( options.corruptSnapshot && asset.name.endsWith( '.zip' ) ) { + bytes = Buffer.from( 'corrupt' ); + } + return { + status: 200, + headers: { + get: ( header ) => + ( { + 'x-playground-cors-proxy': 'true', + 'access-control-allow-origin': PLAYGROUND_ORIGIN, + } )[ header.toLowerCase() ] || null, + }, + arrayBuffer: async () => bytes, + json: async () => JSON.parse( bytes.toString( 'utf8' ) ), + }; + }; +} + +async function handoffDirectory( metadata, bytes = snapshotBytes ) { + const directory = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-trunk-publish-' ) + ); + await writeFile( + path.join( directory, 'build.json' ), + `${ JSON.stringify( metadata ) }\n` + ); + if ( metadata.snapshotFilename ) { + await writeFile( + path.join( directory, metadata.snapshotFilename ), + bytes + ); + } + return directory; +} + +function options( api, directory, extra = {} ) { + return { + repository, + stagingVariable: '', + triggerRunId: 456, + triggerRunAttempt: 1, + handoffDirectory: directory, + artifactAvailable: true, + api, + fetchImplementation: publicFetch( api ), + now: () => '2026-08-09T13:00:00.000Z', + warning: () => {}, + ...extra, + }; +} + +function addAsset( api, id, name, bytes ) { + api.assets.push( { + id, + name, + browser_download_url: releaseAssetUrl( repository, name ), + } ); + api.assetBytes.set( id, Buffer.from( bytes ) ); +} + +function installPrevious( api ) { + const oldSha = 'c'.repeat( 40 ); + const oldBytes = Buffer.from( 'old snapshot' ); + const build = buildMetadata( { + sourceSha: oldSha, + workflowRunId: '300', + bytes: oldBytes, + snapshotBytes: oldBytes.byteLength, + snapshotSha256: createHash( 'sha256' ) + .update( oldBytes ) + .digest( 'hex' ), + } ); + const published = createTrunkPublishedMetadata( + build, + repository, + '2026-08-08T13:00:00.000Z' + ); + addAsset( api, 1, build.snapshotFilename, oldBytes ); + addAsset( + api, + 2, + metadataAssetName( build.snapshotFilename ), + `${ JSON.stringify( published ) }\n` + ); + addAsset( api, 4, 'code-reference-pr-123-preview.zip', 'pr snapshot' ); + api.previousRefSha = '9'.repeat( 40 ); + api.refSha = api.previousRefSha; + api.commits.set( + api.refSha, + Buffer.from( + `${ JSON.stringify( createTrunkBlueprint( published ) ) }\n` + ) + ); + return { build, published }; +} + +test( 'the stable pointer moves only after every candidate is public', async () => { + const api = new FakeApi(); + installPrevious( api ); + const current = buildMetadata(); + const directory = await handoffDirectory( current ); + const result = await publishTrunk( options( api, directory ) ); + assert.equal( result.status, 'ready' ); + assert.equal( api.refSha, 'f'.repeat( 40 ) ); + const blueprint = JSON.parse( + api.commits.get( api.refSha ).toString( 'utf8' ) + ); + assert.match( blueprint.meta.description, new RegExp( sha ) ); + assert.deepEqual( api.deleted.sort(), [ 1, 2 ] ); + assert.ok( api.assets.some( ( asset ) => asset.id === 4 ) ); + const candidateFetch = api.events.lastIndexOf( 'fetch:candidate-pointer' ); + const pointerMove = api.events.lastIndexOf( + `git:update-ref:${ 'f'.repeat( 40 ) }` + ); + const pointerRead = api.events.lastIndexOf( + `git:read-ref:${ 'f'.repeat( 40 ) }` + ); + const firstDelete = api.events.findIndex( ( event ) => + event.startsWith( 'delete:' ) + ); + assert.ok( + candidateFetch > -1 && + candidateFetch < pointerMove && + pointerMove < pointerRead && + pointerRead < firstDelete + ); +} ); + +test( 'public candidate failure leaves the previous pointer and snapshot intact', async () => { + const api = new FakeApi(); + installPrevious( api ); + const directory = await handoffDirectory( buildMetadata() ); + await assert.rejects( + publishTrunk( + options( api, directory, { + fetchImplementation: publicFetch( api, { + corruptSnapshot: true, + } ), + } ) + ), + /Public snapshot/ + ); + assert.equal( api.refSha, api.previousRefSha ); + assert.ok( api.assets.some( ( asset ) => asset.id === 1 ) ); + assert.deepEqual( api.deleted.sort(), [ 10, 11 ] ); +} ); + +test( 'a failed ref update leaves the previous pointer intact', async () => { + const api = new FakeApi(); + installPrevious( api ); + api.refUpdateFailure = 'before'; + const directory = await handoffDirectory( buildMetadata() ); + await assert.rejects( + publishTrunk( options( api, directory ) ), + /ref update failed/ + ); + assert.equal( api.refSha, api.previousRefSha ); + assert.ok( api.assets.some( ( asset ) => asset.id === 1 ) ); + assert.deepEqual( api.deleted.sort(), [ 10, 11 ] ); +} ); + +test( 'an ambiguous ref response resolves the candidate and completes', async () => { + const api = new FakeApi(); + installPrevious( api ); + api.refUpdateFailure = 'after'; + const directory = await handoffDirectory( buildMetadata() ); + assert.equal( + ( await publishTrunk( options( api, directory ) ) ).status, + 'ready' + ); + assert.equal( api.refSha, 'f'.repeat( 40 ) ); + assert.deepEqual( api.deleted.sort(), [ 1, 2 ] ); +} ); + +test( 'unresolved ref state retains both valid generations', async () => { + const api = new FakeApi(); + installPrevious( api ); + api.refUpdateFailure = 'after'; + api.failRefResolution = true; + const directory = await handoffDirectory( buildMetadata() ); + await assert.rejects( + publishTrunk( options( api, directory ) ), + /ref resolution failed/ + ); + assert.equal( api.refSha, 'f'.repeat( 40 ) ); + assert.ok( api.assets.some( ( asset ) => asset.id === 1 ) ); + assert.ok( api.assets.some( ( asset ) => asset.id === 10 ) ); + assert.deepEqual( api.deleted, [] ); +} ); + +test( 'cleanup failure keeps the new validated pointer and both snapshots', async () => { + const api = new FakeApi(); + installPrevious( api ); + api.deleteErrorId = 1; + const directory = await handoffDirectory( buildMetadata() ); + await assert.rejects( + publishTrunk( options( api, directory ) ), + /cleanup failed/ + ); + assert.equal( api.refSha, 'f'.repeat( 40 ) ); + assert.ok( api.assets.some( ( asset ) => asset.id === 1 ) ); + assert.ok( api.assets.some( ( asset ) => asset.id === 10 ) ); +} ); + +test( 'invalid, missing, and superseded handoffs never move the pointer', async () => { + const invalidApi = new FakeApi(); + installPrevious( invalidApi ); + const invalid = buildMetadata( { validationStatus: 'failed' } ); + assert.equal( + ( + await publishTrunk( + options( invalidApi, await handoffDirectory( invalid ) ) + ) + ).status, + 'invalid' + ); + assert.equal( invalidApi.assets.length, 3 ); + + const missingApi = new FakeApi(); + installPrevious( missingApi ); + await assert.rejects( + publishTrunk( + options( missingApi, await handoffDirectory( buildMetadata() ), { + artifactAvailable: false, + } ) + ), + /handoff is unavailable/ + ); + assert.equal( missingApi.assets.length, 3 ); + + const supersededApi = new FakeApi(); + installPrevious( supersededApi ); + supersededApi.latestRun = run( { id: 789 } ); + assert.equal( + ( + await publishTrunk( + options( + supersededApi, + await handoffDirectory( buildMetadata() ) + ) + ) + ).status, + 'superseded' + ); + assert.equal( supersededApi.assets.length, 3 ); +} ); diff --git a/.github/scripts/docs-playground-preview/test/trunk.test.mjs b/.github/scripts/docs-playground-preview/test/trunk.test.mjs index 05c332189e3f8..87fa072061977 100644 --- a/.github/scripts/docs-playground-preview/test/trunk.test.mjs +++ b/.github/scripts/docs-playground-preview/test/trunk.test.mjs @@ -2,19 +2,16 @@ import assert from 'node:assert/strict'; import { createHash } from 'node:crypto'; import { test } from 'node:test'; -import { - PLAYGROUND_ORIGIN, - corsProxyUrl, - releaseAssetUrl, -} from '../lib/publication.mjs'; +import { PLAYGROUND_ORIGIN, corsProxyUrl } from '../lib/publication.mjs'; import { TRUNK_POINTER_ASSET, + TRUNK_POINTER_REF, assertLatestTrunkAuthorized, createTrunkBlueprint, createTrunkPublishedMetadata, inspectTrunkHandoff, trunkPlaygroundUrl, - trunkPointerCandidateName, + trunkBlueprintCommitUrl, trunkSnapshotAssetName, trunkStableBlueprintUrl, validatePublicBlueprint, @@ -104,9 +101,10 @@ test( 'trunk assets bind the exact SHA, run, and attempt', () => { `code-reference-trunk-${ sha }-456-2.zip` ); assert.equal( - trunkPointerCandidateName( identity ), - `code-reference-trunk-pointer-${ sha }-456-2.json` + trunkBlueprintCommitUrl( repository, sha ), + `https://raw.githubusercontent.com/${ repository }/${ sha }/${ TRUNK_POINTER_ASSET }` ); + assert.equal( TRUNK_POINTER_REF, 'heads/docs-preview-code-reference' ); assert.throws( () => trunkSnapshotAssetName( { ...identity, sourceSha: 'short' } ), /full lowercase commit/ @@ -114,7 +112,7 @@ test( 'trunk assets bind the exact SHA, run, and attempt', () => { } ); test( 'the stable trunk entry point uses the fixed public Blueprint asset', () => { - const blueprintUrl = releaseAssetUrl( repository, TRUNK_POINTER_ASSET ); + const blueprintUrl = `https://raw.githubusercontent.com/${ repository }/docs-preview-code-reference/${ TRUNK_POINTER_ASSET }`; assert.equal( trunkStableBlueprintUrl( repository ), blueprintUrl ); const launch = new URL( trunkPlaygroundUrl( repository ) ); assert.equal( launch.origin, PLAYGROUND_ORIGIN ); From 43f4b57ce2ae584f2c4f7b5002a57cfc7ebc7278 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 14:58:00 +0400 Subject: [PATCH 27/51] Build/Test Tools: Run docs preview on trunk pushes The shared cache and stable pointer rot if trunk does not exercise the real build. Build the exact pushed SHA with read-only authority, then hand it to queued default-branch publisher code. Non-blocking follow-ups: reduce duplicated PR/trunk orchestration and broaden the publisher concurrency comment. --- .../docs-playground-preview-build.yml | 101 ++++++++++++++++++ .../docs-playground-preview-publish.yml | 43 ++++++++ 2 files changed, 144 insertions(+) diff --git a/.github/workflows/docs-playground-preview-build.yml b/.github/workflows/docs-playground-preview-build.yml index e765c34c1d418..ecb7158a4c74e 100644 --- a/.github/workflows/docs-playground-preview-build.yml +++ b/.github/workflows/docs-playground-preview-build.yml @@ -1,6 +1,9 @@ name: Code Reference Playground Preview Build on: + push: + branches: + - trunk pull_request: branches: - trunk @@ -13,6 +16,7 @@ jobs: build: name: Build Code Reference snapshot if: >- + github.event_name == 'pull_request' && ( github.repository == 'WordPress/wordpress-develop' || ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) && github.event.pull_request.base.ref == 'trunk' && @@ -135,3 +139,100 @@ jobs: path: ${{ env.HANDOFF_DIRECTORY }} if-no-files-found: error retention-days: 1 + + build-trunk: + name: Build trunk Code Reference snapshot + if: >- + github.event_name == 'push' && + github.ref == 'refs/heads/trunk' && + ( github.repository == 'WordPress/wordpress-develop' || + ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) + concurrency: + group: docs-preview-build-trunk + cancel-in-progress: true + runs-on: ubuntu-24.04 + timeout-minutes: 60 + permissions: + contents: read + env: + SOURCE_REPOSITORY: ${{ github.repository }} + SOURCE_SHA: ${{ github.sha }} + WORKFLOW_RUN_ID: ${{ github.run_id }} + WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + HANDOFF_DIRECTORY: ${{ github.workspace }}/handoff + RESOLVED_INPUTS: ${{ runner.temp }}/docs-preview-resolved-inputs.json + CACHE_ROOT: ${{ runner.temp }}/docs-preview-cache + WORK_ROOT: ${{ runner.temp }}/docs-preview-work + SNAPSHOT_NAME: code-reference-trunk-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}.zip + steps: + - name: Check out the exact trunk commit + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.sha }} + persist-credentials: false + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + + - name: Set up the pinned Node version + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '20.20.2' + + - name: Set up the pinned PHP and Composer versions + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 + with: + php-version: '8.4' + tools: composer:2.8.12 + coverage: none + + - name: Resolve the exact build and cache identity + id: resolve + run: >- + npm --prefix .github/docs-playground-preview run build -- + --resolve-only + --source "$GITHUB_WORKSPACE" + --source-repository "$SOURCE_REPOSITORY" + --source-sha "$SOURCE_SHA" + --run-id "$WORKFLOW_RUN_ID" + --run-attempt "$WORKFLOW_RUN_ATTEMPT" + --run-url "$RUN_URL" + --runner-image ubuntu-24.04 + --cache-root "$CACHE_ROOT" + --work-root "$WORK_ROOT" + --inputs-output "$RESOLVED_INPUTS" + --metadata "$HANDOFF_DIRECTORY/build.json" + + - name: Restore the exact invariant site base + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ${{ steps.resolve.outputs.cache-directory }} + key: ${{ steps.resolve.outputs.cache-key }} + + # Administrator: enable enforcement in Settings > Secrets and variables > + # Actions > Variables by creating or updating DOCS_PREVIEW_ENFORCE to true. + - name: Build and validate the trunk Code Reference snapshot + env: + DOCS_PREVIEW_ENFORCE: ${{ vars.DOCS_PREVIEW_ENFORCE }} + run: >- + npm --prefix .github/docs-playground-preview run build -- + --source "$GITHUB_WORKSPACE" + --source-repository "$SOURCE_REPOSITORY" + --source-sha "$SOURCE_SHA" + --run-id "$WORKFLOW_RUN_ID" + --run-attempt "$WORKFLOW_RUN_ATTEMPT" + --run-url "$RUN_URL" + --runner-image ubuntu-24.04 + --cache-root "$CACHE_ROOT" + --work-root "$WORK_ROOT" + --resolved-inputs "$RESOLVED_INPUTS" + --output "$HANDOFF_DIRECTORY/$SNAPSHOT_NAME" + --metadata "$HANDOFF_DIRECTORY/build.json" + + - name: Upload the terminal trunk publisher handoff + if: ${{ always() && ! cancelled() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: docs-preview-trunk-handoff-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ env.HANDOFF_DIRECTORY }} + if-no-files-found: error + retention-days: 1 diff --git a/.github/workflows/docs-playground-preview-publish.yml b/.github/workflows/docs-playground-preview-publish.yml index 3254bc4a736da..97e48c1c41953 100644 --- a/.github/workflows/docs-playground-preview-publish.yml +++ b/.github/workflows/docs-playground-preview-publish.yml @@ -59,3 +59,46 @@ jobs: GITHUB_TOKEN: ${{ github.token }} HANDOFF_DOWNLOAD_RESULT: ${{ steps.download.outcome }} run: node .github/scripts/docs-playground-preview/publish.mjs handoff + + publish-trunk: + name: Publish trunk Code Reference snapshot + if: >- + github.event.workflow_run.event == 'push' && + github.event.workflow_run.head_branch == 'trunk' && + ( github.repository == 'WordPress/wordpress-develop' || + ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + actions: read + contents: write + steps: + - name: Check out trusted default-branch publisher code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.repository.default_branch }} + persist-credentials: false + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + + - name: Set up the pinned Node version + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '20.20.2' + + - name: Download the exact trunk handoff + id: download + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: docs-preview-trunk-handoff-${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }} + path: handoff + github-token: ${{ github.token }} + run-id: ${{ github.event.workflow_run.id }} + digest-mismatch: error + + - name: Validate and publish the trunk handoff + env: + DOCS_PREVIEW_STAGING: ${{ vars.DOCS_PREVIEW_STAGING }} + GITHUB_TOKEN: ${{ github.token }} + HANDOFF_DOWNLOAD_RESULT: ${{ steps.download.outcome }} + run: node .github/scripts/docs-playground-preview/publish-trunk.mjs handoff From cc4e8786d51863ab14227c47292df4a1310c6379 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 15:05:15 +0400 Subject: [PATCH 28/51] Build/Test Tools: Document Code Reference previews The preview is useless if contributors cannot operate or recover it. Document the exact local command, pins, cache scopes, label lifecycle, publication names, rollback behavior, and justified validators. Keep the repository README on one stable Blueprint URL. Trunk publication moves the ref, not this link. --- .github/docs-playground-preview/README.md | 115 ++++++++++++++++++ .../test/trunk.test.mjs | 10 ++ README.md | 2 + 3 files changed, 127 insertions(+) create mode 100644 .github/docs-playground-preview/README.md diff --git a/.github/docs-playground-preview/README.md b/.github/docs-playground-preview/README.md new file mode 100644 index 0000000000000..87906da0c3adf --- /dev/null +++ b/.github/docs-playground-preview/README.md @@ -0,0 +1,115 @@ +# Code Reference Playground preview + +This directory pins the inputs for the WordPress Core Code Reference preview. The implementation lives in `.github/scripts/docs-playground-preview`; GitHub Actions only supplies exact source identity, caching, and publication authority. + +The preview is informational. Pull request source is parsed as data in a job with no secrets and `contents: read` at most. Build jobs use GitHub's cache service: a trusted `trunk` build can warm the shared cache, while GitHub scopes a pull-request-created entry to its pull request ref so it cannot warm or replace the default-branch cache. Only default-branch publisher and cleanup code receives repository write permissions for release assets, comments, labels, cache deletion, or the stable `trunk` pointer. + +## Local build + +Use the exact Node, npm, PHP, and Composer versions in `dependencies.json`, then run: + +```sh +npm --prefix .github/docs-playground-preview run build +``` + +The command infers the current Git repository and commit, resolves the moving Playground `beta` channel, builds or restores the invariant site base, parses eligible PHP under `src`, imports the complete Code Reference, packages the snapshot, and runs the same behavioral validation as CI. Local output and cache data are written below `.cache/docs-playground-preview`. + +CI passes explicit `--source-repository`, `--source-sha`, run identity, cache, output, and metadata paths to this same command. `--resolve-only` writes the concrete beta and exact cache identity before the cache restore step. Local and CI snapshots are equivalent when their symbol counts, representative routes, search, runtime constants, and size boundary agree; their bytes need not agree. + +Run the script tests with: + +```sh +npm test --prefix .github/docs-playground-preview +``` + +## Updating pins + +All repositories in `dependencies.json` must remain on the allowed repositories enforced by `.github/scripts/docs-playground-preview/lib/config.mjs`, and every repository revision must be a full immutable commit hash. Tool versions are exact. GitHub Actions are pinned to full commit hashes in the workflow files. + +To update a pinned repository: + +1. Change its full commit in `dependencies.json`. The dependency's own lock files come from that immutable checkout; this repository does not copy or re-verify them. +2. Run the script tests and a complete local build. +3. Exercise cold and warm builds in the staging repository. + +Keep duplicated tool identities synchronized: + +- For `@wp-playground/cli`, `yarn`, `@wordpress/scripts`, or `@wordpress/i18n`, update both `dependencies.json` and the exact dependency in `package.json`, then regenerate `package-lock.json` with the manifest's exact Node and npm versions. +- For Node, update `dependencies.json`, `.nvmrc`, `package.json`'s `engines.node`, and every `setup-node` input in the build, publish, and lifecycle workflows. Select a Node distribution containing the npm version recorded in `dependencies.json`; the build rejects a different npm version. +- For PHP, update `dependencies.json`, the fixed runtime checks in `.github/scripts/docs-playground-preview/lib/config.mjs` and `.github/scripts/docs-playground-preview/lib/publication.mjs`, and both `setup-php` inputs in the build workflow. PHP remains `8.4` unless the specification changes. +- For Composer, update `dependencies.json` and both `setup-php` Composer tool specifications in the build workflow. +- For a GitHub Action, update its full commit SHA and the adjacent release comment in every workflow use. + +After any tool change, run the script tests, regenerate the lockfile when applicable, complete a local build, and exercise cold and warm staging builds. + +A manifest or harness change automatically produces a different exact cache key. Increment `cacheSchemaVersion` when the stored base layout or meaning changes incompatibly, even if no dependency pin changed. Broad restore keys are prohibited. + +## Repository activation + +The workflows run in `WordPress/wordpress-develop`. They also run in `sirreal/wordpress-develop` only when the repository Actions variable `DOCS_PREVIEW_STAGING` is exactly `true`. They remain inert in every other fork. + +The staging repository needs a `docs-preview` label and the Actions variables used for the scenario being tested. Disable staging by deleting `DOCS_PREVIEW_STAGING` or setting it to anything other than the lowercase string `true`. + +## Validation enforcement + +Behavioral validation is advisory unless the repository Actions variable `DOCS_PREVIEW_ENFORCE` is exactly `true`. Advisory failures emit workflow warnings and terminal PR state but cannot publish or replace a snapshot. Fatal build, handoff, identity, digest, size, and publication failures always fail. + +To enable enforcement: + +1. Open repository **Settings**. +2. Open **Secrets and variables → Actions → Variables**. +3. Create or update `DOCS_PREVIEW_ENFORCE` with the value `true`. + +## Pull request lifecycle + +The label name is exactly `docs-preview`, and only pull requests targeting `trunk` are eligible. + +- Adding the label requests a complete build of the exact pull request head repository and SHA. +- A healthy public snapshot for the same SHA is reused. **Re-run all jobs** on the original build bypasses reuse. +- While the label remains, a synchronized head cancels the obsolete build and requests the newest SHA. +- The latest terminal publisher removes the label after success, validation failure, or build failure. +- A later unlabeled commit retains the latest successful link, marks it stale, and asks a maintainer to add the label again. +- Closing or merging deletes only that pull request's assets and `refs/pull//merge` docs caches, then marks the historical comment expired. + +The sticky comment is separate from the general Core Playground comment and is identified by ``. + +## Publication and cleanup + +Snapshots and metadata are assets on the `code-reference-playground-preview` prerelease. + +- Pull request snapshots use `code-reference-pr----.zip` with a same-stem JSON metadata asset. Only the latest healthy pair for an open pull request is retained. +- Trunk snapshots use `code-reference-trunk---.zip` with a same-stem JSON metadata asset. +- The stable trunk Blueprint is `code-reference-trunk.json` on the dedicated `docs-preview-code-reference` Git branch. The repository README points Playground at that stable raw URL. + +Trunk publication uploads and publicly validates the immutable snapshot and metadata, creates and publicly validates an immutable Blueprint commit, then atomically moves the dedicated branch ref and re-reads its exact SHA. Only after that identity is proven are older trunk assets deleted. A failed or ambiguous ref mutation retains every snapshot the ref could identify. + +PR cleanup matches only its exact PR prefix. Trunk cleanup matches only the trunk prefix. Neither path deletes assets owned by the other or changes another pull request. + +## Recovery + +Publication failure never requires deleting the current working asset first. + +- For a PR, fix the cause and add `docs-preview` again, or use **Re-run all jobs** on the original build for a forced same-SHA rebuild. +- For `trunk`, re-run the newest trunk build or push the next commit. The next successful publisher moves the stable ref and removes orphaned trunk assets. +- If the publisher cannot determine whether the stable ref moved, it retains both old and candidate assets. Inspect the ref and the two metadata assets before removing anything manually. +- Do not delete the `docs-preview-code-reference` branch or the snapshot it names during recovery. A successful newest trunk run repairs the pointer safely. + +## Bespoke validators + +Pinned standard tools own Git, ZIP, SQLite, JSON, PHP, and process semantics. This implementation does not inspect archive grammar, database bytes, canonical serialization, descriptors, or process trees. The retained project-specific checks are limited to boundaries or product behavior that standard tools cannot understand on their own: + +- **Dependency manifest and cache identity:** checks the repository allowlist, immutable commit shape, exact tool versions, concrete beta identity, and every base input. This protects the shared default-branch cache boundary. +- **Publisher handoff identity:** binds schema, deployment repository, event, PR when applicable, exact source SHA, run ID and attempt, runtime identity, filename, size, and digest before any publication. This protects the untrusted-to-trusted publication boundary. +- **Candidate file size and digest:** uses ordinary file stat and SHA-256 tools without opening the snapshot. This enforces the publisher identity and 100 MiB boundaries. +- **Parser product inspection:** uses the standard JSON parser, then enforces eligible Core source paths, supported hook types, nonempty records, per-type catastrophic-under-count floors, and the trusted importer root. These are required section 14 product and “parse, never execute” checks; they do not validate the parser's internal format beyond the fields the importer consumes. +- **Latest-wins authorization:** re-reads current PR or `trunk` state and the newest eligible workflow attempt immediately before publication mutations. This prevents an obsolete run from moving or deleting the live preview. +- **Public snapshot delivery:** fetches the immutable snapshot through the Playground CORS proxy and checks response status, CORS headers, byte size, and SHA-256. Publication is not complete until the public product has the validated identity. +- **Public metadata identity:** downloads the just-published JSON metadata, applies the same source/runtime/snapshot identity checks, and compares its parsed value with the candidate. This prevents a wrong public metadata asset from becoming the retained pointer identity. +- **Public Blueprint and stable ref identity:** parses the small Blueprint with the standard JSON parser, checks its semantic identity through the public proxy at an immutable commit, atomically moves the dedicated Git ref, and verifies that ref's SHA. This protects transactional availability without reimplementing Git semantics. +- **Behavioral snapshot validation:** boots the final snapshot through Playground and checks the required routes, search, provenance, and runtime policy. This is the section 14 product validator; it deliberately ignores intermediate archive and database representations. + +No archive grammar, database format, canonical byte serialization, descriptor transaction, or process-tree validator is part of this pipeline. + +## Staging evidence + +Before upstream rollout, record links to the `sirreal/wordpress-develop` workflow runs and comments that demonstrate every scenario in the specification's staging acceptance gate. Evidence may be accumulated across runs. Keep the run URLs and observed SHA, cache state, comment state, public URL, route/search results, runtime policy, failure-preservation result, cleanup result, and measured public snapshot size in the eventual pull request description. diff --git a/.github/scripts/docs-playground-preview/test/trunk.test.mjs b/.github/scripts/docs-playground-preview/test/trunk.test.mjs index 87fa072061977..b4ca1ad9f27bb 100644 --- a/.github/scripts/docs-playground-preview/test/trunk.test.mjs +++ b/.github/scripts/docs-playground-preview/test/trunk.test.mjs @@ -1,5 +1,6 @@ import assert from 'node:assert/strict'; import { createHash } from 'node:crypto'; +import { readFile } from 'node:fs/promises'; import { test } from 'node:test'; import { PLAYGROUND_ORIGIN, corsProxyUrl } from '../lib/publication.mjs'; @@ -122,6 +123,15 @@ test( 'the stable trunk entry point uses the fixed public Blueprint asset', () = ); } ); +test( 'the repository README links the stable trunk entry point', async () => { + const readme = await readFile( + new URL( '../../../../README.md', import.meta.url ), + 'utf8' + ); + assert.match( readme, /latest successful Core Code Reference preview/ ); + assert.ok( readme.includes( trunkPlaygroundUrl( repository ) ) ); +} ); + test( 'trunk publication binds the exact terminal push and latest head', () => { const checked = validateTrunkPublicationContext( context() ); assert.equal( checked.sourceSha, sha ); diff --git a/README.md b/README.md index eb88e407cb612..648b0e884d292 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Welcome to the WordPress development repository! Please check out the [contributor handbook](https://make.wordpress.org/core/handbook/) for information about how to open bug reports, contribute patches, test changes, write documentation, or get involved in any way you can. +[Open the latest successful Core Code Reference preview in WordPress Playground](https://playground.wordpress.net/?blueprint-url=https%3A%2F%2Fwordpress-playground-cors-proxy.net%2Fhttps%3A%2F%2Fraw.githubusercontent.com%2FWordPress%2Fwordpress-develop%2Fdocs-preview-code-reference%2Fcode-reference-trunk.json). + * [Getting Started](#getting-started) * [Credentials](#credentials) From 000b7ffc79c00249a647ab957076eb2787f68d46 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 15:09:43 +0400 Subject: [PATCH 29/51] Build/Test Tools: Validate the stable docs pointer A validated commit is not the URL contributors open. The public proxy can still serve the old Blueprint after the ref moves; deleting that snapshot then breaks the stable link. Fetch the stable URL before cleanup. If delivery has not caught up, retain both complete generations. Non-blocking follow-up: retry transient public-pointer propagation before retaining both generations. --- .github/docs-playground-preview/README.md | 2 +- .../docs-playground-preview/publish-trunk.mjs | 6 ++++ .../test/publish-trunk.test.mjs | 28 +++++++++++++++++-- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/.github/docs-playground-preview/README.md b/.github/docs-playground-preview/README.md index 87906da0c3adf..346bcf51a93c3 100644 --- a/.github/docs-playground-preview/README.md +++ b/.github/docs-playground-preview/README.md @@ -34,7 +34,7 @@ To update a pinned repository: Keep duplicated tool identities synchronized: -- For `@wp-playground/cli`, `yarn`, `@wordpress/scripts`, or `@wordpress/i18n`, update both `dependencies.json` and the exact dependency in `package.json`, then regenerate `package-lock.json` with the manifest's exact Node and npm versions. +- For `@wp-playground/cli`, `yarn`, `@wordpress/scripts`, or `@wordpress/i18n`, update both `dependencies.json` and the exact dependency in `package.json`, then regenerate `package-lock.json` with the manifest's exact Node and npm versions. A Playground CLI update also requires changing its fixed version check in `.github/scripts/docs-playground-preview/lib/config.mjs`. - For Node, update `dependencies.json`, `.nvmrc`, `package.json`'s `engines.node`, and every `setup-node` input in the build, publish, and lifecycle workflows. Select a Node distribution containing the npm version recorded in `dependencies.json`; the build rejects a different npm version. - For PHP, update `dependencies.json`, the fixed runtime checks in `.github/scripts/docs-playground-preview/lib/config.mjs` and `.github/scripts/docs-playground-preview/lib/publication.mjs`, and both `setup-php` inputs in the build workflow. PHP remains `8.4` unless the specification changes. - For Composer, update `dependencies.json` and both `setup-php` Composer tool specifications in the build workflow. diff --git a/.github/scripts/docs-playground-preview/publish-trunk.mjs b/.github/scripts/docs-playground-preview/publish-trunk.mjs index f30b737dc6c17..3ae8e895b2558 100644 --- a/.github/scripts/docs-playground-preview/publish-trunk.mjs +++ b/.github/scripts/docs-playground-preview/publish-trunk.mjs @@ -20,6 +20,7 @@ import { createTrunkPublishedMetadata, inspectTrunkHandoff, trunkBlueprintCommitUrl, + trunkStableBlueprintUrl, validatePublicBlueprint, validatePublishedTrunkMetadata, validateTrunkPublicationContext, @@ -271,6 +272,11 @@ async function publishCandidate( session, metadata, uploaded, transaction ) { blueprint ); await moveStablePointer( session, previousRef, pointerCommit, transaction ); + await validatePublicBlueprint( + trunkStableBlueprintUrl( session.repository ), + blueprint, + session.fetchImplementation + ); await cleanupOldTrunkAssets( session, release, diff --git a/.github/scripts/docs-playground-preview/test/publish-trunk.test.mjs b/.github/scripts/docs-playground-preview/test/publish-trunk.test.mjs index 7e873c8682efb..e65fa97d34bfd 100644 --- a/.github/scripts/docs-playground-preview/test/publish-trunk.test.mjs +++ b/.github/scripts/docs-playground-preview/test/publish-trunk.test.mjs @@ -201,7 +201,9 @@ function publicFetch( api, options = {} ) { const reference = parts[ 2 ]; const commitSha = reference === 'docs-preview-code-reference' - ? api.refSha + ? options.staleStablePointer + ? api.previousRefSha + : api.refSha : reference; api.events.push( `fetch:${ @@ -355,6 +357,7 @@ test( 'the stable pointer moves only after every candidate is public', async () const pointerRead = api.events.lastIndexOf( `git:read-ref:${ 'f'.repeat( 40 ) }` ); + const stableFetch = api.events.lastIndexOf( 'fetch:stable-pointer' ); const firstDelete = api.events.findIndex( ( event ) => event.startsWith( 'delete:' ) ); @@ -362,10 +365,31 @@ test( 'the stable pointer moves only after every candidate is public', async () candidateFetch > -1 && candidateFetch < pointerMove && pointerMove < pointerRead && - pointerRead < firstDelete + pointerRead < stableFetch && + stableFetch < firstDelete ); } ); +test( 'stale stable delivery retains both snapshot generations', async () => { + const api = new FakeApi(); + installPrevious( api ); + const directory = await handoffDirectory( buildMetadata() ); + await assert.rejects( + publishTrunk( + options( api, directory, { + fetchImplementation: publicFetch( api, { + staleStablePointer: true, + } ), + } ) + ), + /does not identify the candidate/ + ); + assert.equal( api.refSha, 'f'.repeat( 40 ) ); + assert.ok( api.assets.some( ( asset ) => asset.id === 1 ) ); + assert.ok( api.assets.some( ( asset ) => asset.id === 10 ) ); + assert.deepEqual( api.deleted, [] ); +} ); + test( 'public candidate failure leaves the previous pointer and snapshot intact', async () => { const api = new FakeApi(); installPrevious( api ); From 77f1aafcd67cbc017ce70f1c04a6a03f8f655244 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 15:17:46 +0400 Subject: [PATCH 30/51] Build/Test Tools: Fix docs workflow runner paths Job-level environment expressions cannot read the runner context. Both preview builds would fail workflow validation before the local pipeline started. Use the runner-provided temporary directory inside each shell step. Keep the required publication queue and narrowly silence the repository's older actionlint for that one current GitHub syntax key. Non-blocking follow-up: upgrade actionlint when it accepts GitHub's current concurrency queue key, then remove the narrow suppressions. --- .github/actionlint.yaml | 8 +++++ .../docs-playground-preview-build.yml | 30 ++++++++----------- 2 files changed, 20 insertions(+), 18 deletions(-) create mode 100644 .github/actionlint.yaml diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 0000000000000..6c6fb7f9878d1 --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,8 @@ +paths: + .github/workflows/docs-playground-preview-publish.yml: + ignore: + # actionlint 1.7.11 predates GitHub's concurrency queue property. + - 'unexpected key "queue" for "concurrency" section' + .github/workflows/docs-playground-preview-lifecycle.yml: + ignore: + - 'unexpected key "queue" for "concurrency" section' diff --git a/.github/workflows/docs-playground-preview-build.yml b/.github/workflows/docs-playground-preview-build.yml index ecb7158a4c74e..5d7a9f1f2cf59 100644 --- a/.github/workflows/docs-playground-preview-build.yml +++ b/.github/workflows/docs-playground-preview-build.yml @@ -40,9 +40,6 @@ jobs: WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} HANDOFF_DIRECTORY: ${{ github.workspace }}/handoff - RESOLVED_INPUTS: ${{ runner.temp }}/docs-preview-resolved-inputs.json - CACHE_ROOT: ${{ runner.temp }}/docs-preview-cache - WORK_ROOT: ${{ runner.temp }}/docs-preview-work SNAPSHOT_NAME: code-reference-pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }}-${{ github.run_id }}-${{ github.run_attempt }}.zip steps: - name: Check out the exact pull-request head @@ -97,9 +94,9 @@ jobs: --run-attempt "$WORKFLOW_RUN_ATTEMPT" --run-url "$RUN_URL" --runner-image ubuntu-24.04 - --cache-root "$CACHE_ROOT" - --work-root "$WORK_ROOT" - --inputs-output "$RESOLVED_INPUTS" + --cache-root "$RUNNER_TEMP/docs-preview-cache" + --work-root "$RUNNER_TEMP/docs-preview-work" + --inputs-output "$RUNNER_TEMP/docs-preview-resolved-inputs.json" --metadata "$HANDOFF_DIRECTORY/build.json" - name: Restore the exact invariant site base @@ -125,9 +122,9 @@ jobs: --run-attempt "$WORKFLOW_RUN_ATTEMPT" --run-url "$RUN_URL" --runner-image ubuntu-24.04 - --cache-root "$CACHE_ROOT" - --work-root "$WORK_ROOT" - --resolved-inputs "$RESOLVED_INPUTS" + --cache-root "$RUNNER_TEMP/docs-preview-cache" + --work-root "$RUNNER_TEMP/docs-preview-work" + --resolved-inputs "$RUNNER_TEMP/docs-preview-resolved-inputs.json" --output "$HANDOFF_DIRECTORY/$SNAPSHOT_NAME" --metadata "$HANDOFF_DIRECTORY/build.json" @@ -161,9 +158,6 @@ jobs: WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} HANDOFF_DIRECTORY: ${{ github.workspace }}/handoff - RESOLVED_INPUTS: ${{ runner.temp }}/docs-preview-resolved-inputs.json - CACHE_ROOT: ${{ runner.temp }}/docs-preview-cache - WORK_ROOT: ${{ runner.temp }}/docs-preview-work SNAPSHOT_NAME: code-reference-trunk-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}.zip steps: - name: Check out the exact trunk commit @@ -197,9 +191,9 @@ jobs: --run-attempt "$WORKFLOW_RUN_ATTEMPT" --run-url "$RUN_URL" --runner-image ubuntu-24.04 - --cache-root "$CACHE_ROOT" - --work-root "$WORK_ROOT" - --inputs-output "$RESOLVED_INPUTS" + --cache-root "$RUNNER_TEMP/docs-preview-cache" + --work-root "$RUNNER_TEMP/docs-preview-work" + --inputs-output "$RUNNER_TEMP/docs-preview-resolved-inputs.json" --metadata "$HANDOFF_DIRECTORY/build.json" - name: Restore the exact invariant site base @@ -222,9 +216,9 @@ jobs: --run-attempt "$WORKFLOW_RUN_ATTEMPT" --run-url "$RUN_URL" --runner-image ubuntu-24.04 - --cache-root "$CACHE_ROOT" - --work-root "$WORK_ROOT" - --resolved-inputs "$RESOLVED_INPUTS" + --cache-root "$RUNNER_TEMP/docs-preview-cache" + --work-root "$RUNNER_TEMP/docs-preview-work" + --resolved-inputs "$RUNNER_TEMP/docs-preview-resolved-inputs.json" --output "$HANDOFF_DIRECTORY/$SNAPSHOT_NAME" --metadata "$HANDOFF_DIRECTORY/build.json" From 200908421ac4b47b13656df3f850978ac78c9528 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 15:22:57 +0400 Subject: [PATCH 31/51] Build/Test Tools: Apply Core style to docs harness The preview scripts passed their own tests but failed Core's JavaScript gate. Format the affected modules, write command output directly to the intended streams, and remove test-only shadowing. The transaction logic and emitted diagnostics do not change. --- .../docs-playground-preview/lib/archive.mjs | 19 +++- .../docs-playground-preview/lib/base.mjs | 105 +++++++++++++----- .../docs-playground-preview/lib/config.mjs | 28 +++-- .../docs-playground-preview/lib/files.mjs | 24 ++-- .../docs-playground-preview/lib/inputs.mjs | 5 +- .../docs-playground-preview/lib/process.mjs | 7 +- .../docs-playground-preview/lifecycle.mjs | 8 +- .../docs-playground-preview/publish-trunk.mjs | 10 +- .../docs-playground-preview/publish.mjs | 8 +- .../scripts/docs-playground-preview/reuse.mjs | 7 +- .../test/base.test.mjs | 53 ++++++--- .../test/config.test.mjs | 5 +- .../test/files.test.mjs | 56 +++++++--- .../test/publish-trunk.test.mjs | 40 +++---- 14 files changed, 255 insertions(+), 120 deletions(-) diff --git a/.github/scripts/docs-playground-preview/lib/archive.mjs b/.github/scripts/docs-playground-preview/lib/archive.mjs index eba012e219dd3..d67e60cd2e25f 100644 --- a/.github/scripts/docs-playground-preview/lib/archive.mjs +++ b/.github/scripts/docs-playground-preview/lib/archive.mjs @@ -31,15 +31,22 @@ export async function zipDirectory( rootName, runImplementation = run ) { - const temporary = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-zip-' ) ); - const target = rootName === '.' ? temporary : path.join( temporary, rootName ); + const temporary = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-zip-' ) + ); + const target = + rootName === '.' ? temporary : path.join( temporary, rootName ); try { await copyDirectory( source, target, DEFAULT_EXCLUDES ); await rm( output, { force: true } ); - await runImplementation( 'zip', [ '-rq', path.resolve( output ), '.' ], { - cwd: temporary, - label: `package ${ rootName }`, - } ); + await runImplementation( + 'zip', + [ '-rq', path.resolve( output ), '.' ], + { + cwd: temporary, + label: `package ${ rootName }`, + } + ); } finally { await rm( temporary, { recursive: true, force: true } ); } diff --git a/.github/scripts/docs-playground-preview/lib/base.mjs b/.github/scripts/docs-playground-preview/lib/base.mjs index 12e776f82f0f4..88bfab55e9db6 100644 --- a/.github/scripts/docs-playground-preview/lib/base.mjs +++ b/.github/scripts/docs-playground-preview/lib/base.mjs @@ -1,11 +1,5 @@ import { fileURLToPath } from 'node:url'; -import { - cp, - mkdir, - readFile, - rm, - writeFile, -} from 'node:fs/promises'; +import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'; import path from 'node:path'; import { copyDirectory, downloadFile, zipDirectory } from './archive.mjs'; @@ -14,7 +8,10 @@ import { run } from './process.mjs'; const LIBRARY_ROOT = path.dirname( fileURLToPath( import.meta.url ) ); const SCRIPT_ROOT = path.dirname( LIBRARY_ROOT ); -const TOOLING_ROOT = path.resolve( LIBRARY_ROOT, '../../../docs-playground-preview' ); +const TOOLING_ROOT = path.resolve( + LIBRARY_ROOT, + '../../../docs-playground-preview' +); const PHP_ROOT = path.join( SCRIPT_ROOT, 'php' ); function executable( name ) { @@ -31,7 +28,13 @@ export function dependencyBuildPlan( roots, composer ) { return [ { command: 'php', - args: [ composer, 'install', '--no-interaction', '--no-dev', '--prefer-dist' ], + args: [ + composer, + 'install', + '--no-interaction', + '--no-dev', + '--prefer-dist', + ], cwd: roots.phpdocParser, label: 'install phpdoc-parser dependencies', }, @@ -62,7 +65,13 @@ export function dependencyBuildPlan( roots, composer ) { }, { command: 'php', - args: [ composer, 'install', '--no-interaction', '--no-dev', '--prefer-dist' ], + args: [ + composer, + 'install', + '--no-interaction', + '--no-dev', + '--prefer-dist', + ], cwd: roots.wporgMuPlugins, label: 'install wporg-mu-plugins Composer dependencies', }, @@ -74,13 +83,24 @@ export function dependencyBuildPlan( roots, composer ) { }, { command: 'php', - args: [ composer, 'config', 'allow-plugins.composer/installers', 'true' ], + args: [ + composer, + 'config', + 'allow-plugins.composer/installers', + 'true', + ], cwd: roots.postsToPosts, label: 'configure posts-to-posts installer', }, { command: 'php', - args: [ composer, 'install', '--no-interaction', '--no-dev', '--prefer-dist' ], + args: [ + composer, + 'install', + '--no-interaction', + '--no-dev', + '--prefer-dist', + ], cwd: roots.postsToPosts, label: 'install posts-to-posts dependencies', }, @@ -104,7 +124,8 @@ export function createInvariantBaseBlueprint( inputs ) { meta: { title: 'WordPress Core Code Reference invariant base', author: 'WordPress', - description: 'Pinned DevHub dependencies with an empty reference index.', + description: + 'Pinned DevHub dependencies with an empty reference index.', }, preferredVersions: { php: inputs.dependencies.playground.phpVersion, @@ -135,15 +156,13 @@ export function createInvariantBaseBlueprint( inputs ) { themeData: bundled( 'bundles/wporg-developer-2023.zip' ), ifAlreadyInstalled: 'overwrite', }, - ...[ - 'code-syntax-block', - 'posts-to-posts', - 'phpdoc-parser', - ].map( ( plugin ) => ( { - step: 'unzip', - zipFile: bundled( `bundles/${ plugin }.zip` ), - extractToPath: '/wordpress/wp-content/plugins', - } ) ), + ...[ 'code-syntax-block', 'posts-to-posts', 'phpdoc-parser' ].map( + ( plugin ) => ( { + step: 'unzip', + zipFile: bundled( `bundles/${ plugin }.zip` ), + extractToPath: '/wordpress/wp-content/plugins', + } ) + ), { step: 'writeFile', path: '/tmp/docs-preview-configure-base.php', @@ -189,7 +208,10 @@ async function ensureComposer( inputs, tools, runImplementation ) { function repositoryRoots( upstreams, inputs ) { const root = ( name ) => path.join( upstreams, name ); const selected = ( name ) => - path.join( root( name ), inputs.dependencies.repositories[ name ].path ); + path.join( + root( name ), + inputs.dependencies.repositories[ name ].path + ); return { phpdocParser: selected( 'phpdocParser' ), wporgDeveloperTheme: selected( 'wporgDeveloper' ), @@ -223,7 +245,11 @@ async function buildInvariantBase( inputs, options ) { for ( const [ name, source, rootName ] of [ [ 'wporg-parent-2021', roots.wporgParentTheme, 'wporg-parent-2021' ], - [ 'wporg-developer-2023', roots.wporgDeveloperTheme, 'wporg-developer-2023' ], + [ + 'wporg-developer-2023', + roots.wporgDeveloperTheme, + 'wporg-developer-2023', + ], [ 'wporg-mu-plugins', roots.wporgMuPluginsFiles, '.' ], [ 'posts-to-posts', roots.postsToPosts, 'posts-to-posts' ], [ 'phpdoc-parser', roots.phpdocParser, 'phpdoc-parser' ], @@ -239,12 +265,19 @@ async function buildInvariantBase( inputs, options ) { await mkdir( path.join( work, 'php' ), { recursive: true } ); for ( const script of [ 'base-policy.php', 'configure-base.php' ] ) { - await cp( path.join( PHP_ROOT, script ), path.join( work, 'php', script ) ); + await cp( + path.join( PHP_ROOT, script ), + path.join( work, 'php', script ) + ); } const blueprint = path.join( work, 'base-blueprint.json' ); await writeFile( blueprint, - `${ JSON.stringify( createInvariantBaseBlueprint( inputs ), null, 2 ) }\n` + `${ JSON.stringify( + createInvariantBaseBlueprint( inputs ), + null, + 2 + ) }\n` ); await runImplementation( options.playgroundCli || executable( 'wp-playground-cli' ), @@ -264,7 +297,11 @@ async function buildInvariantBase( inputs, options ) { ], { label: 'build invariant Playground base' } ); - await copyDirectory( roots.phpdocParser, path.join( inputs.cacheDirectory, 'parser' ), [ '.git' ] ); + await copyDirectory( + roots.phpdocParser, + path.join( inputs.cacheDirectory, 'parser' ), + [ '.git' ] + ); await rm( work, { recursive: true, force: true } ); } @@ -274,9 +311,14 @@ export async function ensureInvariantBase( inputs, options = {} ) { const marker = JSON.parse( await readFile( markerFile, 'utf8' ) ); if ( marker.cacheKey === inputs.cacheKey && - ( await exists( path.join( inputs.cacheDirectory, 'base.zip' ) ) ) && ( await exists( - path.join( inputs.cacheDirectory, 'parser/generate-json-manually.php' ) + path.join( inputs.cacheDirectory, 'base.zip' ) + ) ) && + ( await exists( + path.join( + inputs.cacheDirectory, + 'parser/generate-json-manually.php' + ) ) ) ) { return { ...marker, cacheHit: true }; @@ -284,7 +326,10 @@ export async function ensureInvariantBase( inputs, options = {} ) { } await rm( inputs.cacheDirectory, { recursive: true, force: true } ); await mkdir( inputs.cacheDirectory, { recursive: true } ); - await ( options.buildImplementation || buildInvariantBase )( inputs, options ); + await ( options.buildImplementation || buildInvariantBase )( + inputs, + options + ); const marker = { schemaVersion: 1, cacheKey: inputs.cacheKey, diff --git a/.github/scripts/docs-playground-preview/lib/config.mjs b/.github/scripts/docs-playground-preview/lib/config.mjs index 9ddac2016bcbc..633ae0bf3f6fb 100644 --- a/.github/scripts/docs-playground-preview/lib/config.mjs +++ b/.github/scripts/docs-playground-preview/lib/config.mjs @@ -41,7 +41,9 @@ function assertDigest( value, label ) { export function validateDependencies( dependencies ) { assertObject( dependencies, 'Dependency manifest' ); if ( dependencies.schemaVersion !== 1 ) { - throw new Error( `Unsupported dependency schema ${ dependencies.schemaVersion }.` ); + throw new Error( + `Unsupported dependency schema ${ dependencies.schemaVersion }.` + ); } assertPositiveInteger( dependencies.cacheSchemaVersion, @@ -71,7 +73,9 @@ export function validateDependencies( dependencies ) { throw new Error( 'The WordPress runtime channel must be beta.' ); } if ( dependencies.playground.wordpressVersionApi !== VERSION_API ) { - throw new Error( 'The WordPress beta resolver must use the official API.' ); + throw new Error( + 'The WordPress beta resolver must use the official API.' + ); } if ( dependencies.limits?.snapshotBytes !== 104857600 ) { throw new Error( 'The snapshot limit must be exactly 100 MiB.' ); @@ -101,7 +105,9 @@ export function validateDependencies( dependencies ) { Object.keys( dependencies.repositories ).length !== Object.keys( OFFICIAL_REPOSITORIES ).length ) { - throw new Error( 'The dependency manifest contains an unknown repository.' ); + throw new Error( + 'The dependency manifest contains an unknown repository.' + ); } assertObject( dependencies.validation?.minimumSymbols, 'minimumSymbols' ); @@ -136,7 +142,9 @@ export async function resolveWordPressBeta( headers: { Accept: 'application/json' }, } ); if ( ! response.ok ) { - throw new Error( `WordPress beta API returned HTTP ${ response.status }.` ); + throw new Error( + `WordPress beta API returned HTTP ${ response.status }.` + ); } const body = await response.json(); const offer = body?.offers?.find( @@ -146,7 +154,9 @@ export async function resolveWordPressBeta( `https://downloads.wordpress.org/release/wordpress-${ candidate.version }.zip` ); if ( ! offer ) { - throw new Error( 'The WordPress beta API returned no concrete beta build.' ); + throw new Error( + 'The WordPress beta API returned no concrete beta build.' + ); } return Object.freeze( { channel: 'beta', @@ -168,7 +178,10 @@ export function makeBaseCacheKey( inputs ) { phpVersion: inputs.phpVersion, wordpressVersion: inputs.wordpressVersion, } ) ) { - if ( typeof value !== 'string' || ! /^[A-Za-z0-9_.-]+$/.test( value ) ) { + if ( + typeof value !== 'string' || + ! /^[A-Za-z0-9_.-]+$/.test( value ) + ) { throw new Error( `Cache ${ name } is invalid.` ); } } @@ -183,6 +196,7 @@ export function makeBaseCacheKey( inputs ) { export function isDeploymentEnabled( repository, stagingValue = '' ) { return ( DEPLOYMENT_REPOSITORIES.has( repository ) && - ( repository === 'WordPress/wordpress-develop' || stagingValue === 'true' ) + ( repository === 'WordPress/wordpress-develop' || + stagingValue === 'true' ) ); } diff --git a/.github/scripts/docs-playground-preview/lib/files.mjs b/.github/scripts/docs-playground-preview/lib/files.mjs index d15ee7f60788e..3028c045dd631 100644 --- a/.github/scripts/docs-playground-preview/lib/files.mjs +++ b/.github/scripts/docs-playground-preview/lib/files.mjs @@ -1,12 +1,5 @@ import { createHash } from 'node:crypto'; -import { - copyFile, - mkdir, - readdir, - readFile, - rm, - stat, -} from 'node:fs/promises'; +import { copyFile, mkdir, readdir, readFile, rm, stat } from 'node:fs/promises'; import path from 'node:path'; import { run } from './process.mjs'; @@ -31,7 +24,9 @@ export async function exists( filename ) { export async function findWordPressSourceRoot( candidate ) { const root = path.resolve( candidate ); for ( const directory of [ root, path.join( root, 'src' ) ] ) { - if ( await exists( path.join( directory, 'wp-includes/version.php' ) ) ) { + if ( + await exists( path.join( directory, 'wp-includes/version.php' ) ) + ) { return directory; } } @@ -43,7 +38,9 @@ async function listFiles( root, relative = '' ) { const entries = await readdir( path.join( root, relative ), { withFileTypes: true, } ); - for ( const entry of entries.sort( ( a, b ) => a.name.localeCompare( b.name ) ) ) { + for ( const entry of entries.sort( ( a, b ) => + a.name.localeCompare( b.name ) + ) ) { const child = relative ? `${ relative }/${ entry.name }` : entry.name; if ( entry.isDirectory() ) { files.push( ...( await listFiles( root, child ) ) ); @@ -83,7 +80,8 @@ export async function stageCorePhp( candidate, destination ) { ! relative.endsWith( '.php' ) || [ ...EXCLUDED_SOURCE_DIRECTORIES ].some( ( excluded ) => - relative === excluded || relative.startsWith( `${ excluded }/` ) + relative === excluded || + relative.startsWith( `${ excluded }/` ) ) ) { continue; @@ -94,7 +92,9 @@ export async function stageCorePhp( candidate, destination ) { count++; } if ( count === 0 ) { - throw new Error( 'The WordPress source tree contains no eligible PHP files.' ); + throw new Error( + 'The WordPress source tree contains no eligible PHP files.' + ); } return { source, destination, files: count }; } diff --git a/.github/scripts/docs-playground-preview/lib/inputs.mjs b/.github/scripts/docs-playground-preview/lib/inputs.mjs index 7247f6f442232..d7c6c7460dd62 100644 --- a/.github/scripts/docs-playground-preview/lib/inputs.mjs +++ b/.github/scripts/docs-playground-preview/lib/inputs.mjs @@ -46,7 +46,10 @@ export async function resolveBuildInputs( options ) { return { schemaVersion: 1, cacheKey, - cacheDirectory: path.join( path.resolve( options.cacheRoot ), cacheKey ), + cacheDirectory: path.join( + path.resolve( options.cacheRoot ), + cacheKey + ), cacheInputs, dependencies, wordpress, diff --git a/.github/scripts/docs-playground-preview/lib/process.mjs b/.github/scripts/docs-playground-preview/lib/process.mjs index 06cf8f6be69e1..b8c899a342052 100644 --- a/.github/scripts/docs-playground-preview/lib/process.mjs +++ b/.github/scripts/docs-playground-preview/lib/process.mjs @@ -4,7 +4,7 @@ import { writeFile } from 'node:fs/promises'; export async function run( command, args, options = {} ) { const capture = options.capture || Boolean( options.logFile ); if ( ! options.quiet ) { - console.log( `$ ${ [ command, ...args ].join( ' ' ) }` ); + process.stdout.write( `$ ${ [ command, ...args ].join( ' ' ) }\n` ); } return new Promise( ( resolve, reject ) => { @@ -25,7 +25,10 @@ export async function run( command, args, options = {} ) { child.once( 'close', ( code, signal ) => { ( async () => { if ( options.logFile ) { - await writeFile( options.logFile, `${ stdout }${ stderr }` ); + await writeFile( + options.logFile, + `${ stdout }${ stderr }` + ); } const result = { code, signal, stdout, stderr }; if ( code !== 0 && ! options.allowFailure ) { diff --git a/.github/scripts/docs-playground-preview/lifecycle.mjs b/.github/scripts/docs-playground-preview/lifecycle.mjs index ed0b2872a1576..da1a60bdce6f5 100644 --- a/.github/scripts/docs-playground-preview/lifecycle.mjs +++ b/.github/scripts/docs-playground-preview/lifecycle.mjs @@ -106,7 +106,9 @@ function sessionFrom( options ) { context, repository: options.repository, fetchImplementation: options.fetchImplementation || globalThis.fetch, - warning: options.warning || console.warn, + warning: + options.warning || + ( ( message ) => process.stderr.write( `${ message }\n` ) ), authorize, }; } @@ -259,12 +261,12 @@ async function main() { token: process.env.GITHUB_TOKEN, event, } ); - console.log( `Code Reference lifecycle: ${ result.status }.` ); + process.stdout.write( `Code Reference lifecycle: ${ result.status }.\n` ); } if ( import.meta.url === pathToFileURL( process.argv[ 1 ] ).href ) { main().catch( ( error ) => { - console.error( error ); + process.stderr.write( `${ error.stack || error }\n` ); process.exitCode = 1; } ); } diff --git a/.github/scripts/docs-playground-preview/publish-trunk.mjs b/.github/scripts/docs-playground-preview/publish-trunk.mjs index 3ae8e895b2558..3ddd97bb134af 100644 --- a/.github/scripts/docs-playground-preview/publish-trunk.mjs +++ b/.github/scripts/docs-playground-preview/publish-trunk.mjs @@ -56,7 +56,9 @@ async function establishSession( options ) { context, repository: options.repository, fetchImplementation: options.fetchImplementation || globalThis.fetch, - warning: options.warning || console.warn, + warning: + options.warning || + ( ( message ) => process.stderr.write( `${ message }\n` ) ), now: options.now || ( () => new Date().toISOString() ), handoffDirectory: path.resolve( options.handoffDirectory ), authorize: async () => { @@ -332,12 +334,14 @@ async function main() { artifactAvailable: process.env.HANDOFF_DOWNLOAD_RESULT === 'success', token: process.env.GITHUB_TOKEN, } ); - console.log( `Code Reference trunk publisher: ${ result.status }.` ); + process.stdout.write( + `Code Reference trunk publisher: ${ result.status }.\n` + ); } if ( import.meta.url === pathToFileURL( process.argv[ 1 ] ).href ) { main().catch( ( error ) => { - console.error( error ); + process.stderr.write( `${ error.stack || error }\n` ); process.exitCode = 1; } ); } diff --git a/.github/scripts/docs-playground-preview/publish.mjs b/.github/scripts/docs-playground-preview/publish.mjs index 1f6529cd2edaa..65672e0554ba1 100644 --- a/.github/scripts/docs-playground-preview/publish.mjs +++ b/.github/scripts/docs-playground-preview/publish.mjs @@ -51,7 +51,9 @@ async function establishSession( options ) { context, repository: options.repository, fetchImplementation: options.fetchImplementation || globalThis.fetch, - warning: options.warning || console.warn, + warning: + options.warning || + ( ( message ) => process.stderr.write( `${ message }\n` ) ), now: options.now || ( () => new Date().toISOString() ), handoffDirectory: path.resolve( options.handoffDirectory ), authorize: async () => { @@ -368,12 +370,12 @@ async function main() { artifactAvailable: process.env.HANDOFF_DOWNLOAD_RESULT === 'success', token: process.env.GITHUB_TOKEN, } ); - console.log( `Code Reference publisher: ${ result.status }.` ); + process.stdout.write( `Code Reference publisher: ${ result.status }.\n` ); } if ( import.meta.url === pathToFileURL( process.argv[ 1 ] ).href ) { main().catch( ( error ) => { - console.error( error ); + process.stderr.write( `${ error.stack || error }\n` ); process.exitCode = 1; } ); } diff --git a/.github/scripts/docs-playground-preview/reuse.mjs b/.github/scripts/docs-playground-preview/reuse.mjs index da58eb718a71c..05ebe2d952f7d 100644 --- a/.github/scripts/docs-playground-preview/reuse.mjs +++ b/.github/scripts/docs-playground-preview/reuse.mjs @@ -147,7 +147,8 @@ async function main() { ...options, maximumBytes: Number( options.maximumBytes ), token: process.env.GITHUB_TOKEN, - warning: ( message ) => console.warn( `::warning::${ message }` ), + warning: ( message ) => + process.stderr.write( `::warning::${ message }\n` ), } ); if ( handoff ) { await mkdir( path.dirname( options.output ), { recursive: true } ); @@ -167,8 +168,8 @@ async function main() { if ( import.meta.url === pathToFileURL( process.argv[ 1 ] ).href ) { main().catch( async ( error ) => { - console.warn( - `::warning::Same-SHA reuse check failed: ${ error.message }` + process.stderr.write( + `::warning::Same-SHA reuse check failed: ${ error.message }\n` ); if ( process.env.GITHUB_OUTPUT ) { await writeFile( process.env.GITHUB_OUTPUT, 'reused=false\n', { diff --git a/.github/scripts/docs-playground-preview/test/base.test.mjs b/.github/scripts/docs-playground-preview/test/base.test.mjs index faad291f06a81..690e3a6bbe190 100644 --- a/.github/scripts/docs-playground-preview/test/base.test.mjs +++ b/.github/scripts/docs-playground-preview/test/base.test.mjs @@ -29,7 +29,8 @@ function inputs( cacheDirectory ) { }, dependencies: { playground: { - blueprintSchema: 'https://playground.wordpress.net/blueprint-schema.json', + blueprintSchema: + 'https://playground.wordpress.net/blueprint-schema.json', phpVersion: '8.4', }, }, @@ -45,17 +46,16 @@ test( 'the invariant Blueprint installs dependencies without importing source', assert.deepEqual( blueprint.features, { networking: false } ); assert.equal( blueprint.login, false ); assert.equal( - blueprint.steps.some( ( step ) => /import|reference\.json/.test( JSON.stringify( step ) ) ), + blueprint.steps.some( ( step ) => + /import|reference\.json/.test( JSON.stringify( step ) ) + ), false ); assert.deepEqual( blueprint.steps .filter( ( step ) => step.step === 'installTheme' ) .map( ( step ) => step.themeData.path ), - [ - 'bundles/wporg-parent-2021.zip', - 'bundles/wporg-developer-2023.zip', - ] + [ 'bundles/wporg-parent-2021.zip', 'bundles/wporg-developer-2023.zip' ] ); } ); @@ -81,19 +81,30 @@ test( 'dependency build plan uses pinned harness tools and upstream locks', () = '--ignore-scripts', ] ); assert.match( plan[ 3 ].command, /node_modules\/\.bin\/wp-scripts$/ ); - assert.deepEqual( plan[ 4 ].args.slice( 0, 2 ), [ 'ci', '--ignore-scripts' ] ); + assert.deepEqual( plan[ 4 ].args.slice( 0, 2 ), [ + 'ci', + '--ignore-scripts', + ] ); } ); test( 'the invariant base marker is written last and enables exact reuse', async () => { - const cache = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-base-' ) ); + const cache = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-base-' ) + ); const resolved = inputs( path.join( cache, 'entry' ) ); let builds = 0; const buildImplementation = async ( current ) => { builds++; - await writeFile( path.join( current.cacheDirectory, 'base.zip' ), 'base' ); + await writeFile( + path.join( current.cacheDirectory, 'base.zip' ), + 'base' + ); await mkdir( path.join( current.cacheDirectory, 'parser' ) ); await writeFile( - path.join( current.cacheDirectory, 'parser/generate-json-manually.php' ), + path.join( + current.cacheDirectory, + 'parser/generate-json-manually.php' + ), ' { - const temporary = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-bundle-' ) ); + const temporary = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-bundle-' ) + ); const source = path.join( temporary, 'source' ); await mkdir( path.join( source, 'node_modules' ), { recursive: true } ); await writeFile( path.join( source, 'plugin.php' ), ' { - const root = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-fonts-' ) ); + const root = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-fonts-' ) + ); const fonts = path.join( root, 'global-fonts' ); await mkdir( path.join( fonts, 'NotoSerif' ), { recursive: true } ); await writeFile( path.join( fonts, 'NotoSerif/font.woff2' ), 'large' ); @@ -144,7 +165,9 @@ test( 'unused CJK preview fonts are pruned before the size boundary', async () = '@import "./NotoSerif/NotoSerifJP/style.css";\nbody { color: black; }\n' ); await prunePreviewFonts( root ); - await assert.rejects( readFile( path.join( fonts, 'NotoSerif/font.woff2' ) ) ); + await assert.rejects( + readFile( path.join( fonts, 'NotoSerif/font.woff2' ) ) + ); assert.equal( await readFile( path.join( fonts, 'style.css' ), 'utf8' ), 'body { color: black; }\n' diff --git a/.github/scripts/docs-playground-preview/test/config.test.mjs b/.github/scripts/docs-playground-preview/test/config.test.mjs index a9d3df95822fc..adcb27fc9e85f 100644 --- a/.github/scripts/docs-playground-preview/test/config.test.mjs +++ b/.github/scripts/docs-playground-preview/test/config.test.mjs @@ -158,7 +158,10 @@ test( 'the cache key changes with every material base input', () => { dependencyDigest: 'c'.repeat( 64 ), harnessDigest: 'd'.repeat( 64 ), } ) ) { - assert.notEqual( makeBaseCacheKey( { ...inputs, [ name ]: value } ), baseline ); + assert.notEqual( + makeBaseCacheKey( { ...inputs, [ name ]: value } ), + baseline + ); } } ); diff --git a/.github/scripts/docs-playground-preview/test/files.test.mjs b/.github/scripts/docs-playground-preview/test/files.test.mjs index 8ccb1d2a85bc5..e814e9f4c0541 100644 --- a/.github/scripts/docs-playground-preview/test/files.test.mjs +++ b/.github/scripts/docs-playground-preview/test/files.test.mjs @@ -26,16 +26,22 @@ test( 'run captures output and reports ordinary child failure', async () => { ); assert.equal( result.stdout, 'ready' ); await assert.rejects( - run( process.execPath, [ '-e', 'process.stderr.write("broken");process.exit(2)' ], { - capture: true, - quiet: true, - } ), + run( + process.execPath, + [ '-e', 'process.stderr.write("broken");process.exit(2)' ], + { + capture: true, + quiet: true, + } + ), /broken/ ); } ); test( 'stageCorePhp copies only eligible Core PHP source', async () => { - const root = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-source-' ) ); + const root = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-source-' ) + ); const source = path.join( root, 'src' ); for ( const relative of [ 'wp-includes/version.php', @@ -46,7 +52,10 @@ test( 'stageCorePhp copies only eligible Core PHP source', async () => { await mkdir( path.dirname( path.join( source, relative ) ), { recursive: true, } ); - await writeFile( path.join( source, relative ), ` { const result = await stageCorePhp( root, destination ); assert.equal( result.files, 2 ); assert.equal( - await readFile( path.join( destination, 'wp-admin/admin.php' ), 'utf8' ), + await readFile( + path.join( destination, 'wp-admin/admin.php' ), + 'utf8' + ), ' { } ); test( 'digestTree reflects names and contents but ignores excluded files', async () => { - const root = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-digest-' ) ); + const root = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-digest-' ) + ); await writeFile( path.join( root, 'a' ), 'one' ); await writeFile( path.join( root, 'ignored' ), 'first' ); - const first = await digestTree( root, ( relative ) => relative !== 'ignored' ); + const first = await digestTree( + root, + ( relative ) => relative !== 'ignored' + ); await writeFile( path.join( root, 'ignored' ), 'second' ); assert.equal( await digestTree( root, ( relative ) => relative !== 'ignored' ), @@ -86,12 +105,16 @@ test( 'digestTree reflects names and contents but ignores excluded files', async } ); test( 'acquireRepositories uses Git with each validated full commit', async () => { - const root = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-repos-' ) ); + const root = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-repos-' ) + ); const calls = []; const fakeRun = async ( command, args, options ) => { calls.push( { command, args, options } ); if ( args[ 0 ] === 'clone' ) { - await mkdir( path.join( args.at( -1 ), '.git' ), { recursive: true } ); + await mkdir( path.join( args.at( -1 ), '.git' ), { + recursive: true, + } ); } }; const repositories = { @@ -127,7 +150,9 @@ test( 'acquireRepositories uses Git with each validated full commit', async () = } ); test( 'resolveBuildInputs binds the concrete beta and runner to an exact cache', async () => { - const cacheRoot = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-cache-' ) ); + const cacheRoot = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-cache-' ) + ); const fetchImplementation = async () => ( { ok: true, json: async () => ( { @@ -151,5 +176,8 @@ test( 'resolveBuildInputs binds the concrete beta and runner to an exact cache', } ); assert.equal( inputs.wordpress.version, '7.2-beta1' ); assert.match( inputs.cacheKey, /^docs-preview-base-v1-[0-9a-f]{64}$/ ); - assert.equal( inputs.cacheDirectory, path.join( cacheRoot, inputs.cacheKey ) ); + assert.equal( + inputs.cacheDirectory, + path.join( cacheRoot, inputs.cacheKey ) + ); } ); diff --git a/.github/scripts/docs-playground-preview/test/publish-trunk.test.mjs b/.github/scripts/docs-playground-preview/test/publish-trunk.test.mjs index e65fa97d34bfd..db1c5df673ad9 100644 --- a/.github/scripts/docs-playground-preview/test/publish-trunk.test.mjs +++ b/.github/scripts/docs-playground-preview/test/publish-trunk.test.mjs @@ -154,25 +154,25 @@ class FakeApi { } async createGitCommit() { - const sha = 'f'.repeat( 40 ); - this.commits.set( sha, Buffer.from( this.pendingBlueprint ) ); + const commitSha = 'f'.repeat( 40 ); + this.commits.set( commitSha, Buffer.from( this.pendingBlueprint ) ); this.events.push( 'git:commit' ); - return { sha }; + return { sha: commitSha }; } - async createGitReference( reference, sha ) { + async createGitReference( reference, candidateSha ) { assert.equal( reference, TRUNK_POINTER_REF ); - this.refSha = sha; - this.events.push( `git:create-ref:${ sha }` ); + this.refSha = candidateSha; + this.events.push( `git:create-ref:${ candidateSha }` ); } - async updateGitReference( reference, sha ) { + async updateGitReference( reference, candidateSha ) { assert.equal( reference, TRUNK_POINTER_REF ); if ( this.refUpdateFailure === 'before' ) { throw new Error( 'ref update failed' ); } - this.refSha = sha; - this.events.push( `git:update-ref:${ sha }` ); + this.refSha = candidateSha; + this.events.push( `git:update-ref:${ candidateSha }` ); if ( this.refUpdateFailure === 'after' ) { throw new Error( 'ref update response failed' ); } @@ -189,22 +189,19 @@ class FakeApi { } } -function publicFetch( api, options = {} ) { +function publicFetch( api, fetchOptions = {} ) { return async ( url ) => { - const throughProxy = url.includes( - 'wordpress-playground-cors-proxy.net' - ); const rawStart = url.indexOf( 'https://raw.githubusercontent.com/' ); if ( rawStart !== -1 ) { const rawUrl = new URL( url.slice( rawStart ) ); const parts = rawUrl.pathname.split( '/' ).filter( Boolean ); const reference = parts[ 2 ]; - const commitSha = - reference === 'docs-preview-code-reference' - ? options.staleStablePointer - ? api.previousRefSha - : api.refSha - : reference; + let commitSha = reference; + if ( reference === 'docs-preview-code-reference' ) { + commitSha = fetchOptions.staleStablePointer + ? api.previousRefSha + : api.refSha; + } api.events.push( `fetch:${ reference === 'docs-preview-code-reference' @@ -225,6 +222,9 @@ function publicFetch( api, options = {} ) { arrayBuffer: async () => bytes, }; } + const throughProxy = url.includes( + 'wordpress-playground-cors-proxy.net' + ); const name = new URL( throughProxy ? url.slice( url.indexOf( 'https://github.com/' ) ) @@ -240,7 +240,7 @@ function publicFetch( api, options = {} ) { return { status: 404, headers: { get: () => null } }; } let bytes = api.assetBytes.get( asset.id ); - if ( options.corruptSnapshot && asset.name.endsWith( '.zip' ) ) { + if ( fetchOptions.corruptSnapshot && asset.name.endsWith( '.zip' ) ) { bytes = Buffer.from( 'corrupt' ); } return { From 2b1d7cbb9a3174ad3fc62cfe3962d030cc44ad55 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 15:25:42 +0400 Subject: [PATCH 32/51] Build/Test Tools: Apply Core style to docs PHP The build helpers failed Core's PHP standards gate. Apply the project formatter and make the fixed five-post-type query explicit so PHPCS can verify the prepared statement without a suppression. Build and runtime behavior do not change. --- .../docs-playground-preview/php/base-policy.php | 11 +++++++---- .../docs-playground-preview/php/complete-import.php | 2 +- .../docs-playground-preview/php/configure-base.php | 13 ++++++++----- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/scripts/docs-playground-preview/php/base-policy.php b/.github/scripts/docs-playground-preview/php/base-policy.php index aa8eb49b59d31..eee16d1a53607 100644 --- a/.github/scripts/docs-playground-preview/php/base-policy.php +++ b/.github/scripts/docs-playground-preview/php/base-policy.php @@ -17,16 +17,19 @@ define( 'FEATURE_2021_GLOBAL_HEADER_FOOTER', true ); } -add_filter( 'pre_option_blog_public', static function() { - return '0'; -} ); +add_filter( + 'pre_option_blog_public', + static function () { + return '0'; + } +); add_filter( 'wp_is_application_passwords_available', '__return_false' ); foreach ( array( 'update_core', 'update_plugins', 'update_themes' ) as $transient ) { add_filter( "pre_site_transient_{$transient}", - static function() { + static function () { return (object) array( 'last_checked' => time(), 'checked' => array(), diff --git a/.github/scripts/docs-playground-preview/php/complete-import.php b/.github/scripts/docs-playground-preview/php/complete-import.php index 9839af6059f13..4fab5650fbcda 100644 --- a/.github/scripts/docs-playground-preview/php/complete-import.php +++ b/.github/scripts/docs-playground-preview/php/complete-import.php @@ -32,7 +32,7 @@ $counts[ $name ] = (int) wp_count_posts( $post_type )->publish; } -$marker = array( +$marker = array( 'schemaVersion' => 1, 'stage' => 'complete-import', 'counts' => $counts, diff --git a/.github/scripts/docs-playground-preview/php/configure-base.php b/.github/scripts/docs-playground-preview/php/configure-base.php index 6b866c0b3ca72..eb8cfa0cc48c0 100644 --- a/.github/scripts/docs-playground-preview/php/configure-base.php +++ b/.github/scripts/docs-playground-preview/php/configure-base.php @@ -23,7 +23,7 @@ switch_theme( 'wporg-developer-2023' ); update_option( 'permalink_structure', '/%year%/%monthnum%/%postname%/' ); -$ensure_page = static function( $slug, $title ) { +$ensure_page = static function ( $slug, $title ) { $page = get_page_by_path( $slug, OBJECT, 'page' ); if ( $page ) { return $page->ID; @@ -75,11 +75,14 @@ 'wp-parser-method', 'wp-parser-source-file', ); -$placeholders = implode( ',', array_fill( 0, count( $reference_types ), '%s' ) ); -$count = $wpdb->get_var( +$count = $wpdb->get_var( $wpdb->prepare( - "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type IN ({$placeholders})", - $reference_types + "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type IN ( %s, %s, %s, %s, %s )", + $reference_types[0], + $reference_types[1], + $reference_types[2], + $reference_types[3], + $reference_types[4] ) ); if ( 0 !== (int) $count ) { From 65fa1466bf2bdf0529362a60c52680cfd719340a Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 15:28:39 +0400 Subject: [PATCH 33/51] Build/Test Tools: Preserve docs preview comment state Stale comments used the current head as their hidden source, losing the identity of the retained preview. Delayed synchronize jobs could also overwrite a terminal comment for the current head. Keep the retained preview identity and ignore only genuine terminal state for the event SHA. Otherwise the comment can claim the wrong commit or regress after publication. Non-blocking follow-ups: make terminal state machine-readable and simplify the A-to-B-to-A stale wording. --- .../docs-playground-preview/lib/publisher.mjs | 4 +- .../docs-playground-preview/lifecycle.mjs | 18 +++++++- .../test/lifecycle.test.mjs | 44 +++++++++++++++++-- .../test/publisher.test.mjs | 4 ++ 4 files changed, 63 insertions(+), 7 deletions(-) diff --git a/.github/scripts/docs-playground-preview/lib/publisher.mjs b/.github/scripts/docs-playground-preview/lib/publisher.mjs index 21a3c829aacff..99d7a450e4d87 100644 --- a/.github/scripts/docs-playground-preview/lib/publisher.mjs +++ b/.github/scripts/docs-playground-preview/lib/publisher.mjs @@ -310,8 +310,8 @@ export function renderPreviewComment( state ) { } if ( state.status === 'stale' ) { const header = commentHeader( { - repository: state.currentRepository, - sha: state.currentSha, + repository: state.preview.sourceRepository, + sha: state.preview.sourceSha, } ); return `${ header }\n\n**Status:** Stale\n\nThe latest successful preview was built from ${ commitLink( state.preview.sourceRepository, diff --git a/.github/scripts/docs-playground-preview/lifecycle.mjs b/.github/scripts/docs-playground-preview/lifecycle.mjs index da1a60bdce6f5..c0110077933cf 100644 --- a/.github/scripts/docs-playground-preview/lifecycle.mjs +++ b/.github/scripts/docs-playground-preview/lifecycle.mjs @@ -16,6 +16,14 @@ const CACHE_PREFIX = 'docs-preview-base-'; class LifecycleSuperseded extends Error {} +function isTerminalComment( body ) { + return ( + typeof body === 'string' && + ( body.includes( '\n\n**Status:** Ready\n\n' ) || + body.includes( '\n\n**Status:** Latest attempt failed\n\n' ) ) + ); +} + function positiveInteger( value, label ) { const number = Number( value ); if ( ! Number.isSafeInteger( number ) || number < 1 ) { @@ -143,9 +151,17 @@ async function markStale( session ) { if ( ! comment ) { return { status: 'ignored' }; } + const commentSource = readPreviewCommentSource( comment.body ); + if ( + isTerminalComment( comment.body ) && + commentSource?.repository === session.context.sourceRepository && + commentSource.sha === session.context.sourceSha + ) { + return { status: 'ignored' }; + } const preview = await findPreviousPreview( session ); if ( ! preview ) { - const previous = readPreviewCommentSource( comment.body ); + const previous = commentSource; if ( ! previous ) { return { status: 'unavailable' }; } diff --git a/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs b/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs index 3ebe58fd79cc5..15a9c3f917cf9 100644 --- a/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs +++ b/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs @@ -21,13 +21,13 @@ const currentSha = 'a'.repeat( 40 ); const previewSha = 'c'.repeat( 40 ); const snapshotBytes = Buffer.from( 'snapshot' ); -function pullRequest( state = 'open', labels = [] ) { +function pullRequest( state = 'open', labels = [], headSha = currentSha ) { return { number: 123, state, base: { ref: 'trunk' }, head: { - sha: currentSha, + sha: headSha, ref: 'feature', repo: { full_name: sourceRepository }, }, @@ -35,8 +35,12 @@ function pullRequest( state = 'open', labels = [] ) { }; } -function event( action, state = action === 'closed' ? 'closed' : 'open' ) { - return { action, pull_request: pullRequest( state ) }; +function event( + action, + state = action === 'closed' ? 'closed' : 'open', + headSha = currentSha +) { + return { action, pull_request: pullRequest( state, [], headSha ) }; } function previewMetadata() { @@ -240,6 +244,38 @@ test( 'a newer terminal comment supersedes an older stale event', async () => { assert.equal( api.commentBodies.length, 0 ); } ); +test( 'a delayed stale event ignores the current terminal comment', async () => { + const api = new FakeApi(); + api.comment.body = renderPreviewComment( { + status: 'failed', + sourceRepository, + sourceSha: currentSha, + at: '2026-08-09T13:00:00.000Z', + runUrl: `https://github.com/${ repository }/actions/runs/456`, + previous: previewMetadata(), + } ); + const result = await managePullRequest( + options( api, event( 'synchronize' ) ) + ); + assert.equal( result.status, 'ignored' ); + assert.equal( api.commentBodies.length, 0 ); +} ); + +test( 'returning to a stale preview SHA refreshes obsolete visible state', async () => { + const api = new FakeApi(); + installPreview( api ); + await managePullRequest( options( api, event( 'synchronize' ) ) ); + assert.match( api.comment.body, new RegExp( currentSha ) ); + + api.currentPullRequest = pullRequest( 'open', [], previewSha ); + const result = await managePullRequest( + options( api, event( 'synchronize', 'open', previewSha ) ) + ); + assert.equal( result.status, 'stale' ); + assert.doesNotMatch( api.comment.body, new RegExp( currentSha ) ); + assert.match( api.comment.body, new RegExp( previewSha ) ); +} ); + test( 'a stale failed attempt still tells the maintainer how to rebuild', async () => { const api = new FakeApi(); api.comment.body = renderPreviewComment( { diff --git a/.github/scripts/docs-playground-preview/test/publisher.test.mjs b/.github/scripts/docs-playground-preview/test/publisher.test.mjs index ba905833445e7..464fb42c9d88e 100644 --- a/.github/scripts/docs-playground-preview/test/publisher.test.mjs +++ b/.github/scripts/docs-playground-preview/test/publisher.test.mjs @@ -284,6 +284,10 @@ test( 'the sticky comment has ready, failed, stale, and expired states', () => { assert.match( stale, /add the `docs-preview` label again/i ); assert.match( stale, new RegExp( sha ) ); assert.match( stale, new RegExp( 'c'.repeat( 40 ) ) ); + assert.deepEqual( readPreviewCommentSource( stale ), { + repository: preview.sourceRepository, + sha, + } ); const unavailable = renderPreviewComment( { status: 'stale-unavailable', previousRepository: preview.sourceRepository, From 7cc50769f14dc1b93da2e8d31bae989d5c4dd6fb Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 15:37:56 +0400 Subject: [PATCH 34/51] Build/Test Tools: Preserve the last docs preview link A shared proxy failure made previous-preview discovery return nothing. Failure reporting then erased the last successful link even though its assets remained. Recover only the publisher-rendered link and source identity from the existing bot comment. A candidate failure must report the failure without making the previous preview unavailable. --- .../docs-playground-preview/lib/publisher.mjs | 26 ++++++++ .../docs-playground-preview/publish.mjs | 9 ++- .../test/publish.test.mjs | 59 ++++++++++++++++++- .../test/publisher.test.mjs | 11 ++++ 4 files changed, 103 insertions(+), 2 deletions(-) diff --git a/.github/scripts/docs-playground-preview/lib/publisher.mjs b/.github/scripts/docs-playground-preview/lib/publisher.mjs index 99d7a450e4d87..22e87dfb29f71 100644 --- a/.github/scripts/docs-playground-preview/lib/publisher.mjs +++ b/.github/scripts/docs-playground-preview/lib/publisher.mjs @@ -281,6 +281,32 @@ export function readPreviewCommentSource( body ) { return match ? { repository: match[ 1 ], sha: match[ 2 ] } : null; } +export function readPreviewCommentSuccess( body ) { + if ( typeof body !== 'string' ) { + return null; + } + const link = body.match( + /\[(?:Open Code Reference preview|Latest successful docs preview)\]\((https:\/\/playground\.wordpress\.net\/[^\s)]+)\)/ + ); + if ( ! link ) { + return null; + } + const previousSource = body.match( + /Latest successful docs preview\]\([^)]+\) — built from \[([0-9a-f]{40})\]\(https:\/\/github\.com\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)\/commit\/\1\)/ + ); + const source = previousSource + ? { repository: previousSource[ 2 ], sha: previousSource[ 1 ] } + : readPreviewCommentSource( body ); + if ( ! source ) { + return null; + } + return { + sourceRepository: source.repository, + sourceSha: source.sha, + publication: { playgroundUrl: link[ 1 ] }, + }; +} + export function renderPreviewComment( state ) { if ( state.status === 'ready' ) { const header = commentHeader( { diff --git a/.github/scripts/docs-playground-preview/publish.mjs b/.github/scripts/docs-playground-preview/publish.mjs index 65672e0554ba1..2768bac75998c 100644 --- a/.github/scripts/docs-playground-preview/publish.mjs +++ b/.github/scripts/docs-playground-preview/publish.mjs @@ -9,6 +9,7 @@ import { assertLatestAuthorized, createPublishedMetadata, inspectHandoff, + readPreviewCommentSuccess, renderPreviewComment, validateCandidateFile, validatePublicationContext, @@ -152,7 +153,13 @@ async function runIndependently( operations, message ) { } async function reportFailure( session, excluded = new Set() ) { - const previous = await findPreviousPreview( session, excluded ); + let previous = await findPreviousPreview( session, excluded ); + if ( ! previous ) { + const comment = await session.api.findPreviewComment( + session.context.pullRequestNumber + ); + previous = readPreviewCommentSuccess( comment?.body ); + } const body = renderPreviewComment( { status: 'failed', sourceRepository: session.context.sourceRepository, diff --git a/.github/scripts/docs-playground-preview/test/publish.test.mjs b/.github/scripts/docs-playground-preview/test/publish.test.mjs index db94c1e4c536e..853057440030c 100644 --- a/.github/scripts/docs-playground-preview/test/publish.test.mjs +++ b/.github/scripts/docs-playground-preview/test/publish.test.mjs @@ -5,7 +5,10 @@ import os from 'node:os'; import path from 'node:path'; import { test } from 'node:test'; -import { createPublishedMetadata } from '../lib/publisher.mjs'; +import { + createPublishedMetadata, + renderPreviewComment, +} from '../lib/publisher.mjs'; import { PLAYGROUND_ORIGIN, createReuseHandoff, @@ -375,6 +378,60 @@ test( 'public delivery failure keeps the previous healthy preview', async () => assert.match( api.comments[ 0 ].body, new RegExp( 'c'.repeat( 40 ) ) ); } ); +test( 'a proxy outage cannot remove the previous comment link', async () => { + const api = new FakeApi(); + const oldBytes = Buffer.from( 'old snapshot' ); + const oldBuild = buildMetadata( { + sourceSha: 'c'.repeat( 40 ), + workflowRunId: '300', + runUrl: `https://github.com/${ repository }/actions/runs/300`, + snapshotFilename: snapshotAssetName( { + pullRequestNumber: 123, + sourceSha: 'c'.repeat( 40 ), + workflowRunId: 300, + workflowRunAttempt: 1, + } ), + snapshotBytes: oldBytes.byteLength, + snapshotSha256: createHash( 'sha256' ) + .update( oldBytes ) + .digest( 'hex' ), + } ); + const oldPreview = installPublishedPreview( api, oldBuild, oldBytes ); + api.comments = [ + { + id: 1, + body: renderPreviewComment( { + status: 'ready', + preview: oldPreview, + runUrl: oldBuild.runUrl, + } ), + }, + ]; + + const current = buildMetadata(); + const directory = await handoffDirectory( current ); + await assert.rejects( + publishPullRequest( + options( api, directory, { + fetchImplementation: publicFetch( + api, + new Set( [ + oldBuild.snapshotFilename, + current.snapshotFilename, + ] ) + ), + } ) + ), + /digest/ + ); + assert.match( api.comments[ 0 ].body, /Latest attempt failed/ ); + assert.match( api.comments[ 0 ].body, /Latest successful docs preview/ ); + assert.match( api.comments[ 0 ].body, new RegExp( 'c'.repeat( 40 ) ) ); + assert.ok( + api.comments[ 0 ].body.includes( oldPreview.publication.playgroundUrl ) + ); +} ); + test( 'a retry cannot delete a same-name preview it did not upload', async () => { const api = new FakeApi(); const current = buildMetadata(); diff --git a/.github/scripts/docs-playground-preview/test/publisher.test.mjs b/.github/scripts/docs-playground-preview/test/publisher.test.mjs index 464fb42c9d88e..a90dceeeeb5f2 100644 --- a/.github/scripts/docs-playground-preview/test/publisher.test.mjs +++ b/.github/scripts/docs-playground-preview/test/publisher.test.mjs @@ -9,6 +9,7 @@ import { assertLatestAuthorized, createPublishedMetadata, inspectHandoff, + readPreviewCommentSuccess, readPreviewCommentSource, renderPreviewComment, validateCandidateFile, @@ -261,6 +262,11 @@ test( 'the sticky comment has ready, failed, stale, and expired states', () => { repository: preview.sourceRepository, sha, } ); + assert.deepEqual( readPreviewCommentSuccess( ready ), { + sourceRepository: preview.sourceRepository, + sourceSha: sha, + publication: { playgroundUrl: preview.publication.playgroundUrl }, + } ); assert.equal( ready.match( /Open Code Reference preview/g ).length, 1 ); const failed = renderPreviewComment( { @@ -273,6 +279,11 @@ test( 'the sticky comment has ready, failed, stale, and expired states', () => { } ); assert.match( failed, /Latest attempt failed/ ); assert.equal( failed.match( /Latest successful docs preview/g ).length, 1 ); + assert.deepEqual( readPreviewCommentSuccess( failed ), { + sourceRepository: preview.sourceRepository, + sourceSha: sha, + publication: { playgroundUrl: preview.publication.playgroundUrl }, + } ); const stale = renderPreviewComment( { status: 'stale', From 6fa106a59968a28f781e37fc7ec2f6fa4a80b516 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 15:43:01 +0400 Subject: [PATCH 35/51] Docs: Point the staging preview at staging The upstream README must target WordPress, but that URL cannot prove the staging pointer. Use the staging repository only in the staging branch so its next push exercises the real stable entry point. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 648b0e884d292..b1766dc46226c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Welcome to the WordPress development repository! Please check out the [contributor handbook](https://make.wordpress.org/core/handbook/) for information about how to open bug reports, contribute patches, test changes, write documentation, or get involved in any way you can. -[Open the latest successful Core Code Reference preview in WordPress Playground](https://playground.wordpress.net/?blueprint-url=https%3A%2F%2Fwordpress-playground-cors-proxy.net%2Fhttps%3A%2F%2Fraw.githubusercontent.com%2FWordPress%2Fwordpress-develop%2Fdocs-preview-code-reference%2Fcode-reference-trunk.json). +[Open the latest successful Core Code Reference preview in WordPress Playground](https://playground.wordpress.net/?blueprint-url=https%3A%2F%2Fwordpress-playground-cors-proxy.net%2Fhttps%3A%2F%2Fraw.githubusercontent.com%2Fsirreal%2Fwordpress-develop%2Fdocs-preview-code-reference%2Fcode-reference-trunk.json). * [Getting Started](#getting-started) * [Credentials](#credentials) From a19ceeeafea83e7ffad5c82149b6f09d3cc7f767 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 21:11:53 +0400 Subject: [PATCH 36/51] Build/Test Tools: Let Playground choose validation workers The validator forced one worker even though the pinned CLI warns that low worker counts can deadlock on file locks. A ready server could then stop answering health checks and invalidate every snapshot. Use the CLI default and lock that command line down. --- .../docs-playground-preview/lib/validate.mjs | 46 +++++++++++-------- .../test/validate.test.mjs | 18 ++++++++ 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/.github/scripts/docs-playground-preview/lib/validate.mjs b/.github/scripts/docs-playground-preview/lib/validate.mjs index be2c745bc6be0..5f99e443a0db8 100644 --- a/.github/scripts/docs-playground-preview/lib/validate.mjs +++ b/.github/scripts/docs-playground-preview/lib/validate.mjs @@ -62,6 +62,30 @@ function startServer( command, args ) { return { child, closed, output: () => output }; } +export function createValidationServerArguments( + inputs, + blueprint, + baseUrl, + port +) { + return [ + 'server', + '--php', + inputs.dependencies.playground.phpVersion, + '--wp', + inputs.wordpress.version, + '--blueprint', + blueprint, + '--blueprint-may-read-adjacent-files', + '--site-url', + baseUrl, + '--port', + String( port ), + '--verbosity', + 'normal', + ]; +} + async function stopServer( server ) { if ( server.child.exitCode !== null || server.child.signalCode !== null ) { return; @@ -119,24 +143,10 @@ async function withPlaygroundServer( inputs, options, inspect ) { ); const port = await availablePort(); const baseUrl = `http://127.0.0.1:${ port }`; - const server = startServer( options.playgroundCli, [ - 'server', - '--php', - inputs.dependencies.playground.phpVersion, - '--wp', - inputs.wordpress.version, - '--blueprint', - blueprint, - '--blueprint-may-read-adjacent-files', - '--site-url', - baseUrl, - '--port', - String( port ), - '--workers', - '1', - '--verbosity', - 'normal', - ] ); + const server = startServer( + options.playgroundCli, + createValidationServerArguments( inputs, blueprint, baseUrl, port ) + ); try { await waitForBoot( baseUrl, server, options.fetchImplementation ); return await inspect( baseUrl ); diff --git a/.github/scripts/docs-playground-preview/test/validate.test.mjs b/.github/scripts/docs-playground-preview/test/validate.test.mjs index 26735746d4c3b..e108716d85fa4 100644 --- a/.github/scripts/docs-playground-preview/test/validate.test.mjs +++ b/.github/scripts/docs-playground-preview/test/validate.test.mjs @@ -3,6 +3,7 @@ import { test } from 'node:test'; import { createValidationBlueprint, + createValidationServerArguments, inspectSnapshotBehavior, validateSnapshot, } from '../lib/validate.mjs'; @@ -119,6 +120,23 @@ test( 'validation Blueprint boots the exact snapshot without login or networking ] ); } ); +test( 'validation server uses the pinned Playground worker default', () => { + const args = createValidationServerArguments( + inputs(), + '/tmp/validation-blueprint.json', + 'http://127.0.0.1:9400', + 9400 + ); + assert.equal( args.includes( '--workers' ), false ); + assert.deepEqual( args.slice( 0, 5 ), [ + 'server', + '--php', + '8.4', + '--wp', + '7.2-beta1', + ] ); +} ); + test( 'behavioral validation covers health, routes, search, and banner', async () => { const result = await inspectSnapshotBehavior( inputs(), { baseUrl: 'http://127.0.0.1:9400', From 62feb2a0da3ce4e6145867497b1781ab452550fe Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 22:04:14 +0400 Subject: [PATCH 37/51] Build/Test Tools: Use six Playground snapshot workers GitHub exposes four CPUs, so Playground selected three workers and exported a malformed SQLite database in both staging builds. Invoke the pinned programmatic API with its recommended six workers and validate with the same count. If this is wrong, every reference route dies with a database error. --- .../docs-playground-preview/lib/base.mjs | 24 +++++-------- .../docs-playground-preview/lib/final.mjs | 36 ++++++++----------- .../lib/playground.mjs | 21 +++++++++++ .../docs-playground-preview/lib/validate.mjs | 4 +++ .../test/final.test.mjs | 19 +++++----- .../test/playground.test.mjs | 28 +++++++++++++++ .../test/validate.test.mjs | 4 +-- 7 files changed, 89 insertions(+), 47 deletions(-) create mode 100644 .github/scripts/docs-playground-preview/lib/playground.mjs create mode 100644 .github/scripts/docs-playground-preview/test/playground.test.mjs diff --git a/.github/scripts/docs-playground-preview/lib/base.mjs b/.github/scripts/docs-playground-preview/lib/base.mjs index 88bfab55e9db6..b21ee0f7f517f 100644 --- a/.github/scripts/docs-playground-preview/lib/base.mjs +++ b/.github/scripts/docs-playground-preview/lib/base.mjs @@ -4,6 +4,7 @@ import path from 'node:path'; import { copyDirectory, downloadFile, zipDirectory } from './archive.mjs'; import { acquireRepositories, exists } from './files.mjs'; +import { buildSnapshot } from './playground.mjs'; import { run } from './process.mjs'; const LIBRARY_ROOT = path.dirname( fileURLToPath( import.meta.url ) ); @@ -279,23 +280,16 @@ async function buildInvariantBase( inputs, options ) { 2 ) }\n` ); - await runImplementation( + await ( options.buildSnapshotImplementation || buildSnapshot )( options.playgroundCli || executable( 'wp-playground-cli' ), - [ - 'build-snapshot', - '--php', - inputs.dependencies.playground.phpVersion, - '--wp', - inputs.wordpress.version, - '--blueprint', + { + php: inputs.dependencies.playground.phpVersion, + wp: inputs.wordpress.version, blueprint, - '--blueprint-may-read-adjacent-files', - '--outfile', - path.join( inputs.cacheDirectory, 'base.zip' ), - '--verbosity', - 'normal', - ], - { label: 'build invariant Playground base' } + 'blueprint-may-read-adjacent-files': true, + outfile: path.join( inputs.cacheDirectory, 'base.zip' ), + verbosity: 'normal', + } ); await copyDirectory( roots.phpdocParser, diff --git a/.github/scripts/docs-playground-preview/lib/final.mjs b/.github/scripts/docs-playground-preview/lib/final.mjs index cffa6a8d145dd..a9816b8e5314c 100644 --- a/.github/scripts/docs-playground-preview/lib/final.mjs +++ b/.github/scripts/docs-playground-preview/lib/final.mjs @@ -3,7 +3,7 @@ import { fileURLToPath } from 'node:url'; import path from 'node:path'; import { sha256File } from './files.mjs'; -import { run } from './process.mjs'; +import { buildSnapshot } from './playground.mjs'; import { renderRuntimePlugin } from './runtime.mjs'; const LIBRARY_ROOT = path.dirname( fileURLToPath( import.meta.url ) ); @@ -92,7 +92,6 @@ export function createFinalBlueprint( inputs ) { } export async function packageFinalSnapshot( inputs, options ) { - const runImplementation = options.runImplementation || run; const work = path.resolve( options.workDirectory ); const output = path.resolve( options.output ); await rm( work, { recursive: true, force: true } ); @@ -123,27 +122,22 @@ export async function packageFinalSnapshot( inputs, options ) { blueprint, `${ JSON.stringify( createFinalBlueprint( inputs ), null, 2 ) }\n` ); - await runImplementation( + await ( options.buildSnapshotImplementation || buildSnapshot )( options.playgroundCli, - [ - 'build-snapshot', - '--php', - inputs.dependencies.playground.phpVersion, - '--wp', - inputs.wordpress.version, - '--blueprint', + { + php: inputs.dependencies.playground.phpVersion, + wp: inputs.wordpress.version, blueprint, - '--blueprint-may-read-adjacent-files', - '--mount', - `${ path.resolve( - options.stagedSource - ) }:/tmp/docs-preview-source`, - '--outfile', - output, - '--verbosity', - 'normal', - ], - { label: 'build final Code Reference snapshot' } + 'blueprint-may-read-adjacent-files': true, + mount: [ + { + hostPath: path.resolve( options.stagedSource ), + vfsPath: '/tmp/docs-preview-source', + }, + ], + outfile: output, + verbosity: 'normal', + } ); const snapshot = await stat( output ); if ( snapshot.size > inputs.dependencies.limits.snapshotBytes ) { diff --git a/.github/scripts/docs-playground-preview/lib/playground.mjs b/.github/scripts/docs-playground-preview/lib/playground.mjs new file mode 100644 index 0000000000000..a2958e815915f --- /dev/null +++ b/.github/scripts/docs-playground-preview/lib/playground.mjs @@ -0,0 +1,21 @@ +import path from 'node:path'; +import { pathToFileURL } from 'node:url'; + +export const PLAYGROUND_WORKERS = 6; + +export function createSnapshotArguments( options ) { + return { + ...options, + command: 'build-snapshot', + workers: PLAYGROUND_WORKERS, + }; +} + +export async function buildSnapshot( playgroundCli, options ) { + const modulePath = path.resolve( + path.dirname( playgroundCli ), + '../@wp-playground/cli/index.js' + ); + const { runCLI } = await import( pathToFileURL( modulePath ) ); + await runCLI( createSnapshotArguments( options ) ); +} diff --git a/.github/scripts/docs-playground-preview/lib/validate.mjs b/.github/scripts/docs-playground-preview/lib/validate.mjs index 5f99e443a0db8..b46a16bfc21e0 100644 --- a/.github/scripts/docs-playground-preview/lib/validate.mjs +++ b/.github/scripts/docs-playground-preview/lib/validate.mjs @@ -3,6 +3,8 @@ import { copyFile, mkdir, rm, writeFile } from 'node:fs/promises'; import { createServer } from 'node:net'; import path from 'node:path'; +import { PLAYGROUND_WORKERS } from './playground.mjs'; + const BANNER_ID = 'wporg-code-reference-preview-provenance'; function bundled( resourcePath ) { @@ -81,6 +83,8 @@ export function createValidationServerArguments( baseUrl, '--port', String( port ), + '--workers', + String( PLAYGROUND_WORKERS ), '--verbosity', 'normal', ]; diff --git a/.github/scripts/docs-playground-preview/test/final.test.mjs b/.github/scripts/docs-playground-preview/test/final.test.mjs index a18ffa40afb50..c6cead429d0fe 100644 --- a/.github/scripts/docs-playground-preview/test/final.test.mjs +++ b/.github/scripts/docs-playground-preview/test/final.test.mjs @@ -94,8 +94,8 @@ async function fixture( snapshotBytes = 1024 ) { test( 'final packaging returns publisher metadata for a bounded snapshot', async () => { const current = await fixture(); let invocation; - const runImplementation = async ( command, args ) => { - invocation = { command, args }; + const buildSnapshotImplementation = async ( command, options ) => { + invocation = { command, options }; await writeFile( current.output, 'snapshot' ); }; const snapshot = await packageFinalSnapshot( current.resolved, { @@ -110,16 +110,17 @@ test( 'final packaging returns publisher metadata for a bounded snapshot', async generationTimestamp: '2026-08-09T12:34:56.000Z', runUrl: 'https://github.com/example/wordpress-develop/actions/runs/123', }, - runImplementation, + buildSnapshotImplementation, } ); assert.equal( snapshot.filename, 'snapshot.zip' ); assert.equal( snapshot.bytes, 8 ); assert.match( snapshot.sha256, /^[0-9a-f]{64}$/ ); assert.equal( invocation.command, '/tools/wp-playground-cli' ); - assert.equal( - invocation.args[ invocation.args.indexOf( '--mount' ) + 1 ], - '/source:/tmp/docs-preview-source' - ); + assert.deepEqual( invocation.options.mount, [ + { hostPath: '/source', vfsPath: '/tmp/docs-preview-source' }, + ] ); + assert.equal( invocation.options.php, '8.4' ); + assert.equal( invocation.options.wp, '7.2-beta1' ); const blueprint = JSON.parse( await readFile( path.join( current.root, 'work/final-blueprint.json' ), @@ -131,7 +132,7 @@ test( 'final packaging returns publisher metadata for a bounded snapshot', async test( 'the 100 MiB snapshot boundary always fails closed', async () => { const current = await fixture( 8 ); - const runImplementation = async () => { + const buildSnapshotImplementation = async () => { const output = await open( current.output, 'w' ); await output.truncate( 9 ); await output.close(); @@ -149,7 +150,7 @@ test( 'the 100 MiB snapshot boundary always fails closed', async () => { generationTimestamp: '2026-08-09T12:34:56.000Z', runUrl: 'https://github.com/example/wordpress-develop/actions/runs/123', }, - runImplementation, + buildSnapshotImplementation, } ), /exceeds 100 MiB/ ); diff --git a/.github/scripts/docs-playground-preview/test/playground.test.mjs b/.github/scripts/docs-playground-preview/test/playground.test.mjs new file mode 100644 index 0000000000000..72380a7f7afa1 --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/playground.test.mjs @@ -0,0 +1,28 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; + +import { + createSnapshotArguments, + PLAYGROUND_WORKERS, +} from '../lib/playground.mjs'; + +test( 'snapshot builds use six Playground workers', () => { + assert.deepEqual( + createSnapshotArguments( { + php: '8.4', + wp: '7.2-beta1', + blueprint: '/tmp/blueprint.json', + outfile: '/tmp/snapshot.zip', + workers: 2, + command: 'server', + } ), + { + php: '8.4', + wp: '7.2-beta1', + blueprint: '/tmp/blueprint.json', + outfile: '/tmp/snapshot.zip', + workers: PLAYGROUND_WORKERS, + command: 'build-snapshot', + } + ); +} ); diff --git a/.github/scripts/docs-playground-preview/test/validate.test.mjs b/.github/scripts/docs-playground-preview/test/validate.test.mjs index e108716d85fa4..df3836fd09c3c 100644 --- a/.github/scripts/docs-playground-preview/test/validate.test.mjs +++ b/.github/scripts/docs-playground-preview/test/validate.test.mjs @@ -120,14 +120,14 @@ test( 'validation Blueprint boots the exact snapshot without login or networking ] ); } ); -test( 'validation server uses the pinned Playground worker default', () => { +test( 'validation server uses six Playground workers', () => { const args = createValidationServerArguments( inputs(), '/tmp/validation-blueprint.json', 'http://127.0.0.1:9400', 9400 ); - assert.equal( args.includes( '--workers' ), false ); + assert.equal( args[ args.indexOf( '--workers' ) + 1 ], '6' ); assert.deepEqual( args.slice( 0, 5 ), [ 'server', '--php', From 37384b8de76ec1824bb0b1dafe62fb1c46fd9df5 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 22:59:44 +0400 Subject: [PATCH 38/51] Build/Test Tools: Serialize Playground snapshot builds Playground exports a live filesystem after running the Blueprint. Multiple build workers left SQLite corrupt after the full reference import. Build with one worker, then boot the finished snapshot with six. If this regresses, every reference route dies before publication. --- .github/scripts/docs-playground-preview/lib/playground.mjs | 5 +++-- .github/scripts/docs-playground-preview/lib/validate.mjs | 4 ++-- .../docs-playground-preview/test/playground.test.mjs | 7 ++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/scripts/docs-playground-preview/lib/playground.mjs b/.github/scripts/docs-playground-preview/lib/playground.mjs index a2958e815915f..0fbc15e8231d8 100644 --- a/.github/scripts/docs-playground-preview/lib/playground.mjs +++ b/.github/scripts/docs-playground-preview/lib/playground.mjs @@ -1,13 +1,14 @@ import path from 'node:path'; import { pathToFileURL } from 'node:url'; -export const PLAYGROUND_WORKERS = 6; +export const PLAYGROUND_BUILD_WORKERS = 1; +export const PLAYGROUND_SERVER_WORKERS = 6; export function createSnapshotArguments( options ) { return { ...options, command: 'build-snapshot', - workers: PLAYGROUND_WORKERS, + workers: PLAYGROUND_BUILD_WORKERS, }; } diff --git a/.github/scripts/docs-playground-preview/lib/validate.mjs b/.github/scripts/docs-playground-preview/lib/validate.mjs index b46a16bfc21e0..0e23b74433263 100644 --- a/.github/scripts/docs-playground-preview/lib/validate.mjs +++ b/.github/scripts/docs-playground-preview/lib/validate.mjs @@ -3,7 +3,7 @@ import { copyFile, mkdir, rm, writeFile } from 'node:fs/promises'; import { createServer } from 'node:net'; import path from 'node:path'; -import { PLAYGROUND_WORKERS } from './playground.mjs'; +import { PLAYGROUND_SERVER_WORKERS } from './playground.mjs'; const BANNER_ID = 'wporg-code-reference-preview-provenance'; @@ -84,7 +84,7 @@ export function createValidationServerArguments( '--port', String( port ), '--workers', - String( PLAYGROUND_WORKERS ), + String( PLAYGROUND_SERVER_WORKERS ), '--verbosity', 'normal', ]; diff --git a/.github/scripts/docs-playground-preview/test/playground.test.mjs b/.github/scripts/docs-playground-preview/test/playground.test.mjs index 72380a7f7afa1..ba6e04aa5c206 100644 --- a/.github/scripts/docs-playground-preview/test/playground.test.mjs +++ b/.github/scripts/docs-playground-preview/test/playground.test.mjs @@ -3,10 +3,11 @@ import { test } from 'node:test'; import { createSnapshotArguments, - PLAYGROUND_WORKERS, + PLAYGROUND_BUILD_WORKERS, } from '../lib/playground.mjs'; -test( 'snapshot builds use six Playground workers', () => { +test( 'snapshot builds serialize Playground filesystem access', () => { + assert.equal( PLAYGROUND_BUILD_WORKERS, 1 ); assert.deepEqual( createSnapshotArguments( { php: '8.4', @@ -21,7 +22,7 @@ test( 'snapshot builds use six Playground workers', () => { wp: '7.2-beta1', blueprint: '/tmp/blueprint.json', outfile: '/tmp/snapshot.zip', - workers: PLAYGROUND_WORKERS, + workers: PLAYGROUND_BUILD_WORKERS, command: 'build-snapshot', } ); From 3317f97b6b6f32e304c758c4a24c8e65cffaa94f Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 23:39:56 +0400 Subject: [PATCH 39/51] Build/Test Tools: Preserve ambiguous trunk assets GitHub can expose a stale ref immediately after creating the stable pointer. Treating that read as proof of failure deleted the assets the new pointer named. Any unconfirmed mutation now retains both generations; the next successful publication cleans the orphan. --- .../docs-playground-preview/publish-trunk.mjs | 6 ++- .../test/publish-trunk.test.mjs | 43 +++++++++++++++++-- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/.github/scripts/docs-playground-preview/publish-trunk.mjs b/.github/scripts/docs-playground-preview/publish-trunk.mjs index 3ddd97bb134af..e7c942c5bc2df 100644 --- a/.github/scripts/docs-playground-preview/publish-trunk.mjs +++ b/.github/scripts/docs-playground-preview/publish-trunk.mjs @@ -203,7 +203,11 @@ async function moveStablePointer( session, previousRef, commit, transaction ) { commit.sha ); if ( ! moved ) { - throw mutationError || new Error( 'The trunk pointer did not move.' ); + const error = + mutationError || + new Error( 'The stable trunk pointer mutation has unknown state.' ); + error.pointerStateUnknown = true; + throw error; } transaction.pointerPublished = true; } diff --git a/.github/scripts/docs-playground-preview/test/publish-trunk.test.mjs b/.github/scripts/docs-playground-preview/test/publish-trunk.test.mjs index db1c5df673ad9..9ebd9c089a33b 100644 --- a/.github/scripts/docs-playground-preview/test/publish-trunk.test.mjs +++ b/.github/scripts/docs-playground-preview/test/publish-trunk.test.mjs @@ -91,6 +91,7 @@ class FakeApi { this.refReads = 0; this.refUpdateFailure = null; this.failRefResolution = false; + this.staleRefAfterMutation = false; } async getRun() { @@ -135,11 +136,15 @@ class FakeApi { async getGitReference( reference ) { assert.equal( reference, TRUNK_POINTER_REF ); this.refReads++; - this.events.push( `git:read-ref:${ this.refSha }` ); + const visibleSha = + this.staleRefAfterMutation && this.refReads > 1 + ? this.previousRefSha + : this.refSha; + this.events.push( `git:read-ref:${ visibleSha }` ); if ( this.failRefResolution && this.refReads > 1 ) { throw new Error( 'ref resolution failed' ); } - return this.refSha ? { object: { sha: this.refSha } } : null; + return visibleSha ? { object: { sha: visibleSha } } : null; } async createGitBlob( content ) { @@ -409,7 +414,7 @@ test( 'public candidate failure leaves the previous pointer and snapshot intact' assert.deepEqual( api.deleted.sort(), [ 10, 11 ] ); } ); -test( 'a failed ref update leaves the previous pointer intact', async () => { +test( 'a failed ref update retains the previous and candidate assets', async () => { const api = new FakeApi(); installPrevious( api ); api.refUpdateFailure = 'before'; @@ -420,7 +425,8 @@ test( 'a failed ref update leaves the previous pointer intact', async () => { ); assert.equal( api.refSha, api.previousRefSha ); assert.ok( api.assets.some( ( asset ) => asset.id === 1 ) ); - assert.deepEqual( api.deleted.sort(), [ 10, 11 ] ); + assert.ok( api.assets.some( ( asset ) => asset.id === 10 ) ); + assert.deepEqual( api.deleted, [] ); } ); test( 'an ambiguous ref response resolves the candidate and completes', async () => { @@ -452,6 +458,35 @@ test( 'unresolved ref state retains both valid generations', async () => { assert.deepEqual( api.deleted, [] ); } ); +test( 'a stale read after ref creation retains the candidate assets', async () => { + const api = new FakeApi(); + api.staleRefAfterMutation = true; + const directory = await handoffDirectory( buildMetadata() ); + await assert.rejects( + publishTrunk( options( api, directory ) ), + /pointer mutation has unknown state/ + ); + assert.equal( api.refSha, 'f'.repeat( 40 ) ); + assert.equal( api.assets.length, 2 ); + assert.deepEqual( api.deleted, [] ); +} ); + +test( 'an update error and stale read retain both asset generations', async () => { + const api = new FakeApi(); + installPrevious( api ); + api.refUpdateFailure = 'after'; + api.staleRefAfterMutation = true; + const directory = await handoffDirectory( buildMetadata() ); + await assert.rejects( + publishTrunk( options( api, directory ) ), + /ref update response failed/ + ); + assert.equal( api.refSha, 'f'.repeat( 40 ) ); + assert.ok( api.assets.some( ( asset ) => asset.id === 1 ) ); + assert.ok( api.assets.some( ( asset ) => asset.id === 10 ) ); + assert.deepEqual( api.deleted, [] ); +} ); + test( 'cleanup failure keeps the new validated pointer and both snapshots', async () => { const api = new FakeApi(); installPrevious( api ); From c34eec09fd94d5e8a9c59417db129caa2b470f3c Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 15:44:26 +0400 Subject: [PATCH 40/51] Docs: Add a staging preview fixture The staging lifecycle needs a fork head distinct from its base. Change one PHPDoc sentence so the parser input and exact source SHA can be verified without changing executable Core behavior. --- src/wp-includes/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index f5002a45de1e8..86374fb67e9f6 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -3063,7 +3063,7 @@ function wp_get_default_extension_for_mime_type( $mime_type ) { /** * Retrieves the file type from the file name. * - * You can optionally define the mime array, if needed. + * The MIME array can optionally be supplied when needed. * * @since 2.0.4 * From 6ead3c5608125990b3928f8e88d5fafbe28fc8e1 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Sun, 9 Aug 2026 21:37:22 +0400 Subject: [PATCH 41/51] Docs: Advance the staging preview fixture The latest-wins test needs a new fork head while the label remains present. Change parser-visible prose without changing executable Core behavior so the obsolete run must yield to the new SHA. --- src/wp-includes/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 86374fb67e9f6..021a477feffb4 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -3063,7 +3063,7 @@ function wp_get_default_extension_for_mime_type( $mime_type ) { /** * Retrieves the file type from the file name. * - * The MIME array can optionally be supplied when needed. + * A custom MIME array may be supplied when needed. * * @since 2.0.4 * From b8059b0127b1500af0c952925ae7883c29b5b334 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Mon, 10 Aug 2026 07:31:50 +0400 Subject: [PATCH 42/51] Build/Test Tools: Allow staging fork documentation builds Fork pull_request runs do not receive the base repository's Actions variables. The old condition made the required fork lifecycle impossible to exercise in staging.\n\nAllow the hard-coded staging repository to run only the untrusted read-only build. Keep trunk, publication, lifecycle, and cleanup behind DOCS_PREVIEW_STAGING. --- .github/docs-playground-preview/README.md | 6 ++- .../test/config.test.mjs | 40 +++++++++++++++++++ .../docs-playground-preview-build.yml | 5 ++- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/.github/docs-playground-preview/README.md b/.github/docs-playground-preview/README.md index 346bcf51a93c3..b699b11dc2d53 100644 --- a/.github/docs-playground-preview/README.md +++ b/.github/docs-playground-preview/README.md @@ -46,9 +46,11 @@ A manifest or harness change automatically produces a different exact cache key. ## Repository activation -The workflows run in `WordPress/wordpress-develop`. They also run in `sirreal/wordpress-develop` only when the repository Actions variable `DOCS_PREVIEW_STAGING` is exactly `true`. They remain inert in every other fork. +The workflows run in `WordPress/wordpress-develop`. Trusted staging work in `sirreal/wordpress-develop` runs only when the repository Actions variable `DOCS_PREVIEW_STAGING` is exactly `true`. That variable controls `trunk` builds, PR and `trunk` publishers, comments, labels, stale and expired lifecycle handling, cleanup, cache deletion, and every other trusted mutation. -The staging repository needs a `docs-preview` label and the Actions variables used for the scenario being tested. Disable staging by deleting `DOCS_PREVIEW_STAGING` or setting it to anything other than the lowercase string `true`. +There is one deliberate staging exception: the untrusted `pull_request` build job is enabled by the hard-coded `sirreal/wordpress-develop` allowlist without consulting `DOCS_PREVIEW_STAGING`. GitHub does not expose the base repository's Actions variables to a fork's `pull_request` workflow run. Requiring the variable there would make the required fork preview impossible to stage. With staging disabled, explicitly labeling a PR may therefore consume isolated read-only CI and create a one-day handoff artifact, but it cannot publish, comment, change labels, delete caches, or perform another trusted mutation. Arbitrary forks remain inert. This user-approved exception to section 5 is confined to the staging repository and the untrusted build job. + +The staging repository needs a `docs-preview` label and the Actions variables used for the scenario being tested. Disable trusted staging by deleting `DOCS_PREVIEW_STAGING` or setting it to anything other than the lowercase string `true`. ## Validation enforcement diff --git a/.github/scripts/docs-playground-preview/test/config.test.mjs b/.github/scripts/docs-playground-preview/test/config.test.mjs index adcb27fc9e85f..3d00269952c8b 100644 --- a/.github/scripts/docs-playground-preview/test/config.test.mjs +++ b/.github/scripts/docs-playground-preview/test/config.test.mjs @@ -21,6 +21,18 @@ const nodeVersionFile = new URL( '../../../docs-playground-preview/.nvmrc', import.meta.url ); +const buildWorkflowFile = new URL( + '../../../workflows/docs-playground-preview-build.yml', + import.meta.url +); +const publishWorkflowFile = new URL( + '../../../workflows/docs-playground-preview-publish.yml', + import.meta.url +); +const lifecycleWorkflowFile = new URL( + '../../../workflows/docs-playground-preview-lifecycle.yml', + import.meta.url +); async function manifest() { return JSON.parse( await readFile( dependenciesFile, 'utf8' ) ); @@ -181,3 +193,31 @@ test( 'deployment is inert outside the primary and opted-in staging repositories false ); } ); + +test( 'only the untrusted staging pull-request build bypasses staging activation', async () => { + const buildWorkflow = await readFile( buildWorkflowFile, 'utf8' ); + const publishWorkflow = await readFile( publishWorkflowFile, 'utf8' ); + const lifecycleWorkflow = await readFile( lifecycleWorkflowFile, 'utf8' ); + const trunkJobOffset = buildWorkflow.indexOf( '\n build-trunk:' ); + + assert.notEqual( trunkJobOffset, -1 ); + assert.match( + buildWorkflow.slice( 0, trunkJobOffset ), + /github\.repository == 'sirreal\/wordpress-develop' / + ); + assert.doesNotMatch( + buildWorkflow.slice( 0, trunkJobOffset ), + /vars\.DOCS_PREVIEW_STAGING/ + ); + + for ( const trustedWorkflow of [ + buildWorkflow.slice( trunkJobOffset ), + publishWorkflow, + lifecycleWorkflow, + ] ) { + assert.match( + trustedWorkflow, + /github\.repository == 'sirreal\/wordpress-develop' && vars\.DOCS_PREVIEW_STAGING == 'true'/ + ); + } +} ); diff --git a/.github/workflows/docs-playground-preview-build.yml b/.github/workflows/docs-playground-preview-build.yml index 5d7a9f1f2cf59..ed311e720e7e8 100644 --- a/.github/workflows/docs-playground-preview-build.yml +++ b/.github/workflows/docs-playground-preview-build.yml @@ -15,10 +15,13 @@ permissions: {} jobs: build: name: Build Code Reference snapshot + # Fork pull_request runs cannot read the base repository's Actions variables. + # This allowlisted job is untrusted and read-only; every trusted staging job + # remains gated by DOCS_PREVIEW_STAGING. if: >- github.event_name == 'pull_request' && ( github.repository == 'WordPress/wordpress-develop' || - ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) && + github.repository == 'sirreal/wordpress-develop' ) && github.event.pull_request.base.ref == 'trunk' && github.event.pull_request.state == 'open' && ( ( github.event.action == 'labeled' && github.event.label.name == 'docs-preview' ) || From 7670d3b481d8fdd403cf6bd5d2d7449ca9d17fd1 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Mon, 10 Aug 2026 07:43:56 +0400 Subject: [PATCH 43/51] Build/Test Tools: Enforce fork preview validation Fork pull_request runs cannot read the base repository's Actions variables. The old check silently left every fork in advisory mode, so DOCS_PREVIEW_ENFORCE did not enforce anything.\n\nApply the exact base-repository value in the trusted publisher after it records the failure and preserves the live preview. --- .github/docs-playground-preview/README.md | 2 ++ .../docs-playground-preview/publish.mjs | 31 +++++++++++++------ .../test/config.test.mjs | 4 +++ .../test/publish.test.mjs | 19 +++++++++++- .../docs-playground-preview-publish.yml | 6 ++++ 5 files changed, 52 insertions(+), 10 deletions(-) diff --git a/.github/docs-playground-preview/README.md b/.github/docs-playground-preview/README.md index b699b11dc2d53..56b23b28a4af4 100644 --- a/.github/docs-playground-preview/README.md +++ b/.github/docs-playground-preview/README.md @@ -56,6 +56,8 @@ The staging repository needs a `docs-preview` label and the Actions variables us Behavioral validation is advisory unless the repository Actions variable `DOCS_PREVIEW_ENFORCE` is exactly `true`. Advisory failures emit workflow warnings and terminal PR state but cannot publish or replace a snapshot. Fatal build, handoff, identity, digest, size, and publication failures always fail. +GitHub does not expose base-repository Actions variables to an untrusted fork `pull_request` job. Such a job therefore records a behavioral failure in its handoff without enforcing it locally. The trusted publisher reads `DOCS_PREVIEW_ENFORCE` from the base repository, updates the sticky comment without publishing the invalid candidate, and then fails its workflow when enforcement is enabled. Same-repository and `trunk` builds can read the variable directly and fail their build workflow as well. + To enable enforcement: 1. Open repository **Settings**. diff --git a/.github/scripts/docs-playground-preview/publish.mjs b/.github/scripts/docs-playground-preview/publish.mjs index 2768bac75998c..304ddc1bd3408 100644 --- a/.github/scripts/docs-playground-preview/publish.mjs +++ b/.github/scripts/docs-playground-preview/publish.mjs @@ -366,17 +366,30 @@ export async function publishPullRequest( options ) { } } +export function enforceValidationResult( result, enforcementVariable ) { + if ( result.status === 'invalid' && enforcementVariable === 'true' ) { + throw new Error( + 'Code Reference validation failed with DOCS_PREVIEW_ENFORCE enabled.' + ); + } + return result; +} + async function main() { const event = JSON.parse( await readFile( process.env.GITHUB_EVENT_PATH ) ); - const result = await publishPullRequest( { - repository: process.env.GITHUB_REPOSITORY, - stagingVariable: process.env.DOCS_PREVIEW_STAGING, - triggerRunId: event.workflow_run.id, - triggerRunAttempt: event.workflow_run.run_attempt, - handoffDirectory: process.argv[ 2 ] || 'handoff', - artifactAvailable: process.env.HANDOFF_DOWNLOAD_RESULT === 'success', - token: process.env.GITHUB_TOKEN, - } ); + const result = enforceValidationResult( + await publishPullRequest( { + repository: process.env.GITHUB_REPOSITORY, + stagingVariable: process.env.DOCS_PREVIEW_STAGING, + triggerRunId: event.workflow_run.id, + triggerRunAttempt: event.workflow_run.run_attempt, + handoffDirectory: process.argv[ 2 ] || 'handoff', + artifactAvailable: + process.env.HANDOFF_DOWNLOAD_RESULT === 'success', + token: process.env.GITHUB_TOKEN, + } ), + process.env.DOCS_PREVIEW_ENFORCE + ); process.stdout.write( `Code Reference publisher: ${ result.status }.\n` ); } diff --git a/.github/scripts/docs-playground-preview/test/config.test.mjs b/.github/scripts/docs-playground-preview/test/config.test.mjs index 3d00269952c8b..27c6c06e4c1b8 100644 --- a/.github/scripts/docs-playground-preview/test/config.test.mjs +++ b/.github/scripts/docs-playground-preview/test/config.test.mjs @@ -220,4 +220,8 @@ test( 'only the untrusted staging pull-request build bypasses staging activation /github\.repository == 'sirreal\/wordpress-develop' && vars\.DOCS_PREVIEW_STAGING == 'true'/ ); } + assert.match( + publishWorkflow, + /DOCS_PREVIEW_ENFORCE: \$\{\{ vars\.DOCS_PREVIEW_ENFORCE \}\}/ + ); } ); diff --git a/.github/scripts/docs-playground-preview/test/publish.test.mjs b/.github/scripts/docs-playground-preview/test/publish.test.mjs index 853057440030c..5d7ceab6d9b3a 100644 --- a/.github/scripts/docs-playground-preview/test/publish.test.mjs +++ b/.github/scripts/docs-playground-preview/test/publish.test.mjs @@ -16,7 +16,10 @@ import { releaseAssetUrl, snapshotAssetName, } from '../lib/publication.mjs'; -import { publishPullRequest } from '../publish.mjs'; +import { + enforceValidationResult, + publishPullRequest, +} from '../publish.mjs'; const repository = 'WordPress/wordpress-develop'; const sourceRepository = 'contributor/wordpress-develop'; @@ -301,6 +304,20 @@ test( 'an advisory validation failure updates the comment but publishes nothing' assert.equal( api.labelRemovals, 1 ); } ); +test( 'the trusted publisher enforces invalid fork handoffs', () => { + const invalid = { status: 'invalid' }; + assert.throws( + () => enforceValidationResult( invalid, 'true' ), + /DOCS_PREVIEW_ENFORCE/ + ); + assert.equal( enforceValidationResult( invalid, 'false' ), invalid ); + assert.equal( enforceValidationResult( invalid, 'TRUE' ), invalid ); + assert.equal( + enforceValidationResult( { status: 'ready' }, 'true' ).status, + 'ready' + ); +} ); + test( 'a missing handoff always fails after its terminal comment', async () => { const api = new FakeApi(); const directory = await mkdtemp( diff --git a/.github/workflows/docs-playground-preview-publish.yml b/.github/workflows/docs-playground-preview-publish.yml index 97e48c1c41953..06086b100affd 100644 --- a/.github/workflows/docs-playground-preview-publish.yml +++ b/.github/workflows/docs-playground-preview-publish.yml @@ -54,7 +54,13 @@ jobs: digest-mismatch: error - name: Validate and publish the handoff + # Fork pull_request jobs cannot read the base repository's Actions + # variables. The trusted publisher therefore applies enforcement + # after reporting the invalid candidate and preserving the live link. + # Administrator: enable enforcement in Settings > Secrets and variables > + # Actions > Variables by creating or updating DOCS_PREVIEW_ENFORCE to true. env: + DOCS_PREVIEW_ENFORCE: ${{ vars.DOCS_PREVIEW_ENFORCE }} DOCS_PREVIEW_STAGING: ${{ vars.DOCS_PREVIEW_STAGING }} GITHUB_TOKEN: ${{ github.token }} HANDOFF_DOWNLOAD_RESULT: ${{ steps.download.outcome }} From 8c4df68dfd07abc852720e525009dec5baddcddb Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Mon, 10 Aug 2026 08:14:46 +0400 Subject: [PATCH 44/51] Build/Test Tools: Report nested publisher failures The publisher collapsed multiple failures into an AggregateError and discarded every cause. That leaves a broken comment or label path with no actionable log.\n\nPrint the nested causes after the transaction has already failed. Publication behavior and exit status stay unchanged. --- .../docs-playground-preview/publish.mjs | 16 +++++++++++++- .../test/publish.test.mjs | 22 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/.github/scripts/docs-playground-preview/publish.mjs b/.github/scripts/docs-playground-preview/publish.mjs index 304ddc1bd3408..3a7cfd92496fe 100644 --- a/.github/scripts/docs-playground-preview/publish.mjs +++ b/.github/scripts/docs-playground-preview/publish.mjs @@ -19,6 +19,20 @@ import { findPreviousPreview, loadPublishedPreview } from './lib/published.mjs'; class SupersededRun extends Error {} +export function formatFailure( error ) { + const description = error.stack || String( error ); + if ( ! ( error instanceof AggregateError ) ) { + return description; + } + return [ + description, + ...error.errors.map( + ( cause, index ) => + `Cause ${ index + 1 }:\n${ formatFailure( cause ) }` + ), + ].join( '\n' ); +} + async function readHandoff( directory ) { return JSON.parse( await readFile( path.join( directory, 'build.json' ), 'utf8' ) @@ -395,7 +409,7 @@ async function main() { if ( import.meta.url === pathToFileURL( process.argv[ 1 ] ).href ) { main().catch( ( error ) => { - process.stderr.write( `${ error.stack || error }\n` ); + process.stderr.write( `${ formatFailure( error ) }\n` ); process.exitCode = 1; } ); } diff --git a/.github/scripts/docs-playground-preview/test/publish.test.mjs b/.github/scripts/docs-playground-preview/test/publish.test.mjs index 5d7ceab6d9b3a..123ed316d3fa2 100644 --- a/.github/scripts/docs-playground-preview/test/publish.test.mjs +++ b/.github/scripts/docs-playground-preview/test/publish.test.mjs @@ -18,6 +18,7 @@ import { } from '../lib/publication.mjs'; import { enforceValidationResult, + formatFailure, publishPullRequest, } from '../publish.mjs'; @@ -318,6 +319,27 @@ test( 'the trusted publisher enforces invalid fork handoffs', () => { ); } ); +test( 'publisher failures retain every nested cause in the Actions log', () => { + const failure = new AggregateError( + [ + new Error( 'candidate upload failed' ), + new AggregateError( + [ + new Error( 'comment failed' ), + new Error( 'label removal failed' ), + ], + 'failure reporting failed' + ), + ], + 'publication failed' + ); + const formatted = formatFailure( failure ); + + assert.match( formatted, /candidate upload failed/ ); + assert.match( formatted, /comment failed/ ); + assert.match( formatted, /label removal failed/ ); +} ); + test( 'a missing handoff always fails after its terminal comment', async () => { const api = new FakeApi(); const directory = await mkdtemp( From 6f72a9b7139b64ac7b5e5bcfe022c977bca01da9 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Mon, 10 Aug 2026 08:18:54 +0400 Subject: [PATCH 45/51] Build/Test Tools: Grant PR publisher write access The trusted publisher could upload snapshots but GitHub rejected every PR comment and label mutation. Declaring pull requests read-only made the required lifecycle impossible.\n\nGrant write access to pull requests and drop the redundant issue scope. The trunk publisher remains unchanged. --- .../docs-playground-preview/test/config.test.mjs | 13 +++++++++++++ .../workflows/docs-playground-preview-publish.yml | 3 +-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/scripts/docs-playground-preview/test/config.test.mjs b/.github/scripts/docs-playground-preview/test/config.test.mjs index 27c6c06e4c1b8..25ad9a0c1690b 100644 --- a/.github/scripts/docs-playground-preview/test/config.test.mjs +++ b/.github/scripts/docs-playground-preview/test/config.test.mjs @@ -224,4 +224,17 @@ test( 'only the untrusted staging pull-request build bypasses staging activation publishWorkflow, /DOCS_PREVIEW_ENFORCE: \$\{\{ vars\.DOCS_PREVIEW_ENFORCE \}\}/ ); + const trunkPublisherOffset = publishWorkflow.indexOf( + '\n publish-trunk:' + ); + assert.notEqual( trunkPublisherOffset, -1 ); + const pullRequestPublisher = publishWorkflow.slice( + 0, + trunkPublisherOffset + ); + assert.match( + pullRequestPublisher, + /publish:\n[\s\S]*?permissions:\n[\s\S]*?pull-requests: write/ + ); + assert.doesNotMatch( pullRequestPublisher, /issues: write/ ); } ); diff --git a/.github/workflows/docs-playground-preview-publish.yml b/.github/workflows/docs-playground-preview-publish.yml index 06086b100affd..49c4029a2c9da 100644 --- a/.github/workflows/docs-playground-preview-publish.yml +++ b/.github/workflows/docs-playground-preview-publish.yml @@ -27,8 +27,7 @@ jobs: permissions: actions: read contents: write - issues: write - pull-requests: read + pull-requests: write steps: - name: Check out trusted default-branch publisher code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 From 95bcd71a4fb5364f14ac2decdd4e84812a07a438 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Mon, 10 Aug 2026 07:38:30 +0400 Subject: [PATCH 46/51] Build/Test Tools: Break the staging publication path The staging gate needs proof that publisher failure leaves the live preview untouched. Point the trusted publisher at a reserved invalid proxy identity. This commit is temporary and must be reverted after the failure is recorded. --- .github/scripts/docs-playground-preview/lib/publication.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/docs-playground-preview/lib/publication.mjs b/.github/scripts/docs-playground-preview/lib/publication.mjs index 24e89063c7501..df28004aa89ef 100644 --- a/.github/scripts/docs-playground-preview/lib/publication.mjs +++ b/.github/scripts/docs-playground-preview/lib/publication.mjs @@ -3,7 +3,7 @@ import { createHash } from 'node:crypto'; export const RELEASE_TAG = 'code-reference-playground-preview'; export const COMMENT_MARKER = ''; export const PLAYGROUND_ORIGIN = 'https://playground.wordpress.net'; -export const CORS_PROXY = 'https://wordpress-playground-cors-proxy.net/'; +export const CORS_PROXY = 'https://example.invalid/'; export const SNAPSHOT_BYTES_LIMIT = 104857600; const FULL_COMMIT = /^[0-9a-f]{40}$/; From 65ef19f3dec3c40a994ff4bf175ab7184fa77364 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Mon, 10 Aug 2026 07:39:07 +0400 Subject: [PATCH 47/51] Build/Test Tools: Restore the staging publication path The forced publisher failure has one purpose and must not survive the staging test. Restore the real Playground CORS proxy exactly; leaving the fault in place would break every later publication. --- .github/scripts/docs-playground-preview/lib/publication.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/docs-playground-preview/lib/publication.mjs b/.github/scripts/docs-playground-preview/lib/publication.mjs index df28004aa89ef..24e89063c7501 100644 --- a/.github/scripts/docs-playground-preview/lib/publication.mjs +++ b/.github/scripts/docs-playground-preview/lib/publication.mjs @@ -3,7 +3,7 @@ import { createHash } from 'node:crypto'; export const RELEASE_TAG = 'code-reference-playground-preview'; export const COMMENT_MARKER = ''; export const PLAYGROUND_ORIGIN = 'https://playground.wordpress.net'; -export const CORS_PROXY = 'https://example.invalid/'; +export const CORS_PROXY = 'https://wordpress-playground-cors-proxy.net/'; export const SNAPSHOT_BYTES_LIMIT = 104857600; const FULL_COMMIT = /^[0-9a-f]{40}$/; From d5074d8608a9dc0f24ece0689d4e49f0c4b7e961 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Mon, 10 Aug 2026 09:42:31 +0400 Subject: [PATCH 48/51] Build/Test Tools: Preserve stale docs preview links Stale lifecycle work discarded the last working link when public revalidation was temporarily unavailable. Its token also used a different PR write scope than the publisher.\n\nReuse the marked comment's last-successful identity as the fallback and grant the same pull-request write permission. A proxy outage now leaves the working preview reachable while the comment moves to Stale. --- .../docs-playground-preview/lifecycle.mjs | 6 +++++- .../test/config.test.mjs | 5 +++++ .../test/lifecycle.test.mjs | 21 +++++++++++++++++++ .../docs-playground-preview-lifecycle.yml | 6 ++---- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/.github/scripts/docs-playground-preview/lifecycle.mjs b/.github/scripts/docs-playground-preview/lifecycle.mjs index c0110077933cf..1daf868ac6e6a 100644 --- a/.github/scripts/docs-playground-preview/lifecycle.mjs +++ b/.github/scripts/docs-playground-preview/lifecycle.mjs @@ -7,6 +7,7 @@ import { isDeploymentEnabled } from './lib/config.mjs'; import { GitHubApi } from './lib/github.mjs'; import { findPreviousPreview } from './lib/published.mjs'; import { + readPreviewCommentSuccess, readPreviewCommentSource, renderPreviewComment, } from './lib/publisher.mjs'; @@ -159,7 +160,10 @@ async function markStale( session ) { ) { return { status: 'ignored' }; } - const preview = await findPreviousPreview( session ); + let preview = await findPreviousPreview( session ); + if ( ! preview ) { + preview = readPreviewCommentSuccess( comment.body ); + } if ( ! preview ) { const previous = commentSource; if ( ! previous ) { diff --git a/.github/scripts/docs-playground-preview/test/config.test.mjs b/.github/scripts/docs-playground-preview/test/config.test.mjs index 25ad9a0c1690b..3b43da2b2e364 100644 --- a/.github/scripts/docs-playground-preview/test/config.test.mjs +++ b/.github/scripts/docs-playground-preview/test/config.test.mjs @@ -237,4 +237,9 @@ test( 'only the untrusted staging pull-request build bypasses staging activation /publish:\n[\s\S]*?permissions:\n[\s\S]*?pull-requests: write/ ); assert.doesNotMatch( pullRequestPublisher, /issues: write/ ); + assert.equal( + lifecycleWorkflow.match( /pull-requests: write/g )?.length, + 2 + ); + assert.doesNotMatch( lifecycleWorkflow, /issues: write/ ); } ); diff --git a/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs b/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs index 15a9c3f917cf9..ac5c63dc9f2ed 100644 --- a/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs +++ b/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs @@ -211,6 +211,27 @@ test( 'a later unlabeled commit marks the healthy preview stale', async () => { assert.match( api.comment.body, /add the `docs-preview` label again/i ); } ); +test( 'a proxy outage cannot remove the last successful stale link', async () => { + const api = new FakeApi(); + const preview = installPreview( api ); + api.comment.body = renderPreviewComment( { + status: 'ready', + preview, + runUrl: preview.runUrl, + } ); + const lifecycleOptions = options( api, event( 'synchronize' ) ); + lifecycleOptions.fetchImplementation = async () => ( { status: 503 } ); + + const result = await managePullRequest( lifecycleOptions ); + + assert.equal( result.status, 'stale' ); + assert.match( api.comment.body, /Status:\*\* Stale/ ); + assert.match( api.comment.body, /Latest successful docs preview/ ); + assert.ok( api.comment.body.includes( preview.publication.playgroundUrl ) ); + assert.match( api.comment.body, new RegExp( previewSha ) ); + assert.match( api.comment.body, new RegExp( currentSha ) ); +} ); + test( 'a labeled synchronize event leaves the publisher in charge', async () => { const api = new FakeApi(); api.currentPullRequest = pullRequest( 'open', [ diff --git a/.github/workflows/docs-playground-preview-lifecycle.yml b/.github/workflows/docs-playground-preview-lifecycle.yml index 1bb0a238319c4..fb7f7f0cbdc3f 100644 --- a/.github/workflows/docs-playground-preview-lifecycle.yml +++ b/.github/workflows/docs-playground-preview-lifecycle.yml @@ -25,8 +25,7 @@ jobs: permissions: actions: read contents: read - issues: write - pull-requests: read + pull-requests: write steps: - name: Check out trusted default-branch lifecycle code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -76,8 +75,7 @@ jobs: permissions: actions: write contents: write - issues: write - pull-requests: read + pull-requests: write steps: - name: Check out trusted default-branch lifecycle code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 From 4277884c408f4b80e2123883a93c72d5a896c039 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Mon, 7 Sep 2026 14:52:06 +0400 Subject: [PATCH 49/51] Staging: update the Code Reference preview pipeline to the simplified implementation --- .github/docs-playground-preview/README.md | 131 +-- .../docs-playground-preview/dependencies.json | 19 +- .../docs-playground-preview/package-lock.json | 26 +- .github/docs-playground-preview/package.json | 8 +- .github/docs-playground-preview/tsconfig.json | 15 + .../scripts/docs-playground-preview/build.mjs | 487 +++------- .../docs-playground-preview/lib/archive.mjs | 62 -- .../docs-playground-preview/lib/base.mjs | 513 +++++----- .../docs-playground-preview/lib/comment.mjs | 119 +++ .../docs-playground-preview/lib/config.mjs | 202 ---- .../docs-playground-preview/lib/files.mjs | 144 --- .../docs-playground-preview/lib/final.mjs | 153 --- .../docs-playground-preview/lib/github.mjs | 476 ++++++---- .../docs-playground-preview/lib/inputs.mjs | 183 +++- .../docs-playground-preview/lib/parser.mjs | 170 ++-- .../lib/playground.mjs | 22 - .../docs-playground-preview/lib/preview.mjs | 256 +++++ .../docs-playground-preview/lib/process.mjs | 54 +- .../lib/publication.mjs | 274 ------ .../docs-playground-preview/lib/published.mjs | 132 --- .../docs-playground-preview/lib/publisher.mjs | 370 -------- .../docs-playground-preview/lib/runtime.mjs | 127 --- .../docs-playground-preview/lib/snapshot.mjs | 206 ++++ .../docs-playground-preview/lib/trunk.mjs | 311 ------ .../docs-playground-preview/lib/validate.mjs | 396 ++++---- .../docs-playground-preview/lifecycle.mjs | 330 ++----- .../php/base-policy.php | 26 +- .../php/complete-import.php | 45 +- .../php/configure-base.php | 63 +- .../php/preview-runtime.php | 93 ++ .../docs-playground-preview/publish-trunk.mjs | 351 ------- .../docs-playground-preview/publish.mjs | 723 ++++++++------ .../scripts/docs-playground-preview/reuse.mjs | 211 ++--- .../test/base.test.mjs | 175 ---- .../test/build.test.mjs | 292 ------ .../test/comment.test.mjs | 97 ++ .../test/config.test.mjs | 245 ----- .../test/fake-github.mjs | 226 +++++ .../test/files.test.mjs | 183 ---- .../test/final.test.mjs | 157 ---- .../test/github.test.mjs | 469 ++++----- .../test/inputs.test.mjs | 171 ++++ .../test/lifecycle.test.mjs | 465 +++------ .../test/parser.test.mjs | 164 ++-- .../test/playground.test.mjs | 29 - .../test/preview.test.mjs | 141 +++ .../test/publication.test.mjs | 217 ----- .../test/publish-trunk.test.mjs | 545 ----------- .../test/publish.test.mjs | 886 ++++++++---------- .../test/published.test.mjs | 220 ----- .../test/publisher.test.mjs | 320 ------- .../test/reuse.test.mjs | 133 +++ .../test/runtime.test.mjs | 51 - .../test/snapshot.test.mjs | 102 ++ .../test/trunk.test.mjs | 279 ------ .../test/validate.test.mjs | 344 +++---- .../docs-playground-preview-build.yml | 73 +- .../docs-playground-preview-lifecycle.yml | 165 ++-- .../docs-playground-preview-publish.yml | 189 ++-- .../docs-playground-preview-tests.yml | 87 ++ 60 files changed, 4617 insertions(+), 8506 deletions(-) create mode 100644 .github/docs-playground-preview/tsconfig.json delete mode 100644 .github/scripts/docs-playground-preview/lib/archive.mjs create mode 100644 .github/scripts/docs-playground-preview/lib/comment.mjs delete mode 100644 .github/scripts/docs-playground-preview/lib/config.mjs delete mode 100644 .github/scripts/docs-playground-preview/lib/files.mjs delete mode 100644 .github/scripts/docs-playground-preview/lib/final.mjs delete mode 100644 .github/scripts/docs-playground-preview/lib/playground.mjs create mode 100644 .github/scripts/docs-playground-preview/lib/preview.mjs delete mode 100644 .github/scripts/docs-playground-preview/lib/publication.mjs delete mode 100644 .github/scripts/docs-playground-preview/lib/published.mjs delete mode 100644 .github/scripts/docs-playground-preview/lib/publisher.mjs delete mode 100644 .github/scripts/docs-playground-preview/lib/runtime.mjs create mode 100644 .github/scripts/docs-playground-preview/lib/snapshot.mjs delete mode 100644 .github/scripts/docs-playground-preview/lib/trunk.mjs create mode 100644 .github/scripts/docs-playground-preview/php/preview-runtime.php delete mode 100644 .github/scripts/docs-playground-preview/publish-trunk.mjs delete mode 100644 .github/scripts/docs-playground-preview/test/base.test.mjs delete mode 100644 .github/scripts/docs-playground-preview/test/build.test.mjs create mode 100644 .github/scripts/docs-playground-preview/test/comment.test.mjs delete mode 100644 .github/scripts/docs-playground-preview/test/config.test.mjs create mode 100644 .github/scripts/docs-playground-preview/test/fake-github.mjs delete mode 100644 .github/scripts/docs-playground-preview/test/files.test.mjs delete mode 100644 .github/scripts/docs-playground-preview/test/final.test.mjs create mode 100644 .github/scripts/docs-playground-preview/test/inputs.test.mjs delete mode 100644 .github/scripts/docs-playground-preview/test/playground.test.mjs create mode 100644 .github/scripts/docs-playground-preview/test/preview.test.mjs delete mode 100644 .github/scripts/docs-playground-preview/test/publication.test.mjs delete mode 100644 .github/scripts/docs-playground-preview/test/publish-trunk.test.mjs delete mode 100644 .github/scripts/docs-playground-preview/test/published.test.mjs delete mode 100644 .github/scripts/docs-playground-preview/test/publisher.test.mjs create mode 100644 .github/scripts/docs-playground-preview/test/reuse.test.mjs delete mode 100644 .github/scripts/docs-playground-preview/test/runtime.test.mjs create mode 100644 .github/scripts/docs-playground-preview/test/snapshot.test.mjs delete mode 100644 .github/scripts/docs-playground-preview/test/trunk.test.mjs create mode 100644 .github/workflows/docs-playground-preview-tests.yml diff --git a/.github/docs-playground-preview/README.md b/.github/docs-playground-preview/README.md index 56b23b28a4af4..82a784c762286 100644 --- a/.github/docs-playground-preview/README.md +++ b/.github/docs-playground-preview/README.md @@ -1,119 +1,80 @@ # Code Reference Playground preview -This directory pins the inputs for the WordPress Core Code Reference preview. The implementation lives in `.github/scripts/docs-playground-preview`; GitHub Actions only supplies exact source identity, caching, and publication authority. +This directory holds the pinned inputs for the WordPress Core Code Reference preview: the dependency manifest, the Node version, and the npm tools the build installs. The implementation is in `.github/scripts/docs-playground-preview`. -The preview is informational. Pull request source is parsed as data in a job with no secrets and `contents: read` at most. Build jobs use GitHub's cache service: a trusted `trunk` build can warm the shared cache, while GitHub scopes a pull-request-created entry to its pull request ref so it cannot warm or replace the default-branch cache. Only default-branch publisher and cleanup code receives repository write permissions for release assets, comments, labels, cache deletion, or the stable `trunk` pointer. +A preview is the Core Code Reference built from PHPDoc at one commit, imported into a wporg-developer site and packaged as a WordPress Playground snapshot. Adding the `docs-preview` label to a pull request against `trunk` builds one and posts a link in a single comment on the pull request. Every push to `trunk` refreshes the preview that the repository `README.md` links. -## Local build - -Use the exact Node, npm, PHP, and Composer versions in `dependencies.json`, then run: +Pull request source is parsed, never executed. The build job runs with `contents: read` and nothing else; only default-branch code, in a separate workflow, has the write access to publish. A pull request build may warm the build cache because GitHub scopes an entry written from a pull request ref to that ref, where it cannot replace the entry the default branch reads. -```sh -npm --prefix .github/docs-playground-preview run build -``` +## What runs where -The command infers the current Git repository and commit, resolves the moving Playground `beta` channel, builds or restores the invariant site base, parses eligible PHP under `src`, imports the complete Code Reference, packages the snapshot, and runs the same behavioral validation as CI. Local output and cache data are written below `.cache/docs-playground-preview`. +| Workflow | Trigger | What it does | +| --- | --- | --- | +| Build | The `docs-preview` label is added, a labeled pull request is pushed to, or `trunk` is pushed to | Builds and validates the snapshot and uploads it as an artifact kept for one day. A first attempt on a pull request reuses a published snapshot for the same commit instead of building one. | +| Publish | The build workflow finishes | Uploads the snapshot to the release. For a pull request it removes the label and updates the comment; for `trunk` it moves the stable Blueprint. | +| Lifecycle | A pull request against `trunk` is pushed to or closed | Marks the comment stale when the pull request is no longer labeled. On close, deletes the pull request's assets and caches and marks the comment expired. | +| Tests | A change under `.github/scripts/docs-playground-preview` or this directory | Runs the script tests and type checking. | -CI passes explicit `--source-repository`, `--source-sha`, run identity, cache, output, and metadata paths to this same command. `--resolve-only` writes the concrete beta and exact cache identity before the cache restore step. Local and CI snapshots are equivalent when their symbol counts, representative routes, search, runtime constants, and size boundary agree; their bytes need not agree. +The comment is identified by the marker `` and is separate from the general Core Playground comment. It is created once and updated in place. -Run the script tests with: +## Local build ```sh -npm test --prefix .github/docs-playground-preview +npm --prefix .github/docs-playground-preview run build ``` -## Updating pins - -All repositories in `dependencies.json` must remain on the allowed repositories enforced by `.github/scripts/docs-playground-preview/lib/config.mjs`, and every repository revision must be a full immutable commit hash. Tool versions are exact. GitHub Actions are pinned to full commit hashes in the workflow files. - -To update a pinned repository: - -1. Change its full commit in `dependencies.json`. The dependency's own lock files come from that immutable checkout; this repository does not copy or re-verify them. -2. Run the script tests and a complete local build. -3. Exercise cold and warm builds in the staging repository. - -Keep duplicated tool identities synchronized: +Needs `git`, `node` at the version in `.nvmrc`, `php` 8.4, `composer` and `zip` on `PATH`. The command installs the pinned npm tools itself. It then takes the repository and commit from the checkout, resolves the current WordPress beta, builds or restores the site base, parses the PHP under `src`, imports the Code Reference, packages the snapshot and runs the same validation as CI. Everything is written below `.cache/docs-playground-preview`: the snapshot and its `build.json` in `output`, the cached base in `cache`. -- For `@wp-playground/cli`, `yarn`, `@wordpress/scripts`, or `@wordpress/i18n`, update both `dependencies.json` and the exact dependency in `package.json`, then regenerate `package-lock.json` with the manifest's exact Node and npm versions. A Playground CLI update also requires changing its fixed version check in `.github/scripts/docs-playground-preview/lib/config.mjs`. -- For Node, update `dependencies.json`, `.nvmrc`, `package.json`'s `engines.node`, and every `setup-node` input in the build, publish, and lifecycle workflows. Select a Node distribution containing the npm version recorded in `dependencies.json`; the build rejects a different npm version. -- For PHP, update `dependencies.json`, the fixed runtime checks in `.github/scripts/docs-playground-preview/lib/config.mjs` and `.github/scripts/docs-playground-preview/lib/publication.mjs`, and both `setup-php` inputs in the build workflow. PHP remains `8.4` unless the specification changes. -- For Composer, update `dependencies.json` and both `setup-php` Composer tool specifications in the build workflow. -- For a GitHub Action, update its full commit SHA and the adjacent release comment in every workflow use. +The first build clones and builds the six pinned repositories and takes a long time. Later builds restore that base from the cache directory while the cache key is unchanged. -After any tool change, run the script tests, regenerate the lockfile when applicable, complete a local build, and exercise cold and warm staging builds. +CI runs the same command twice. `--resolve-only` writes the cache key and directory for the cache restore step, plus the resolved inputs the second run reads back, so both runs agree on the WordPress version. The build run adds the source identity, `--source-repository`, `--source-sha` and `--run-url`, which appear in the snapshot's provenance banner. Both runs write into `--handoff`, the directory the publish job downloads; a failure in either run is reported through it. -A manifest or harness change automatically produces a different exact cache key. Increment `cacheSchemaVersion` when the stored base layout or meaning changes incompatibly, even if no dependency pin changed. Broad restore keys are prohibited. +## Tests and type checking -## Repository activation - -The workflows run in `WordPress/wordpress-develop`. Trusted staging work in `sirreal/wordpress-develop` runs only when the repository Actions variable `DOCS_PREVIEW_STAGING` is exactly `true`. That variable controls `trunk` builds, PR and `trunk` publishers, comments, labels, stale and expired lifecycle handling, cleanup, cache deletion, and every other trusted mutation. - -There is one deliberate staging exception: the untrusted `pull_request` build job is enabled by the hard-coded `sirreal/wordpress-develop` allowlist without consulting `DOCS_PREVIEW_STAGING`. GitHub does not expose the base repository's Actions variables to a fork's `pull_request` workflow run. Requiring the variable there would make the required fork preview impossible to stage. With staging disabled, explicitly labeling a PR may therefore consume isolated read-only CI and create a one-day handoff artifact, but it cannot publish, comment, change labels, delete caches, or perform another trusted mutation. Arbitrary forks remain inert. This user-approved exception to section 5 is confined to the staging repository and the untrusted build job. - -The staging repository needs a `docs-preview` label and the Actions variables used for the scenario being tested. Disable trusted staging by deleting `DOCS_PREVIEW_STAGING` or setting it to anything other than the lowercase string `true`. - -## Validation enforcement - -Behavioral validation is advisory unless the repository Actions variable `DOCS_PREVIEW_ENFORCE` is exactly `true`. Advisory failures emit workflow warnings and terminal PR state but cannot publish or replace a snapshot. Fatal build, handoff, identity, digest, size, and publication failures always fail. - -GitHub does not expose base-repository Actions variables to an untrusted fork `pull_request` job. Such a job therefore records a behavioral failure in its handoff without enforcing it locally. The trusted publisher reads `DOCS_PREVIEW_ENFORCE` from the base repository, updates the sticky comment without publishing the invalid candidate, and then fails its workflow when enforcement is enabled. Same-repository and `trunk` builds can read the variable directly and fail their build workflow as well. - -To enable enforcement: - -1. Open repository **Settings**. -2. Open **Secrets and variables → Actions → Variables**. -3. Create or update `DOCS_PREVIEW_ENFORCE` with the value `true`. +```sh +npm --prefix .github/docs-playground-preview test +npm --prefix .github/docs-playground-preview run typecheck +``` -## Pull request lifecycle +The `Code Reference Playground Preview Tests` workflow runs both. Type checking runs even when the tests fail, so one run reports both. -The label name is exactly `docs-preview`, and only pull requests targeting `trunk` are eligible. +## Updating pins -- Adding the label requests a complete build of the exact pull request head repository and SHA. -- A healthy public snapshot for the same SHA is reused. **Re-run all jobs** on the original build bypasses reuse. -- While the label remains, a synchronized head cancels the obsolete build and requests the newest SHA. -- The latest terminal publisher removes the label after success, validation failure, or build failure. -- A later unlabeled commit retains the latest successful link, marks it stale, and asks a maintainer to add the label again. -- Closing or merging deletes only that pull request's assets and `refs/pull//merge` docs caches, then marks the historical comment expired. +`dependencies.json` pins each upstream repository to a full commit, and the build refuses anything else, because a branch or a tag would let the base change under a cache key that stays the same. To move a pin, change its commit, run a local build, then exercise a cold and a warm build in the staging repository. -The sticky comment is separate from the general Core Playground comment and is identified by ``. +The other pins live where the tool that reads them looks: -## Publication and cleanup +- **Node:** `.nvmrc`. All seven `setup-node` steps read it. Update `engines.node` and `@types/node` in `package.json` to match. +- **npm packages** (`@wp-playground/cli`, `@wordpress/scripts`, `yarn`): `package.json`, then regenerate `package-lock.json`. +- **PHP:** two versions that must agree. `playground.phpVersion` in `dependencies.json` is the PHP inside the snapshot; `php-version` in both `setup-php` steps of the build workflow is the PHP that runs phpdoc-parser. +- **Composer:** the `tools: composer:` version in both `setup-php` steps of the build workflow. +- **A GitHub Action:** its full commit SHA and the version comment beside it, in every workflow that uses it. -Snapshots and metadata are assets on the `code-reference-playground-preview` prerelease. +The cache key covers every file under this directory and under `.github/scripts/docs-playground-preview`, apart from `node_modules`, the type-check output and the tests, plus the resolved WordPress version and the runner image. Editing any of them builds a new base, whether or not that file shapes what the base contains: rebuilding for nothing costs one build, while a file left out of the key would be served from a stale cache forever. Increment `cacheSchemaVersion` to force a new base when nothing in the repository changed. -- Pull request snapshots use `code-reference-pr----.zip` with a same-stem JSON metadata asset. Only the latest healthy pair for an open pull request is retained. -- Trunk snapshots use `code-reference-trunk---.zip` with a same-stem JSON metadata asset. -- The stable trunk Blueprint is `code-reference-trunk.json` on the dedicated `docs-preview-code-reference` Git branch. The repository README points Playground at that stable raw URL. +`validation.minimumSymbols`, `validation.routes` and `validation.search` in `dependencies.json` are what the finished snapshot is checked against. A route renamed upstream is changed there. -Trunk publication uploads and publicly validates the immutable snapshot and metadata, creates and publicly validates an immutable Blueprint commit, then atomically moves the dedicated branch ref and re-reads its exact SHA. Only after that identity is proven are older trunk assets deleted. A failed or ambiguous ref mutation retains every snapshot the ref could identify. +## Repository variables -PR cleanup matches only its exact PR prefix. Trunk cleanup matches only the trunk prefix. Neither path deletes assets owned by the other or changes another pull request. +The workflows run in `WordPress/wordpress-develop`. In `sirreal/wordpress-develop` they run only while the Actions variable `DOCS_PREVIEW_STAGING` is exactly `true`. Every job that writes anything tests that variable, and `assertDeploymentEnabled()` tests it again inside the publish and lifecycle scripts. To disable staging, delete the variable or give it any other value. -## Recovery +The pull request build job is the exception: it is allowlisted by repository name and does not consult the variable, because GitHub does not expose the base repository's Actions variables to a fork's `pull_request` run. That job cannot publish, comment, label or delete anything. With staging off, labeling a pull request there costs a read-only build and a one-day artifact. Other forks run nothing. -Publication failure never requires deleting the current working asset first. +`DOCS_PREVIEW_ENFORCE` decides what a validation failure costs. Unset, the failures are reported as warnings, the comment says the attempt failed, and the preview published earlier stays linked. Set to exactly `true`, the build fails, and so does the publisher after it has reported the failure. Set either variable under **Settings > Secrets and variables > Actions > Variables**. -- For a PR, fix the cause and add `docs-preview` again, or use **Re-run all jobs** on the original build for a forced same-SHA rebuild. -- For `trunk`, re-run the newest trunk build or push the next commit. The next successful publisher moves the stable ref and removes orphaned trunk assets. -- If the publisher cannot determine whether the stable ref moved, it retains both old and candidate assets. Inspect the ref and the two metadata assets before removing anything manually. -- Do not delete the `docs-preview-code-reference` branch or the snapshot it names during recovery. A successful newest trunk run repairs the pointer safely. +## Published assets -## Bespoke validators +Snapshots are assets on the `code-reference-playground-preview` prerelease, each beside a JSON metadata asset with the same stem. The publisher names them after its own workflow run, so no build can name another pull request's asset. -Pinned standard tools own Git, ZIP, SQLite, JSON, PHP, and process semantics. This implementation does not inspect archive grammar, database bytes, canonical serialization, descriptors, or process trees. The retained project-specific checks are limited to boundaries or product behavior that standard tools cannot understand on their own: +- `code-reference-pr----.zip`. Publishing deletes every other asset of that pull request. Closing the pull request deletes them all. +- `code-reference-trunk---.zip`. The Blueprint `code-reference-trunk.json`, committed to the `docs-preview-code-reference` branch, names the newest one, and the repository `README.md` links that file through the Playground CORS proxy. The two newest generations are kept, because a cached copy of the Blueprint can name the previous snapshot for minutes after the pointer moves. -- **Dependency manifest and cache identity:** checks the repository allowlist, immutable commit shape, exact tool versions, concrete beta identity, and every base input. This protects the shared default-branch cache boundary. -- **Publisher handoff identity:** binds schema, deployment repository, event, PR when applicable, exact source SHA, run ID and attempt, runtime identity, filename, size, and digest before any publication. This protects the untrusted-to-trusted publication boundary. -- **Candidate file size and digest:** uses ordinary file stat and SHA-256 tools without opening the snapshot. This enforces the publisher identity and 100 MiB boundaries. -- **Parser product inspection:** uses the standard JSON parser, then enforces eligible Core source paths, supported hook types, nonempty records, per-type catastrophic-under-count floors, and the trusted importer root. These are required section 14 product and “parse, never execute” checks; they do not validate the parser's internal format beyond the fields the importer consumes. -- **Latest-wins authorization:** re-reads current PR or `trunk` state and the newest eligible workflow attempt immediately before publication mutations. This prevents an obsolete run from moving or deleting the live preview. -- **Public snapshot delivery:** fetches the immutable snapshot through the Playground CORS proxy and checks response status, CORS headers, byte size, and SHA-256. Publication is not complete until the public product has the validated identity. -- **Public metadata identity:** downloads the just-published JSON metadata, applies the same source/runtime/snapshot identity checks, and compares its parsed value with the candidate. This prevents a wrong public metadata asset from becoming the retained pointer identity. -- **Public Blueprint and stable ref identity:** parses the small Blueprint with the standard JSON parser, checks its semantic identity through the public proxy at an immutable commit, atomically moves the dedicated Git ref, and verifies that ref's SHA. This protects transactional availability without reimplementing Git semantics. -- **Behavioral snapshot validation:** boots the final snapshot through Playground and checks the required routes, search, provenance, and runtime policy. This is the section 14 product validator; it deliberately ignores intermediate archive and database representations. +A snapshot over 100 MiB (104857600 bytes) fails the build, and the publisher checks the size again before uploading. -No archive grammar, database format, canonical byte serialization, descriptor transaction, or process-tree validator is part of this pipeline. +## When something fails -## Staging evidence +The comment's status line is one of Ready, Latest attempt failed, Stale or Expired. A failed attempt keeps the link to the last preview that worked. -Before upstream rollout, record links to the `sirreal/wordpress-develop` workflow runs and comments that demonstrate every scenario in the specification's staging acceptance gate. Evidence may be accumulated across runs. Keep the run URLs and observed SHA, cache state, comment state, public URL, route/search results, runtime policy, failure-preservation result, cleanup result, and measured public snapshot size in the eventual pull request description. +- Read the run linked from the comment. Validation failures are also `::warning::` annotations on the build job. +- Publishing removes the label, so a rebuild means adding `docs-preview` again. A push while the preview is publishing keeps the label, because that push already started a build of its own. **Re-run all jobs** on the original build rebuilds the same commit and skips the same-commit reuse. +- For `trunk`, re-run the newest trunk build or push another commit. The next successful publication moves the pointer and prunes older assets. Do not delete the `docs-preview-code-reference` branch or the snapshot it names. diff --git a/.github/docs-playground-preview/dependencies.json b/.github/docs-playground-preview/dependencies.json index 6351a45e3e529..1751ce42ce4f4 100644 --- a/.github/docs-playground-preview/dependencies.json +++ b/.github/docs-playground-preview/dependencies.json @@ -1,20 +1,7 @@ { - "schemaVersion": 1, "cacheSchemaVersion": 1, - "toolchain": { - "nodeVersion": "20.20.2", - "npmVersion": "10.8.2", - "composerVersion": "2.8.12", - "yarnVersion": "1.22.22", - "wpScriptsVersion": "32.6.0", - "wpI18nVersion": "6.24.0" - }, "playground": { - "cliVersion": "3.1.48", - "phpVersion": "8.4", - "wordpressChannel": "beta", - "wordpressVersionApi": "https://api.wordpress.org/core/version-check/1.7/?channel=beta", - "blueprintSchema": "https://playground.wordpress.net/blueprint-schema.json" + "phpVersion": "8.4" }, "repositories": { "phpdocParser": { @@ -48,10 +35,6 @@ "path": "." } }, - "limits": { - "snapshotBytes": 104857600, - "artifactRetentionDays": 1 - }, "validation": { "minimumSymbols": { "classes": 400, diff --git a/.github/docs-playground-preview/package-lock.json b/.github/docs-playground-preview/package-lock.json index 087ed80d5d889..bd7eb283f314a 100644 --- a/.github/docs-playground-preview/package-lock.json +++ b/.github/docs-playground-preview/package-lock.json @@ -9,11 +9,14 @@ "version": "1.0.0", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/i18n": "6.24.0", "@wordpress/scripts": "32.6.0", "@wp-playground/cli": "3.1.48", "yarn": "1.22.22" }, + "devDependencies": { + "@types/node": "20.19.43", + "typescript": "^6.0.3" + }, "engines": { "node": "20.20.2" } @@ -7324,26 +7327,6 @@ "npm": ">=8.19.2" } }, - "node_modules/@wordpress/i18n": { - "version": "6.24.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.24.0.tgz", - "integrity": "sha512-K4XmCyyyDOKH/5ea75P2L36ZiAvQTy4Hsa73Na3n6NzVjAwrrKZm4JJGf0t+OlThUG4D4GyfEv5szs5EeCA0lA==", - "license": "GPL-2.0-or-later", - "dependencies": { - "@tannin/sprintf": "^1.3.2", - "@wordpress/hooks": "^4.51.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/is-shallow-equal": { "version": "5.52.0", "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-5.52.0.tgz", @@ -22144,7 +22127,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/.github/docs-playground-preview/package.json b/.github/docs-playground-preview/package.json index 63bccaa4d95c8..32eb38d94281a 100644 --- a/.github/docs-playground-preview/package.json +++ b/.github/docs-playground-preview/package.json @@ -9,12 +9,16 @@ }, "scripts": { "build": "node ../scripts/docs-playground-preview/build.mjs", - "test": "node --test ../scripts/docs-playground-preview/test/*.test.mjs" + "test": "node --test ../scripts/docs-playground-preview/test/*.test.mjs", + "typecheck": "tsc --build tsconfig.json" }, "dependencies": { - "@wordpress/i18n": "6.24.0", "@wordpress/scripts": "32.6.0", "@wp-playground/cli": "3.1.48", "yarn": "1.22.22" + }, + "devDependencies": { + "@types/node": "20.19.43", + "typescript": "^6.0.3" } } diff --git a/.github/docs-playground-preview/tsconfig.json b/.github/docs-playground-preview/tsconfig.json new file mode 100644 index 0000000000000..122a2ab366db9 --- /dev/null +++ b/.github/docs-playground-preview/tsconfig.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "module": "NodeNext", + "noEmit": true, + "strict": true, + "target": "ES2022", + "types": [ "node" ], + "incremental": false, + "composite": false + }, + "include": [ "../scripts/docs-playground-preview/**/*.mjs" ] +} diff --git a/.github/scripts/docs-playground-preview/build.mjs b/.github/scripts/docs-playground-preview/build.mjs index a28bc9df241ad..d20b385dc105b 100644 --- a/.github/scripts/docs-playground-preview/build.mjs +++ b/.github/scripts/docs-playground-preview/build.mjs @@ -1,17 +1,19 @@ #!/usr/bin/env node import { mkdir, writeFile } from 'node:fs/promises'; -import { fileURLToPath, pathToFileURL } from 'node:url'; +import { fileURLToPath } from 'node:url'; +import { parseArgs } from 'node:util'; import path from 'node:path'; import { ensureInvariantBase } from './lib/base.mjs'; -import { packageFinalSnapshot } from './lib/final.mjs'; import { readResolvedInputs, resolveBuildInputs, writeResolvedInputs, } from './lib/inputs.mjs'; import { generateParserJson } from './lib/parser.mjs'; +import { HANDOFF_METADATA, HANDOFF_SNAPSHOT } from './lib/preview.mjs'; +import { packageSnapshot } from './lib/snapshot.mjs'; import { run } from './lib/process.mjs'; import { validateSnapshot } from './lib/validate.mjs'; @@ -21,423 +23,222 @@ const TOOLING_ROOT = path.join( REPOSITORY_ROOT, '.github/docs-playground-preview' ); -const FULL_COMMIT = /^[0-9a-f]{40}$/; -const REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/; -function argumentValue( args, index, name ) { - const value = args[ index + 1 ]; - if ( ! value || value.startsWith( '--' ) ) { - throw new Error( `${ name } requires a value.` ); - } - return value; -} - -export function parseArguments( args, environment = process.env ) { - const values = {}; - for ( let index = 0; index < args.length; index++ ) { - const name = args[ index ]; - if ( name === '--resolve-only' ) { - values.resolveOnly = true; - continue; - } - if ( ! name.startsWith( '--' ) ) { - throw new Error( `Unexpected argument ${ name }.` ); - } - const key = name - .slice( 2 ) - .replace( /-([a-z])/g, ( _, letter ) => letter.toUpperCase() ); - if ( - ! [ - 'source', - 'sourceRepository', - 'sourceSha', - 'pullRequest', - 'runId', - 'runAttempt', - 'runUrl', - 'runnerImage', - 'cacheRoot', - 'workRoot', - 'output', - 'metadata', - 'inputsOutput', - 'resolvedInputs', - ].includes( key ) - ) { - throw new Error( `Unknown option ${ name }.` ); - } - values[ key ] = argumentValue( args, index, name ); - index++; - } - return { - ...values, - enforce: environment.DOCS_PREVIEW_ENFORCE === 'true', - }; -} - -function positiveInteger( value, label, nullable = true ) { - if ( - ( value === undefined || value === null || value === '' ) && - nullable - ) { - return null; - } - const number = Number( value ); - if ( ! Number.isSafeInteger( number ) || number < 1 ) { - throw new Error( `${ label } must be a positive integer.` ); - } - return number; -} - -function githubRepository( remote ) { - const match = remote - .trim() - .match( - /^(?:git@github\.com:|https:\/\/github\.com\/)([^/]+\/[^/]+?)(?:\.git)?$/ - ); - return match?.[ 1 ] || null; -} - -async function gitIdentity( source, runImplementation ) { - const sha = await runImplementation( 'git', [ 'rev-parse', 'HEAD' ], { - cwd: source, - capture: true, - quiet: true, - label: 'read source commit', - } ); - const remote = await runImplementation( +const ARGUMENTS = /** @type {const} */ ( { + 'source-repository': { type: 'string' }, + 'source-sha': { type: 'string' }, + 'run-url': { type: 'string' }, + 'runner-image': { type: 'string' }, + 'cache-root': { type: 'string' }, + 'work-root': { type: 'string' }, + handoff: { type: 'string' }, + 'inputs-output': { type: 'string' }, + 'resolved-inputs': { type: 'string' }, + 'resolve-only': { type: 'boolean' }, +} ); + +/** + * A local run has no workflow inputs, so it describes the checkout it runs in. + */ +async function gitIdentity() { + const options = { cwd: REPOSITORY_ROOT, capture: true }; + const sha = await run( 'git', [ 'rev-parse', 'HEAD' ], options ); + const remote = await run( 'git', [ 'config', '--get', 'remote.origin.url' ], - { - cwd: source, - capture: true, - quiet: true, - label: 'read source repository', - } + options ); return { sha: sha.stdout.trim(), - repository: githubRepository( remote.stdout ), + repository: remote.stdout + .trim() + .replace( /^(?:git@github\.com:|https:\/\/github\.com\/)/, '' ) + .replace( /\.git$/, '' ), }; } -async function normalizeOptions( raw, implementations ) { +/** + * @param {string[]} argv + */ +async function resolveOptions( argv ) { + const { values } = parseArgs( { args: argv, options: ARGUMENTS } ); const workspace = path.join( REPOSITORY_ROOT, '.cache/docs-playground-preview' ); - const source = path.resolve( raw.source || REPOSITORY_ROOT ); - let sourceSha = raw.sourceSha; - let sourceRepository = raw.sourceRepository; - if ( ! sourceSha || ! sourceRepository ) { - const inferred = await gitIdentity( source, implementations.run ); - sourceSha ||= inferred.sha; - sourceRepository ||= inferred.repository; - } - if ( ! FULL_COMMIT.test( sourceSha || '' ) ) { - throw new Error( 'sourceSha must be a full lowercase commit hash.' ); - } - if ( ! REPOSITORY.test( sourceRepository || '' ) ) { - throw new Error( - 'sourceRepository must be a GitHub owner/repository.' - ); - } + // Only the snapshot's provenance needs the source identity, so resolving the + // cache identity never asks git for it. + const resolveOnly = values[ 'resolve-only' ] === true; + const inferred = + resolveOnly || + ( values[ 'source-sha' ] && values[ 'source-repository' ] ) + ? null + : await gitIdentity(); const workRoot = path.resolve( - raw.workRoot || path.join( workspace, 'work' ) + values[ 'work-root' ] || path.join( workspace, 'work' ) + ); + // The build writes both handoff files; the publish job downloads the whole + // directory and reads them by their fixed names. + const handoff = path.resolve( + values.handoff || path.join( workspace, 'output' ) ); - const outputRoot = path.join( workspace, 'output' ); return { - ...raw, - source, - sourceSha, - sourceRepository, - pullRequestNumber: positiveInteger( raw.pullRequest, 'pullRequest' ), - workflowRunId: raw.runId || null, - workflowRunAttempt: positiveInteger( raw.runAttempt, 'runAttempt' ), - runUrl: raw.runUrl || null, + resolveOnly, + resolvedInputs: values[ 'resolved-inputs' ], + enforce: process.env.DOCS_PREVIEW_ENFORCE === 'true', + sourceSha: values[ 'source-sha' ] || inferred?.sha, + sourceRepository: values[ 'source-repository' ] || inferred?.repository, + runUrl: values[ 'run-url' ] || null, runnerImage: - raw.runnerImage || `${ process.platform }-${ process.arch }`, + values[ 'runner-image' ] || + `${ process.platform }-${ process.arch }`, cacheRoot: path.resolve( - raw.cacheRoot || path.join( workspace, 'cache' ) + values[ 'cache-root' ] || path.join( workspace, 'cache' ) ), workRoot, - output: path.resolve( - raw.output || - path.join( outputRoot, `code-reference-${ sourceSha }.zip` ) - ), - metadata: path.resolve( - raw.metadata || path.join( outputRoot, 'build.json' ) - ), + snapshot: path.join( handoff, HANDOFF_SNAPSHOT ), + metadata: path.join( handoff, HANDOFF_METADATA ), inputsOutput: path.resolve( - raw.inputsOutput || path.join( workRoot, 'resolved-inputs.json' ) + values[ 'inputs-output' ] || + path.join( workRoot, 'resolved-inputs.json' ) ), }; } -async function ensureNodeTools( implementations ) { - const playgroundCli = path.join( - TOOLING_ROOT, - `node_modules/.bin/wp-playground-cli${ - process.platform === 'win32' ? '.cmd' : '' - }` - ); - await implementations.run( - 'npm', - [ 'ci', '--ignore-scripts', '--no-audit', '--no-fund' ], - { cwd: TOOLING_ROOT, label: 'install pinned preview tools' } - ); - return playgroundCli; -} - -export async function verifyToolchain( inputs, options = {} ) { - const runImplementation = options.runImplementation || run; - const expected = inputs.dependencies.toolchain; - const nodeVersion = options.nodeVersion || process.versions.node; - if ( nodeVersion !== expected.nodeVersion ) { - throw new Error( - `Node ${ expected.nodeVersion } is required; found ${ nodeVersion }.` - ); - } - const npm = await runImplementation( 'npm', [ '--version' ], { - capture: true, - quiet: true, - label: 'read npm version', - } ); - if ( npm.stdout.trim() !== expected.npmVersion ) { - throw new Error( - `npm ${ - expected.npmVersion - } is required; found ${ npm.stdout.trim() }.` - ); - } - const php = await runImplementation( - 'php', - [ '-r', 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;' ], - { - capture: true, - quiet: true, - label: 'read PHP version', - } - ); - if ( php.stdout.trim() !== inputs.dependencies.playground.phpVersion ) { - throw new Error( - `PHP ${ - inputs.dependencies.playground.phpVersion - } is required; found ${ php.stdout.trim() }.` - ); - } -} - -export function createBuildMetadata( - options, - inputs, - base, - parser, - snapshot, - validation, - generationTimestamp -) { - const validationFailures = [ ...parser.failures, ...validation.failures ]; - return { - schemaVersion: 1, - sourceRepository: options.sourceRepository, - pullRequestNumber: options.pullRequestNumber, - sourceSha: options.sourceSha, - workflowRunId: options.workflowRunId, - workflowRunAttempt: options.workflowRunAttempt, - runUrl: options.runUrl, - resolvedWordPressBeta: inputs.wordpress, - phpVersion: inputs.dependencies.playground.phpVersion, - dependencyManifestDigest: inputs.cacheInputs.dependencyDigest, - snapshotFilename: snapshot.filename, - snapshotBytes: snapshot.bytes, - snapshotSha256: snapshot.sha256, - buildStatus: 'success', - validationStatus: validationFailures.length === 0 ? 'passed' : 'failed', - validationFailures, - parser: { - sourceFiles: parser.sourceFiles, - records: parser.records, - counts: parser.counts, - }, - behavior: { checks: validation.checks }, - cache: { key: inputs.cacheKey, hit: base.cacheHit }, - generationTimestamp, - }; -} - -export function createFailureMetadata( - options, - inputs, - error, - generationTimestamp -) { +/** + * The handoff contract with publish.mjs: `buildStatus` and `validationStatus` + * decide what the publish job does, and the rest describes the snapshot it + * publishes. The publish job takes identity from its own workflow run, not from + * here, so this file names no repository, commit or pull request. + * + * @param {Record | undefined} inputs + * @param {Record} result + * @returns {Record} + */ +function handoffMetadata( inputs, result ) { return { - schemaVersion: 1, - sourceRepository: options.sourceRepository, - pullRequestNumber: options.pullRequestNumber, - sourceSha: options.sourceSha, - workflowRunId: options.workflowRunId, - workflowRunAttempt: options.workflowRunAttempt, - runUrl: options.runUrl, resolvedWordPressBeta: inputs?.wordpress || null, - phpVersion: inputs?.dependencies?.playground?.phpVersion || null, - dependencyManifestDigest: inputs?.cacheInputs?.dependencyDigest || null, - snapshotFilename: null, - snapshotBytes: null, - snapshotSha256: null, - buildStatus: 'failed', - validationStatus: 'not-run', - validationFailures: [], - generationTimestamp, - buildError: error.message, + phpVersion: inputs?.dependencies.playground.phpVersion || null, + dependencyManifestDigest: inputs?.dependencyDigest || null, + ...result, }; } +/** + * @param {string} filename + * @param {unknown} value + */ async function writeJson( filename, value ) { await mkdir( path.dirname( filename ), { recursive: true } ); await writeFile( filename, `${ JSON.stringify( value, null, 2 ) }\n` ); } -const DEFAULT_IMPLEMENTATIONS = { - run, - resolveBuildInputs, - readResolvedInputs, - writeResolvedInputs, - ensureInvariantBase, - generateParserJson, - packageFinalSnapshot, - validateSnapshot, - verifyToolchain, -}; - -export async function buildCodeReferencePreview( rawOptions, overrides = {} ) { - const implementations = { ...DEFAULT_IMPLEMENTATIONS, ...overrides }; - const options = await normalizeOptions( rawOptions, implementations ); - let generationTimestamp; +/** + * @param {Record} options + */ +async function build( options ) { let inputs; - let result; try { inputs = options.resolvedInputs - ? await implementations.readResolvedInputs( options.resolvedInputs ) - : await implementations.resolveBuildInputs( { + ? await readResolvedInputs( options.resolvedInputs ) + : await resolveBuildInputs( { repositoryRoot: REPOSITORY_ROOT, cacheRoot: options.cacheRoot, platform: process.platform, architecture: process.arch, runnerImage: options.runnerImage, } ); - await implementations.verifyToolchain( inputs, { - runImplementation: implementations.run, - } ); if ( options.resolveOnly ) { - await implementations.writeResolvedInputs( - inputs, - options.inputsOutput - ); - return { inputs, options }; + await writeResolvedInputs( inputs, options.inputsOutput ); + return { inputs, metadata: null }; } - generationTimestamp = new Date().toISOString(); const provenance = { sourceRepository: options.sourceRepository, sourceSha: options.sourceSha, - generationTimestamp, + generationTimestamp: new Date().toISOString(), runUrl: options.runUrl, }; - const playgroundCli = await ensureNodeTools( implementations ); - const base = await implementations.ensureInvariantBase( inputs, { - playgroundCli, - } ); - const parser = await implementations.generateParserJson( { - source: options.source, + const playgroundCli = path.join( + TOOLING_ROOT, + 'node_modules/.bin/wp-playground-cli' + ); + await run( + 'npm', + [ 'ci', '--ignore-scripts', '--no-audit', '--no-fund' ], + { cwd: TOOLING_ROOT, label: 'install pinned preview tools' } + ); + await ensureInvariantBase( inputs, playgroundCli ); + const parser = await generateParserJson( { + source: REPOSITORY_ROOT, stagedSource: path.join( options.workRoot, 'source' ), parser: path.join( inputs.cacheDirectory, 'parser' ), output: path.join( options.workRoot, 'reference.json' ), - logFile: path.join( options.workRoot, 'parser.log' ), minimumSymbols: inputs.dependencies.validation.minimumSymbols, } ); - const snapshot = await implementations.packageFinalSnapshot( inputs, { + await packageSnapshot( inputs, { workDirectory: path.join( options.workRoot, 'final' ), - output: options.output, + output: options.snapshot, referenceJson: path.join( options.workRoot, 'reference.json' ), stagedSource: path.join( options.workRoot, 'source' ), playgroundCli, provenance, } ); - const validation = await implementations.validateSnapshot( inputs, { - snapshot: options.output, + const validation = await validateSnapshot( inputs, { + snapshot: options.snapshot, workDirectory: path.join( options.workRoot, 'validation' ), playgroundCli, provenance, - requireRunUrl: options.workflowRunId !== null, } ); - const metadata = createBuildMetadata( - options, - inputs, - base, - parser, - snapshot, - validation, - generationTimestamp - ); - await writeJson( options.metadata, metadata ); - result = { + const validationFailures = [ + ...parser.failures, + ...validation.failures, + ]; + return { inputs, - base, - parser, - snapshot, - validation, - metadata, - options, + metadata: handoffMetadata( inputs, { + buildStatus: 'success', + validationStatus: + validationFailures.length === 0 ? 'passed' : 'failed', + validationFailures, + generationTimestamp: provenance.generationTimestamp, + } ), }; } catch ( error ) { - try { - await writeJson( - options.metadata, - createFailureMetadata( - options, - inputs, - error, - generationTimestamp || new Date().toISOString() - ) - ); - } catch { - // Preserve the build error when its terminal metadata cannot be written. - } + // The publish job reports the failure, so it needs a handoff even here. + await writeJson( + options.metadata, + handoffMetadata( inputs, { + buildStatus: 'failed', + buildError: + error instanceof Error ? error.message : String( error ), + } ) + ); throw error; } - const { metadata } = result; - if ( metadata.validationStatus === 'failed' && options.enforce ) { - throw new Error( metadata.validationFailures.join( '\n' ) ); - } - return result; } async function main() { - const result = await buildCodeReferencePreview( - parseArguments( process.argv.slice( 2 ) ) - ); - for ( const failure of result.metadata?.validationFailures || [] ) { + const options = await resolveOptions( process.argv.slice( 2 ) ); + const { inputs, metadata } = await build( options ); + if ( ! metadata ) { + process.stdout.write( + `Resolved WordPress ${ inputs.wordpress.version } and cache key ${ inputs.cacheKey }.\n` + ); + return; + } + + await writeJson( options.metadata, metadata ); + for ( const failure of metadata.validationFailures ) { process.stderr.write( `::warning::${ failure }\n` ); } - process.stdout.write( - `${ JSON.stringify( - result.metadata || { - cacheKey: result.inputs.cacheKey, - resolvedWordPressBeta: result.inputs.wordpress, - }, - null, - 2 - ) }\n` - ); + process.stdout.write( `${ JSON.stringify( metadata, null, 2 ) }\n` ); + if ( metadata.validationStatus === 'failed' && options.enforce ) { + throw new Error( metadata.validationFailures.join( '\n' ) ); + } } -if ( - import.meta.url === pathToFileURL( path.resolve( process.argv[ 1 ] ) ).href -) { - main().catch( ( error ) => { - process.stderr.write( `${ error.message }\n` ); - process.exitCode = 1; - } ); -} +main().catch( ( error ) => { + process.stderr.write( `${ error.message }\n` ); + process.exitCode = 1; +} ); diff --git a/.github/scripts/docs-playground-preview/lib/archive.mjs b/.github/scripts/docs-playground-preview/lib/archive.mjs deleted file mode 100644 index d67e60cd2e25f..0000000000000 --- a/.github/scripts/docs-playground-preview/lib/archive.mjs +++ /dev/null @@ -1,62 +0,0 @@ -import { cp, mkdir, mkdtemp, rm } from 'node:fs/promises'; -import os from 'node:os'; -import path from 'node:path'; - -import { run } from './process.mjs'; - -const DEFAULT_EXCLUDES = [ '.git', '.github', 'node_modules', 'tests' ]; - -function isExcluded( relative, excludes ) { - return excludes.some( - ( excluded ) => - relative === excluded || relative.startsWith( `${ excluded }/` ) - ); -} - -export async function copyDirectory( source, destination, excludes = [] ) { - const root = path.resolve( source ); - await mkdir( destination, { recursive: true } ); - await cp( root, destination, { - recursive: true, - filter: ( filename ) => { - const relative = path.relative( root, filename ); - return ! relative || ! isExcluded( relative, excludes ); - }, - } ); -} - -export async function zipDirectory( - source, - output, - rootName, - runImplementation = run -) { - const temporary = await mkdtemp( - path.join( os.tmpdir(), 'docs-preview-zip-' ) - ); - const target = - rootName === '.' ? temporary : path.join( temporary, rootName ); - try { - await copyDirectory( source, target, DEFAULT_EXCLUDES ); - await rm( output, { force: true } ); - await runImplementation( - 'zip', - [ '-rq', path.resolve( output ), '.' ], - { - cwd: temporary, - label: `package ${ rootName }`, - } - ); - } finally { - await rm( temporary, { recursive: true, force: true } ); - } -} - -export async function downloadFile( url, output, runImplementation = run ) { - await mkdir( path.dirname( output ), { recursive: true } ); - await runImplementation( - 'curl', - [ '--fail', '--location', '--output', output, url ], - { label: `download ${ url }` } - ); -} diff --git a/.github/scripts/docs-playground-preview/lib/base.mjs b/.github/scripts/docs-playground-preview/lib/base.mjs index b21ee0f7f517f..e92add491a6c3 100644 --- a/.github/scripts/docs-playground-preview/lib/base.mjs +++ b/.github/scripts/docs-playground-preview/lib/base.mjs @@ -1,41 +1,269 @@ +import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; +import { existsSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; -import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'; +import os from 'node:os'; import path from 'node:path'; -import { copyDirectory, downloadFile, zipDirectory } from './archive.mjs'; -import { acquireRepositories, exists } from './files.mjs'; -import { buildSnapshot } from './playground.mjs'; import { run } from './process.mjs'; +import { buildSnapshot } from './snapshot.mjs'; -const LIBRARY_ROOT = path.dirname( fileURLToPath( import.meta.url ) ); -const SCRIPT_ROOT = path.dirname( LIBRARY_ROOT ); -const TOOLING_ROOT = path.resolve( - LIBRARY_ROOT, - '../../../docs-playground-preview' +const SCRIPT_ROOT = path.resolve( + path.dirname( fileURLToPath( import.meta.url ) ), + '..' ); const PHP_ROOT = path.join( SCRIPT_ROOT, 'php' ); +const TOOLING_ROOT = path.resolve( + SCRIPT_ROOT, + '../../docs-playground-preview' +); +const NODE_MODULES = path.join( TOOLING_ROOT, 'node_modules' ); +const COMPOSER_INSTALL = [ + 'install', + '--no-interaction', + '--no-dev', + '--prefer-dist', +]; +// Build output and version control never belong in an installed plugin or theme. +const BUNDLE_EXCLUDES = [ '.git', '.github', 'node_modules', 'tests' ]; +const PINNED_COMMIT = /^[0-9a-f]{40}$/; +/** + * @param {string} name + */ function executable( name ) { - return path.join( - TOOLING_ROOT, - 'node_modules/.bin', - process.platform === 'win32' ? `${ name }.cmd` : name + return path.join( NODE_MODULES, '.bin', name ); +} + +/** + * Checks out every upstream dependency at its pinned commit, fetching only the + * commit itself rather than the history behind it. + * + * @param {Record} repositories + * @param {string} destination + */ +async function acquireRepositories( repositories, destination ) { + for ( const [ name, dependency ] of Object.entries( repositories ) ) { + // The cache key covers the manifest as text, so a branch or a tag here + // would let the base change under a key that stays the same, and a + // value starting with a dash would reach git as an option. + if ( ! PINNED_COMMIT.test( dependency.commit ) ) { + throw new Error( + `repositories.${ name }.commit must be a full commit hash.` + ); + } + const target = path.join( destination, name ); + const label = dependency.repository; + await mkdir( target, { recursive: true } ); + await run( 'git', [ 'init', '--quiet' ], { + cwd: target, + label: `initialize ${ label }`, + } ); + await run( + 'git', + [ + 'fetch', + '--quiet', + '--depth=1', + `https://github.com/${ dependency.repository }.git`, + dependency.commit, + ], + { cwd: target, label: `fetch ${ label }` } + ); + await run( + 'git', + [ + '-c', + 'advice.detachedHead=false', + 'checkout', + '--quiet', + 'FETCH_HEAD', + ], + { cwd: target, label: `checkout ${ label }` } + ); + } +} + +/** + * @param {string} source + * @param {string} destination + * @param {string[]} excludes + */ +async function copyDirectory( source, destination, excludes ) { + const root = path.resolve( source ); + await mkdir( destination, { recursive: true } ); + await cp( root, destination, { + recursive: true, + filter: ( filename ) => { + const relative = path.relative( root, filename ); + return ( + ! relative || + ! excludes.some( + ( excluded ) => + relative === excluded || + relative.startsWith( `${ excluded }/` ) + ) + ); + }, + } ); +} + +/** + * Packages an installable ZIP whose entries live under `rootName`, the + * directory name WordPress installs the plugin or theme into. + * + * @param {string} source + * @param {string} output + * @param {string} rootName + */ +async function zipDirectory( source, output, rootName ) { + const temporary = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-zip-' ) ); + const target = + rootName === '.' ? temporary : path.join( temporary, rootName ); + try { + await copyDirectory( source, target, BUNDLE_EXCLUDES ); + await run( 'zip', [ '-rq', path.resolve( output ), '.' ], { + cwd: temporary, + label: `package ${ rootName }`, + } ); + } finally { + await rm( temporary, { recursive: true, force: true } ); + } +} + +/** + * The CJK serif faces are tens of megabytes and no Code Reference page uses + * them; the snapshot has to stay under 100 MiB. + * + * @param {string} muPlugins + */ +async function prunePreviewFonts( muPlugins ) { + await rm( path.join( muPlugins, 'global-fonts/NotoSerif' ), { + recursive: true, + force: true, + } ); + const stylesheet = path.join( muPlugins, 'global-fonts/style.css' ); + const css = await readFile( stylesheet, 'utf8' ); + await writeFile( + stylesheet, + css + .split( '\n' ) + .filter( ( line ) => ! line.includes( '@import "./NotoSerif/' ) ) + .join( '\n' ) + ); +} + +/** + * @param {string} upstreams + * @param {Record} inputs + */ +function repositoryRoots( upstreams, inputs ) { + const root = /** @param {string} name */ ( name ) => + path.join( upstreams, name ); + const selected = /** @param {string} name */ ( name ) => + path.join( + root( name ), + inputs.dependencies.repositories[ name ].path + ); + return { + phpdocParser: selected( 'phpdocParser' ), + wporgDeveloperTheme: selected( 'wporgDeveloper' ), + wporgParent2021: root( 'wporgParent2021' ), + wporgParentTheme: selected( 'wporgParent2021' ), + wporgMuPlugins: root( 'wporgMuPlugins' ), + wporgMuPluginsFiles: selected( 'wporgMuPlugins' ), + postsToPosts: selected( 'postsToPosts' ), + codeSyntaxBlock: selected( 'codeSyntaxBlock' ), + }; +} + +/** + * The base holds the DevHub site with no reference content at all, so it stays + * valid for every pull request that resolves to the same cache key. + * + * @param {Record} inputs + */ +function baseBlueprint( inputs ) { + return { + meta: { + title: 'WordPress Core Code Reference invariant base', + author: 'WordPress', + description: + 'Pinned DevHub dependencies with an empty reference index.', + }, + preferredVersions: { + php: inputs.dependencies.playground.phpVersion, + wp: inputs.wordpress.version, + }, + landingPage: '/reference/', + login: false, + features: { networking: false }, + extraLibraries: [ 'wp-cli' ], + steps: [ + { + step: 'writeFile', + path: '/wordpress/wp-content/mu-plugins/000-docs-preview-build.php', + data: { resource: 'bundled', path: 'php/base-policy.php' }, + }, + { + step: 'unzip', + zipFile: { + resource: 'bundled', + path: 'bundles/wporg-mu-plugins.zip', + }, + extractToPath: '/wordpress/wp-content/mu-plugins', + }, + ...[ 'wporg-parent-2021', 'wporg-developer-2023' ].map( + ( theme ) => ( { + step: 'installTheme', + themeData: { + resource: 'bundled', + path: `bundles/${ theme }.zip`, + }, + ifAlreadyInstalled: 'overwrite', + } ) + ), + ...[ 'code-syntax-block', 'posts-to-posts', 'phpdoc-parser' ].map( + ( plugin ) => ( { + step: 'unzip', + zipFile: { + resource: 'bundled', + path: `bundles/${ plugin }.zip`, + }, + extractToPath: '/wordpress/wp-content/plugins', + } ) + ), + { + step: 'writeFile', + path: '/tmp/docs-preview-configure-base.php', + data: { resource: 'bundled', path: 'php/configure-base.php' }, + }, + { + step: 'wp-cli', + command: 'wp eval-file /tmp/docs-preview-configure-base.php', + }, + ], + }; } -export function dependencyBuildPlan( roots, composer ) { - const nodePath = path.join( TOOLING_ROOT, 'node_modules' ); - const wpScriptsEnvironment = { NODE_PATH: nodePath }; - return [ +/** + * @param {Record} inputs + * @param {string} playgroundCli + */ +async function buildInvariantBase( inputs, playgroundCli ) { + const work = path.join( inputs.cacheDirectory, 'work' ); + const upstreams = path.join( work, 'upstreams' ); + const bundles = path.join( work, 'bundles' ); + await mkdir( bundles, { recursive: true } ); + await acquireRepositories( inputs.dependencies.repositories, upstreams ); + const roots = repositoryRoots( upstreams, inputs ); + + const wpScriptsEnvironment = { NODE_PATH: NODE_MODULES }; + const plan = [ { - command: 'php', - args: [ - composer, - 'install', - '--no-interaction', - '--no-dev', - '--prefer-dist', - ], + command: 'composer', + args: COMPOSER_INSTALL, cwd: roots.phpdocParser, label: 'install phpdoc-parser dependencies', }, @@ -65,14 +293,8 @@ export function dependencyBuildPlan( roots, composer ) { label: 'install wporg-mu-plugins dependencies', }, { - command: 'php', - args: [ - composer, - 'install', - '--no-interaction', - '--no-dev', - '--prefer-dist', - ], + command: 'composer', + args: COMPOSER_INSTALL, cwd: roots.wporgMuPlugins, label: 'install wporg-mu-plugins Composer dependencies', }, @@ -83,25 +305,14 @@ export function dependencyBuildPlan( roots, composer ) { label: 'build wporg-mu-plugins', }, { - command: 'php', - args: [ - composer, - 'config', - 'allow-plugins.composer/installers', - 'true', - ], + command: 'composer', + args: [ 'config', 'allow-plugins.composer/installers', 'true' ], cwd: roots.postsToPosts, label: 'configure posts-to-posts installer', }, { - command: 'php', - args: [ - composer, - 'install', - '--no-interaction', - '--no-dev', - '--prefer-dist', - ], + command: 'composer', + args: COMPOSER_INSTALL, cwd: roots.postsToPosts, label: 'install posts-to-posts dependencies', }, @@ -113,134 +324,8 @@ export function dependencyBuildPlan( roots, composer ) { label: 'build code-syntax-block', }, ]; -} - -function bundled( resourcePath ) { - return { resource: 'bundled', path: resourcePath }; -} - -export function createInvariantBaseBlueprint( inputs ) { - return { - $schema: inputs.dependencies.playground.blueprintSchema, - meta: { - title: 'WordPress Core Code Reference invariant base', - author: 'WordPress', - description: - 'Pinned DevHub dependencies with an empty reference index.', - }, - preferredVersions: { - php: inputs.dependencies.playground.phpVersion, - wp: inputs.wordpress.version, - }, - landingPage: '/reference/', - login: false, - features: { networking: false }, - extraLibraries: [ 'wp-cli' ], - steps: [ - { - step: 'writeFile', - path: '/wordpress/wp-content/mu-plugins/000-docs-preview-build.php', - data: bundled( 'php/base-policy.php' ), - }, - { - step: 'unzip', - zipFile: bundled( 'bundles/wporg-mu-plugins.zip' ), - extractToPath: '/wordpress/wp-content/mu-plugins', - }, - { - step: 'installTheme', - themeData: bundled( 'bundles/wporg-parent-2021.zip' ), - ifAlreadyInstalled: 'overwrite', - }, - { - step: 'installTheme', - themeData: bundled( 'bundles/wporg-developer-2023.zip' ), - ifAlreadyInstalled: 'overwrite', - }, - ...[ 'code-syntax-block', 'posts-to-posts', 'phpdoc-parser' ].map( - ( plugin ) => ( { - step: 'unzip', - zipFile: bundled( `bundles/${ plugin }.zip` ), - extractToPath: '/wordpress/wp-content/plugins', - } ) - ), - { - step: 'writeFile', - path: '/tmp/docs-preview-configure-base.php', - data: bundled( 'php/configure-base.php' ), - }, - { - step: 'wp-cli', - command: 'wp eval-file /tmp/docs-preview-configure-base.php', - }, - ], - }; -} - -export async function prunePreviewFonts( muPlugins ) { - await rm( path.join( muPlugins, 'global-fonts/NotoSerif' ), { - recursive: true, - force: true, - } ); - const stylesheet = path.join( muPlugins, 'global-fonts/style.css' ); - const css = await readFile( stylesheet, 'utf8' ); - await writeFile( - stylesheet, - css - .split( '\n' ) - .filter( ( line ) => ! line.includes( '@import "./NotoSerif/' ) ) - .join( '\n' ) - ); -} - -async function ensureComposer( inputs, tools, runImplementation ) { - const version = inputs.dependencies.toolchain.composerVersion; - const composer = path.join( tools, `composer-${ version }.phar` ); - if ( ! ( await exists( composer ) ) ) { - await downloadFile( - `https://getcomposer.org/download/${ version }/composer.phar`, - composer, - runImplementation - ); - } - return composer; -} - -function repositoryRoots( upstreams, inputs ) { - const root = ( name ) => path.join( upstreams, name ); - const selected = ( name ) => - path.join( - root( name ), - inputs.dependencies.repositories[ name ].path - ); - return { - phpdocParser: selected( 'phpdocParser' ), - wporgDeveloperTheme: selected( 'wporgDeveloper' ), - wporgParent2021: root( 'wporgParent2021' ), - wporgParentTheme: selected( 'wporgParent2021' ), - wporgMuPlugins: root( 'wporgMuPlugins' ), - wporgMuPluginsFiles: selected( 'wporgMuPlugins' ), - postsToPosts: selected( 'postsToPosts' ), - codeSyntaxBlock: selected( 'codeSyntaxBlock' ), - }; -} - -async function buildInvariantBase( inputs, options ) { - const runImplementation = options.runImplementation || run; - const work = path.join( inputs.cacheDirectory, 'work' ); - const upstreams = path.join( work, 'upstreams' ); - const bundles = path.join( work, 'bundles' ); - const tools = path.join( work, 'tools' ); - await mkdir( bundles, { recursive: true } ); - await acquireRepositories( - inputs.dependencies.repositories, - upstreams, - runImplementation - ); - const roots = repositoryRoots( upstreams, inputs ); - const composer = await ensureComposer( inputs, tools, runImplementation ); - for ( const task of dependencyBuildPlan( roots, composer ) ) { - await runImplementation( task.command, task.args, task ); + for ( const task of plan ) { + await run( task.command, task.args, task ); } await prunePreviewFonts( roots.wporgMuPluginsFiles ); @@ -259,8 +344,7 @@ async function buildInvariantBase( inputs, options ) { await zipDirectory( source, path.join( bundles, `${ name }.zip` ), - rootName, - runImplementation + rootName ); } @@ -274,23 +358,14 @@ async function buildInvariantBase( inputs, options ) { const blueprint = path.join( work, 'base-blueprint.json' ); await writeFile( blueprint, - `${ JSON.stringify( - createInvariantBaseBlueprint( inputs ), - null, - 2 - ) }\n` - ); - await ( options.buildSnapshotImplementation || buildSnapshot )( - options.playgroundCli || executable( 'wp-playground-cli' ), - { - php: inputs.dependencies.playground.phpVersion, - wp: inputs.wordpress.version, - blueprint, - 'blueprint-may-read-adjacent-files': true, - outfile: path.join( inputs.cacheDirectory, 'base.zip' ), - verbosity: 'normal', - } + `${ JSON.stringify( baseBlueprint( inputs ), null, 2 ) }\n` ); + await buildSnapshot( inputs, playgroundCli, { + blueprint, + outfile: path.join( inputs.cacheDirectory, 'base.zip' ), + } ); + // Every build runs the parser from the cache, so it is kept; the clones and + // bundles it was built from are not worth restoring. await copyDirectory( roots.phpdocParser, path.join( inputs.cacheDirectory, 'parser' ), @@ -299,38 +374,26 @@ async function buildInvariantBase( inputs, options ) { await rm( work, { recursive: true, force: true } ); } -export async function ensureInvariantBase( inputs, options = {} ) { - const markerFile = path.join( inputs.cacheDirectory, 'base.json' ); - if ( await exists( markerFile ) ) { - const marker = JSON.parse( await readFile( markerFile, 'utf8' ) ); - if ( - marker.cacheKey === inputs.cacheKey && - ( await exists( - path.join( inputs.cacheDirectory, 'base.zip' ) - ) ) && - ( await exists( - path.join( - inputs.cacheDirectory, - 'parser/generate-json-manually.php' - ) - ) ) - ) { - return { ...marker, cacheHit: true }; - } +/** + * Restores the cached base site, or builds it when the cache is cold or was + * only partly restored. + * + * @param {Record} inputs + * @param {string} playgroundCli + */ +export async function ensureInvariantBase( inputs, playgroundCli ) { + const snapshot = path.join( inputs.cacheDirectory, 'base.zip' ); + const parser = path.join( + inputs.cacheDirectory, + 'parser/generate-json-manually.php' + ); + if ( existsSync( snapshot ) && existsSync( parser ) ) { + process.stdout.write( + `Reusing the cached site base ${ inputs.cacheKey }.\n` + ); + return; } await rm( inputs.cacheDirectory, { recursive: true, force: true } ); await mkdir( inputs.cacheDirectory, { recursive: true } ); - await ( options.buildImplementation || buildInvariantBase )( - inputs, - options - ); - const marker = { - schemaVersion: 1, - cacheKey: inputs.cacheKey, - wordpress: inputs.wordpress, - dependencyDigest: inputs.cacheInputs.dependencyDigest, - harnessDigest: inputs.cacheInputs.harnessDigest, - }; - await writeFile( markerFile, `${ JSON.stringify( marker, null, 2 ) }\n` ); - return { ...marker, cacheHit: false }; + await buildInvariantBase( inputs, playgroundCli ); } diff --git a/.github/scripts/docs-playground-preview/lib/comment.mjs b/.github/scripts/docs-playground-preview/lib/comment.mjs new file mode 100644 index 0000000000000..e5add2327c293 --- /dev/null +++ b/.github/scripts/docs-playground-preview/lib/comment.mjs @@ -0,0 +1,119 @@ +// The single pull request comment. It is created once and updated in place, so +// every state below replaces the whole body. + +import { COMMENT_MARKER } from './preview.mjs'; + +const SOURCE_MARKER = + //; + +/** + * @param {string} repository + * @param {string} sha + */ +function commitLink( repository, sha ) { + return `[${ sha }](https://github.com/${ repository }/commit/${ sha })`; +} + +/** + * The source marker records which commit the comment describes. The lifecycle + * reads it back to tell an outdated comment from a current one. + * + * @param {string | null} repository + * @param {string | null} sha + */ +function header( repository = null, sha = null ) { + const marker = repository + ? `\n` + : ''; + return `${ COMMENT_MARKER }${ marker }\n## Code Reference documentation preview`; +} + +/** + * @param {unknown} body + */ +export function readPreviewCommentSource( body ) { + const match = typeof body === 'string' ? body.match( SOURCE_MARKER ) : null; + return match ? { repository: match[ 1 ], sha: match[ 2 ] } : null; +} + +/** + * @param {Record} preview + * @param {string} runUrl + */ +export function readyComment( preview, runUrl ) { + return `${ header( + preview.sourceRepository, + preview.sourceSha + ) }\n\n**Status:** Ready\n\n[Open Code Reference preview](${ + preview.publication.playgroundUrl + })\n\nSource: ${ commitLink( + preview.sourceRepository, + preview.sourceSha + ) } \nPublished: ${ + preview.publication.publishedAt + } \n[GitHub Actions run](${ runUrl })`; +} + +/** + * @param {Record} context + * @param {Record | null} previous + */ +export function failedComment( context, previous ) { + const fallback = previous + ? `\n\n[Latest successful docs preview](${ + previous.publication.playgroundUrl + }) — built from ${ commitLink( + previous.sourceRepository, + previous.sourceSha + ) }.` + : ''; + return `${ header( + context.sourceRepository, + context.sourceSha + ) }\n\n**Status:** Latest attempt failed\n\nThe latest attempt for ${ commitLink( + context.sourceRepository, + context.sourceSha + ) } failed at ${ new Date().toISOString() }. [View the GitHub Actions run](${ + context.runUrl + }).${ fallback }`; +} + +/** + * @param {Record} preview + * @param {Record} context + */ +export function staleComment( preview, context ) { + return `${ header( + preview.sourceRepository, + preview.sourceSha + ) }\n\n**Status:** Stale\n\nThe latest successful preview was built from ${ commitLink( + preview.sourceRepository, + preview.sourceSha + ) }, but the pull request is now at ${ commitLink( + context.sourceRepository, + context.sourceSha + ) }.\n\n[Latest successful docs preview](${ + preview.publication.playgroundUrl + })\n\nAdd the \`docs-preview\` label again to build the current commit.`; +} + +/** + * @param {Record} previous + * @param {Record} context + */ +export function staleUnavailableComment( previous, context ) { + return `${ header( + previous.repository, + previous.sha + ) }\n\n**Status:** Stale\n\nThe latest preview attempt was for ${ commitLink( + previous.repository, + previous.sha + ) }, but no healthy docs preview is available. The pull request is now at ${ commitLink( + context.sourceRepository, + context.sourceSha + ) }.\n\nAdd the \`docs-preview\` label again to build the current commit.`; +} + +export function expiredComment() { + return `${ header() }\n\n**Status:** Expired\n\nThis pull request is closed or merged. Its Code Reference preview expired and is no longer live.`; +} diff --git a/.github/scripts/docs-playground-preview/lib/config.mjs b/.github/scripts/docs-playground-preview/lib/config.mjs deleted file mode 100644 index 633ae0bf3f6fb..0000000000000 --- a/.github/scripts/docs-playground-preview/lib/config.mjs +++ /dev/null @@ -1,202 +0,0 @@ -import { createHash } from 'node:crypto'; -import { readFile } from 'node:fs/promises'; - -const FULL_COMMIT = /^[0-9a-f]{40}$/; -const FULL_DIGEST = /^[0-9a-f]{64}$/; -const VERSION = /^\d+\.\d+\.\d+$/; -const API_BETA_VERSION = /^\d+\.\d+(?:\.\d+)?-(?:beta\d+|RC\d+)$/; -const OFFICIAL_REPOSITORIES = Object.freeze( { - phpdocParser: 'WordPress/phpdoc-parser', - wporgDeveloper: 'WordPress/wporg-developer', - wporgParent2021: 'WordPress/wporg-parent-2021', - wporgMuPlugins: 'WordPress/wporg-mu-plugins', - postsToPosts: 'scribu/wp-posts-to-posts', - codeSyntaxBlock: 'mkaz/code-syntax-block', -} ); -const DEPLOYMENT_REPOSITORIES = new Set( [ - 'WordPress/wordpress-develop', - 'sirreal/wordpress-develop', -] ); -const VERSION_API = - 'https://api.wordpress.org/core/version-check/1.7/?channel=beta'; - -function assertObject( value, label ) { - if ( ! value || typeof value !== 'object' || Array.isArray( value ) ) { - throw new Error( `${ label } must be an object.` ); - } -} - -function assertPositiveInteger( value, label ) { - if ( ! Number.isSafeInteger( value ) || value < 1 ) { - throw new Error( `${ label } must be a positive integer.` ); - } -} - -function assertDigest( value, label ) { - if ( typeof value !== 'string' || ! FULL_DIGEST.test( value ) ) { - throw new Error( `${ label } must be a lowercase SHA-256 digest.` ); - } -} - -export function validateDependencies( dependencies ) { - assertObject( dependencies, 'Dependency manifest' ); - if ( dependencies.schemaVersion !== 1 ) { - throw new Error( - `Unsupported dependency schema ${ dependencies.schemaVersion }.` - ); - } - assertPositiveInteger( - dependencies.cacheSchemaVersion, - 'cacheSchemaVersion' - ); - assertObject( dependencies.toolchain, 'toolchain' ); - assertObject( dependencies.playground, 'playground' ); - for ( const name of [ - 'nodeVersion', - 'npmVersion', - 'composerVersion', - 'yarnVersion', - 'wpScriptsVersion', - 'wpI18nVersion', - ] ) { - if ( ! VERSION.test( dependencies.toolchain[ name ] ) ) { - throw new Error( `toolchain.${ name } must be an exact version.` ); - } - } - if ( dependencies.playground.cliVersion !== '3.1.48' ) { - throw new Error( 'Unexpected Playground CLI version.' ); - } - if ( dependencies.playground.phpVersion !== '8.4' ) { - throw new Error( 'The Playground PHP version must be 8.4.' ); - } - if ( dependencies.playground.wordpressChannel !== 'beta' ) { - throw new Error( 'The WordPress runtime channel must be beta.' ); - } - if ( dependencies.playground.wordpressVersionApi !== VERSION_API ) { - throw new Error( - 'The WordPress beta resolver must use the official API.' - ); - } - if ( dependencies.limits?.snapshotBytes !== 104857600 ) { - throw new Error( 'The snapshot limit must be exactly 100 MiB.' ); - } - - assertObject( dependencies.repositories, 'repositories' ); - for ( const [ name, repository ] of Object.entries( - OFFICIAL_REPOSITORIES - ) ) { - const dependency = dependencies.repositories[ name ]; - assertObject( dependency, `repositories.${ name }` ); - if ( dependency.repository !== repository ) { - throw new Error( - `repositories.${ name } must use ${ repository }.` - ); - } - if ( ! FULL_COMMIT.test( dependency.commit ) ) { - throw new Error( - `repositories.${ name }.commit must be a full commit hash.` - ); - } - if ( typeof dependency.path !== 'string' || dependency.path === '' ) { - throw new Error( `repositories.${ name }.path must be nonempty.` ); - } - } - if ( - Object.keys( dependencies.repositories ).length !== - Object.keys( OFFICIAL_REPOSITORIES ).length - ) { - throw new Error( - 'The dependency manifest contains an unknown repository.' - ); - } - - assertObject( dependencies.validation?.minimumSymbols, 'minimumSymbols' ); - for ( const type of [ - 'classes', - 'methods', - 'functions', - 'hooks', - 'filters', - ] ) { - assertPositiveInteger( - dependencies.validation.minimumSymbols[ type ], - `minimumSymbols.${ type }` - ); - } - return dependencies; -} - -export async function loadDependencies( filename ) { - const bytes = await readFile( filename ); - const dependencies = validateDependencies( JSON.parse( bytes ) ); - return { - dependencies, - digest: createHash( 'sha256' ).update( bytes ).digest( 'hex' ), - }; -} - -export async function resolveWordPressBeta( - fetchImplementation = globalThis.fetch -) { - const response = await fetchImplementation( VERSION_API, { - headers: { Accept: 'application/json' }, - } ); - if ( ! response.ok ) { - throw new Error( - `WordPress beta API returned HTTP ${ response.status }.` - ); - } - const body = await response.json(); - const offer = body?.offers?.find( - ( candidate ) => - API_BETA_VERSION.test( candidate?.version ) && - candidate.download === - `https://downloads.wordpress.org/release/wordpress-${ candidate.version }.zip` - ); - if ( ! offer ) { - throw new Error( - 'The WordPress beta API returned no concrete beta build.' - ); - } - return Object.freeze( { - channel: 'beta', - version: offer.version, - downloadUrl: offer.download, - } ); -} - -export function makeBaseCacheKey( inputs ) { - assertObject( inputs, 'Cache inputs' ); - assertPositiveInteger( - inputs.cacheSchemaVersion, - 'Cache cacheSchemaVersion' - ); - for ( const [ name, value ] of Object.entries( { - platform: inputs.platform, - architecture: inputs.architecture, - runnerImage: inputs.runnerImage, - phpVersion: inputs.phpVersion, - wordpressVersion: inputs.wordpressVersion, - } ) ) { - if ( - typeof value !== 'string' || - ! /^[A-Za-z0-9_.-]+$/.test( value ) - ) { - throw new Error( `Cache ${ name } is invalid.` ); - } - } - assertDigest( inputs.dependencyDigest, 'Cache dependencyDigest' ); - assertDigest( inputs.harnessDigest, 'Cache harnessDigest' ); - const identity = createHash( 'sha256' ) - .update( JSON.stringify( inputs ) ) - .digest( 'hex' ); - return `docs-preview-base-v${ inputs.cacheSchemaVersion }-${ identity }`; -} - -export function isDeploymentEnabled( repository, stagingValue = '' ) { - return ( - DEPLOYMENT_REPOSITORIES.has( repository ) && - ( repository === 'WordPress/wordpress-develop' || - stagingValue === 'true' ) - ); -} diff --git a/.github/scripts/docs-playground-preview/lib/files.mjs b/.github/scripts/docs-playground-preview/lib/files.mjs deleted file mode 100644 index 3028c045dd631..0000000000000 --- a/.github/scripts/docs-playground-preview/lib/files.mjs +++ /dev/null @@ -1,144 +0,0 @@ -import { createHash } from 'node:crypto'; -import { copyFile, mkdir, readdir, readFile, rm, stat } from 'node:fs/promises'; -import path from 'node:path'; - -import { run } from './process.mjs'; - -const EXCLUDED_SOURCE_DIRECTORIES = new Set( [ - 'wp-content/plugins', - 'wp-content/themes', -] ); - -export async function exists( filename ) { - try { - await stat( filename ); - return true; - } catch ( error ) { - if ( error.code === 'ENOENT' ) { - return false; - } - throw error; - } -} - -export async function findWordPressSourceRoot( candidate ) { - const root = path.resolve( candidate ); - for ( const directory of [ root, path.join( root, 'src' ) ] ) { - if ( - await exists( path.join( directory, 'wp-includes/version.php' ) ) - ) { - return directory; - } - } - throw new Error( `No WordPress source tree found beneath ${ root }.` ); -} - -async function listFiles( root, relative = '' ) { - const files = []; - const entries = await readdir( path.join( root, relative ), { - withFileTypes: true, - } ); - for ( const entry of entries.sort( ( a, b ) => - a.name.localeCompare( b.name ) - ) ) { - const child = relative ? `${ relative }/${ entry.name }` : entry.name; - if ( entry.isDirectory() ) { - files.push( ...( await listFiles( root, child ) ) ); - } else if ( entry.isFile() ) { - files.push( child ); - } - } - return files; -} - -export async function digestTree( root, include = () => true ) { - const hash = createHash( 'sha256' ); - for ( const relative of await listFiles( root ) ) { - if ( ! include( relative ) ) { - continue; - } - const bytes = await readFile( path.join( root, relative ) ); - hash.update( `${ Buffer.byteLength( relative ) }:${ relative }` ); - hash.update( `${ bytes.length }:` ); - hash.update( bytes ); - } - return hash.digest( 'hex' ); -} - -export async function sha256File( filename ) { - return createHash( 'sha256' ) - .update( await readFile( filename ) ) - .digest( 'hex' ); -} - -export async function stageCorePhp( candidate, destination ) { - const source = await findWordPressSourceRoot( candidate ); - await rm( destination, { recursive: true, force: true } ); - let count = 0; - for ( const relative of await listFiles( source ) ) { - if ( - ! relative.endsWith( '.php' ) || - [ ...EXCLUDED_SOURCE_DIRECTORIES ].some( - ( excluded ) => - relative === excluded || - relative.startsWith( `${ excluded }/` ) - ) - ) { - continue; - } - const target = path.join( destination, relative ); - await mkdir( path.dirname( target ), { recursive: true } ); - await copyFile( path.join( source, relative ), target ); - count++; - } - if ( count === 0 ) { - throw new Error( - 'The WordPress source tree contains no eligible PHP files.' - ); - } - return { source, destination, files: count }; -} - -export async function acquireRepositories( - repositories, - destination, - runImplementation = run -) { - await mkdir( destination, { recursive: true } ); - const acquired = {}; - for ( const [ name, dependency ] of Object.entries( repositories ) ) { - const target = path.join( destination, name ); - if ( ! ( await exists( path.join( target, '.git' ) ) ) ) { - await rm( target, { recursive: true, force: true } ); - await runImplementation( - 'git', - [ - 'clone', - '--quiet', - '--no-checkout', - `https://github.com/${ dependency.repository }.git`, - target, - ], - { label: `clone ${ dependency.repository }` } - ); - } - await runImplementation( - 'git', - [ 'fetch', '--quiet', '--depth=1', 'origin', dependency.commit ], - { cwd: target, label: `fetch ${ dependency.repository }` } - ); - await runImplementation( - 'git', - [ - '-c', - 'advice.detachedHead=false', - 'checkout', - '--quiet', - dependency.commit, - ], - { cwd: target, label: `checkout ${ dependency.repository }` } - ); - acquired[ name ] = path.join( target, dependency.path ); - } - return acquired; -} diff --git a/.github/scripts/docs-playground-preview/lib/final.mjs b/.github/scripts/docs-playground-preview/lib/final.mjs deleted file mode 100644 index a9816b8e5314c..0000000000000 --- a/.github/scripts/docs-playground-preview/lib/final.mjs +++ /dev/null @@ -1,153 +0,0 @@ -import { copyFile, mkdir, rm, stat, writeFile } from 'node:fs/promises'; -import { fileURLToPath } from 'node:url'; -import path from 'node:path'; - -import { sha256File } from './files.mjs'; -import { buildSnapshot } from './playground.mjs'; -import { renderRuntimePlugin } from './runtime.mjs'; - -const LIBRARY_ROOT = path.dirname( fileURLToPath( import.meta.url ) ); -const PHP_ROOT = path.resolve( LIBRARY_ROOT, '../php' ); - -function bundled( resourcePath ) { - return { resource: 'bundled', path: resourcePath }; -} - -export function createFinalBlueprint( inputs ) { - return { - $schema: inputs.dependencies.playground.blueprintSchema, - meta: { - title: 'WordPress Core Code Reference preview', - author: 'WordPress', - description: - 'Complete Core Code Reference imported into the cached site.', - }, - preferredVersions: { - php: inputs.dependencies.playground.phpVersion, - wp: inputs.wordpress.version, - }, - landingPage: '/reference/', - login: false, - features: { networking: false }, - extraLibraries: [ 'wp-cli' ], - steps: [ - { - step: 'unzip', - zipFile: bundled( 'base.zip' ), - extractToPath: '/', - }, - { - step: 'mkdir', - path: '/tmp/docs-preview-version', - }, - { - step: 'mkdir', - path: '/tmp/docs-preview-version/wp-includes', - }, - { - step: 'writeFile', - path: '/tmp/reference.json', - data: bundled( 'reference.json' ), - }, - { - step: 'writeFile', - path: '/tmp/docs-preview-version/wp-includes/version.php', - data: bundled( 'safe-version.php' ), - }, - { - step: 'wp-cli', - command: - 'wp parser import /tmp/reference.json --quick --user=1', - }, - { - step: 'writeFile', - path: '/tmp/docs-preview-complete-import.php', - data: bundled( 'complete-import.php' ), - }, - { - step: 'wp-cli', - command: 'wp eval-file /tmp/docs-preview-complete-import.php', - }, - { - step: 'rmdir', - path: '/wordpress/wp-content/plugins/phpdoc-parser', - }, - { - step: 'writeFile', - path: '/wordpress/wp-content/mu-plugins/001-docs-preview-runtime.php', - data: bundled( 'runtime.php' ), - }, - { - step: 'defineWpConfigConsts', - method: 'rewrite-wp-config', - consts: { - DISABLE_WP_CRON: true, - AUTOMATIC_UPDATER_DISABLED: true, - WP_AUTO_UPDATE_CORE: false, - DISALLOW_FILE_MODS: true, - }, - }, - ], - }; -} - -export async function packageFinalSnapshot( inputs, options ) { - const work = path.resolve( options.workDirectory ); - const output = path.resolve( options.output ); - await rm( work, { recursive: true, force: true } ); - await mkdir( work, { recursive: true } ); - await mkdir( path.dirname( output ), { recursive: true } ); - await copyFile( - path.join( inputs.cacheDirectory, 'base.zip' ), - path.join( work, 'base.zip' ) - ); - await copyFile( - options.referenceJson, - path.join( work, 'reference.json' ) - ); - await copyFile( - path.join( PHP_ROOT, 'complete-import.php' ), - path.join( work, 'complete-import.php' ) - ); - await copyFile( - path.join( PHP_ROOT, 'safe-version.php' ), - path.join( work, 'safe-version.php' ) - ); - await writeFile( - path.join( work, 'runtime.php' ), - renderRuntimePlugin( options.provenance ) - ); - const blueprint = path.join( work, 'final-blueprint.json' ); - await writeFile( - blueprint, - `${ JSON.stringify( createFinalBlueprint( inputs ), null, 2 ) }\n` - ); - await ( options.buildSnapshotImplementation || buildSnapshot )( - options.playgroundCli, - { - php: inputs.dependencies.playground.phpVersion, - wp: inputs.wordpress.version, - blueprint, - 'blueprint-may-read-adjacent-files': true, - mount: [ - { - hostPath: path.resolve( options.stagedSource ), - vfsPath: '/tmp/docs-preview-source', - }, - ], - outfile: output, - verbosity: 'normal', - } - ); - const snapshot = await stat( output ); - if ( snapshot.size > inputs.dependencies.limits.snapshotBytes ) { - throw new Error( - `Snapshot exceeds 100 MiB: ${ snapshot.size } > ${ inputs.dependencies.limits.snapshotBytes }.` - ); - } - return { - filename: path.basename( output ), - bytes: snapshot.size, - sha256: await sha256File( output ), - }; -} diff --git a/.github/scripts/docs-playground-preview/lib/github.mjs b/.github/scripts/docs-playground-preview/lib/github.mjs index f4078a40fb1eb..7356447591e5d 100644 --- a/.github/scripts/docs-playground-preview/lib/github.mjs +++ b/.github/scripts/docs-playground-preview/lib/github.mjs @@ -1,63 +1,162 @@ -import { COMMENT_MARKER, RELEASE_TAG } from './publication.mjs'; +import { readFile } from 'node:fs/promises'; + +import { COMMENT_MARKER, RELEASE_TAG } from './preview.mjs'; + +// A stalled connection without a deadline runs until the job timeout kills it, +// which leaves a preview reported as still building and no useful log. +const REQUEST_TIMEOUT_MS = 30_000; + +// Snapshots reach 100 MiB and travel through a third-party proxy, so transfers +// need a deadline far longer than an API call yet shorter than any job. +const TRANSFER_TIMEOUT_MS = 300_000; const API = 'https://api.github.com'; const UPLOADS = 'https://uploads.github.com'; +const RETRY_ATTEMPTS = 3; +const RETRY_BASE_MS = 1000; const BUILD_WORKFLOW = 'docs-playground-preview-build.yml'; const BUILD_JOB = 'Build Code Reference snapshot'; -function query( values ) { - const parameters = new URLSearchParams(); - for ( const [ name, value ] of Object.entries( values ) ) { - if ( value !== undefined && value !== null ) { - parameters.set( name, String( value ) ); - } +/** + * The preview publishes from one repository, plus the staging repository that + * opts in with the DOCS_PREVIEW_STAGING variable. Every trusted mutation asks + * here first; the workflow jobs carry the same condition in their `if:`. + * + * @param {string | undefined} repository + * @param {string} [stagingValue] + */ +export function assertDeploymentEnabled( repository, stagingValue ) { + const enabled = + repository === 'WordPress/wordpress-develop' || + ( repository === 'sirreal/wordpress-develop' && + stagingValue === 'true' ); + if ( ! enabled ) { + throw new Error( 'The docs preview is disabled in this repository.' ); } - return parameters.toString(); +} + +/** + * The event payload the runner wrote for this job. + */ +export async function readWorkflowEvent() { + const eventPath = process.env.GITHUB_EVENT_PATH; + if ( ! eventPath ) { + throw new Error( 'GITHUB_EVENT_PATH is required.' ); + } + return JSON.parse( await readFile( eventPath, 'utf8' ) ); +} + +/** + * @param {Record} values + */ +function query( values ) { + return new URLSearchParams( + Object.entries( values ).map( ( [ name, value ] ) => [ + name, + String( value ), + ] ) + ).toString(); } export class GitHubApi { + /** + * @param {string | undefined} repository + * @param {string | undefined} token + * @param {(...args: any[]) => Promise} [fetchImplementation] + */ constructor( repository, token, fetchImplementation = globalThis.fetch ) { this.repository = repository; this.token = token; this.fetch = fetchImplementation; } + /** + * @param {string} url + * @param {Record} [options] + */ async request( url, options = {} ) { - const headers = { - Accept: 'application/vnd.github+json', - Authorization: `Bearer ${ this.token }`, - 'X-GitHub-Api-Version': '2022-11-28', - ...options.headers, - }; - const response = await this.fetch( - url.startsWith( 'https://' ) ? url : `${ API }${ url }`, - { - ...options, - headers, - body: - options.json === undefined - ? options.body - : JSON.stringify( options.json ), + const { + method = 'GET', + body, + json, + contentType, + allowNotFound = false, + anonymous = false, + timeoutMs = REQUEST_TIMEOUT_MS, + } = options; + const headers = anonymous + ? undefined + : { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${ this.token }`, + 'X-GitHub-Api-Version': '2022-11-28', + ...( contentType && { 'Content-Type': contentType } ), + }; + const target = url.startsWith( 'https://' ) ? url : `${ API }${ url }`; + // Only reads repeat. Repeating a mutation could post a second comment + // or a second upload for an attempt the server had already applied. + const repeatable = method === 'GET'; + for ( let attempt = 1; ; attempt++ ) { + const retrying = repeatable && attempt < RETRY_ATTEMPTS; + let response; + try { + response = await this.fetch( target, { + method, + headers, + signal: AbortSignal.timeout( timeoutMs ), + body: json === undefined ? body : JSON.stringify( json ), + } ); + } catch ( error ) { + if ( ! retrying ) { + throw error; + } + await this.backoff( attempt ); + continue; } - ); - if ( options.allowNotFound && response.status === 404 ) { - return null; - } - if ( ! response.ok ) { - const detail = await response.text(); - throw new Error( - `GitHub API returned HTTP ${ - response.status - } for ${ url }: ${ detail.slice( 0, 300 ) }` - ); - } - if ( response.status === 204 ) { - return null; + if ( allowNotFound && response.status === 404 ) { + return null; + } + if ( ! response.ok ) { + if ( + retrying && + ( response.status === 429 || response.status >= 500 ) + ) { + await this.backoff( attempt ); + continue; + } + const detail = await response.text(); + throw Object.assign( + new Error( + `Request for ${ url } returned HTTP ${ + response.status + }: ${ detail.slice( 0, 300 ) }` + ), + { status: response.status } + ); + } + return response.status === 204 ? null : response.json(); } - return response.json(); } + /** + * Spreads repeated attempts so that concurrent jobs meeting the same + * outage do not return in lockstep. + * + * @param {number} attempt + */ + async backoff( attempt ) { + const delay = + RETRY_BASE_MS * 2 ** ( attempt - 1 ) + + Math.random() * RETRY_BASE_MS; + await new Promise( ( resolve ) => setTimeout( resolve, delay ) ); + } + + /** + * @param {string} path + * @param {string | null} [field] + */ async pages( path, field = null ) { + /** @type {any[]} */ const values = []; for ( let page = 1; ; page++ ) { const separator = path.includes( '?' ) ? '&' : '?'; @@ -72,124 +171,128 @@ export class GitHubApi { } } + /** + * Release assets are public and their download URL redirects to a storage + * host, so this read deliberately carries no credentials. + * + * @param {Record} asset + */ + readAssetJson( asset ) { + return this.request( asset.browser_download_url, { anonymous: true } ); + } + + /** + * @param {number} runId + */ getRun( runId ) { return this.request( `/repos/${ this.repository }/actions/runs/${ runId }` ); } - async getTrunkHeadSha() { - const reference = await this.request( - `/repos/${ this.repository }/git/ref/heads/trunk` + /** + * @param {number} pullRequestNumber + */ + getPullRequest( pullRequestNumber ) { + return this.request( + `/repos/${ this.repository }/pulls/${ pullRequestNumber }` ); - return reference.object?.sha || null; - } - - getPullRequest( number ) { - return this.request( `/repos/${ this.repository }/pulls/${ number }` ); } + /** + * The workflow_run event omits the pull request for a fork head, so it is + * found from the head repository and branch that the run recorded. + * + * @param {Record} run + */ async findPullRequestForRun( run ) { const owner = run.head_repository?.owner?.login; if ( ! owner || ! run.head_branch ) { - throw new Error( 'Workflow run has no fork head identity.' ); + return null; } - const pulls = await this.pages( + const pulls = await this.request( `/repos/${ this.repository }/pulls?${ query( { state: 'open', base: 'trunk', head: `${ owner }:${ run.head_branch }`, + per_page: 100, } ) }` ); - const matches = pulls.filter( - ( pullRequest ) => - pullRequest.head.sha === run.head_sha && - pullRequest.head.repo?.full_name === - run.head_repository.full_name + return ( + pulls.find( + ( /** @type {Record} */ pullRequest ) => + pullRequest.head.sha === run.head_sha && + pullRequest.head.repo?.full_name === + run.head_repository.full_name + ) || null ); - if ( matches.length === 0 ) { - return null; - } - if ( matches.length !== 1 ) { - throw new Error( - `Expected one pull request for the workflow run; found ${ matches.length }.` - ); - } - return matches[ 0 ]; } - async findLatestPreviewRun( run ) { - const runs = await this.pages( + /** + * @param {Record} search + */ + async listBuildRuns( search ) { + const response = await this.request( `/repos/${ this.repository - }/actions/workflows/${ BUILD_WORKFLOW }/runs?${ query( { - event: 'pull_request', - head_sha: run.head_sha, - } ) }`, - 'workflow_runs' + }/actions/workflows/${ BUILD_WORKFLOW }/runs?${ query( search ) }` ); + return response.workflow_runs; + } + + /** + * The newest build run for a pull request head. Adding an unrelated label + * starts a run whose build job skips; that never supersedes a real build. + * + * @param {Record} run + */ + async latestPullRequestBuildRun( run ) { + const runs = await this.listBuildRuns( { + event: 'pull_request', + head_sha: run.head_sha, + per_page: 100, + } ); const matching = runs .filter( - ( candidate ) => - candidate.head_sha === run.head_sha && + ( /** @type {Record} */ candidate ) => candidate.head_branch === run.head_branch && candidate.head_repository?.full_name === run.head_repository?.full_name ) - .sort( ( left, right ) => right.id - left.id ); + .sort( + ( + /** @type {Record} */ left, + /** @type {Record} */ right + ) => right.id - left.id + ); for ( const candidate of matching ) { - const current = await this.getRun( candidate.id ); - if ( ! ( await this.isSkippedPreviewBuild( current ) ) ) { - return current; + if ( ! ( await this.isSkippedBuild( candidate ) ) ) { + return candidate; } } return null; } - async latestPreviewRun( run ) { - const latest = await this.findLatestPreviewRun( run ); - if ( latest ) { - return latest; - } - throw new Error( 'No current docs preview build matches this head.' ); - } - - async latestTrunkPreviewRun() { - const runs = await this.pages( - `/repos/${ - this.repository - }/actions/workflows/${ BUILD_WORKFLOW }/runs?${ query( { - event: 'push', - branch: 'trunk', - } ) }`, - 'workflow_runs' - ); - const latest = runs - .filter( - ( run ) => - run.event === 'push' && - run.head_branch === 'trunk' && - run.head_repository?.full_name === this.repository - ) - .sort( ( left, right ) => right.id - left.id )[ 0 ]; - if ( ! latest ) { - throw new Error( 'No current trunk docs preview build exists.' ); - } - return this.getRun( latest.id ); + async latestTrunkBuildRun() { + // The API answers newest first, so the first run is the current one. + const runs = await this.listBuildRuns( { + event: 'push', + branch: 'trunk', + per_page: 1, + } ); + return runs[ 0 ] || null; } - async isSkippedPreviewBuild( run ) { + /** + * @param {Record} run + */ + async isSkippedBuild( run ) { const jobs = await this.pages( `/repos/${ this.repository }/actions/runs/${ run.id }/attempts/${ run.run_attempt }/jobs`, 'jobs' ); - const matches = jobs.filter( ( job ) => job.name === BUILD_JOB ); - if ( matches.length !== 1 ) { - throw new Error( - `Expected one Code Reference build job; found ${ matches.length }.` - ); - } - return matches[ 0 ].conclusion === 'skipped'; + const build = jobs.find( ( job ) => job.name === BUILD_JOB ); + return ! build || build.conclusion === 'skipped'; } getRelease() { @@ -211,28 +314,68 @@ export class GitHubApi { } ); } + /** + * @param {number} releaseId + */ listReleaseAssets( releaseId ) { return this.pages( `/repos/${ this.repository }/releases/${ releaseId }/assets` ); } - uploadReleaseAsset( releaseId, name, bytes, contentType ) { - return this.request( - `${ UPLOADS }/repos/${ - this.repository - }/releases/${ releaseId }/assets?${ query( { name } ) }`, - { - method: 'POST', - headers: { - Accept: 'application/vnd.github+json', - 'Content-Type': contentType, - }, - body: bytes, + /** + * @param {number} releaseId + * @param {string} name + * @param {Uint8Array} bytes + * @param {string} contentType + */ + async uploadReleaseAsset( releaseId, name, bytes, contentType ) { + const upload = () => + this.request( + `${ UPLOADS }/repos/${ + this.repository + }/releases/${ releaseId }/assets?${ query( { name } ) }`, + { + method: 'POST', + contentType, + body: bytes, + timeoutMs: TRANSFER_TIMEOUT_MS, + } + ); + try { + return await upload(); + } catch ( error ) { + // Re-running the publish workflow for one build attempt meets the + // asset that the earlier run uploaded. Replace it and continue. + if ( + ! ( error instanceof Error ) || + /** @type {any} */ ( error ).status !== 422 + ) { + throw error; + } + const assets = await this.listReleaseAssets( releaseId ); + const stale = assets.find( ( asset ) => asset.name === name ); + if ( ! stale ) { + throw error; } + await this.deleteReleaseAsset( stale.id ); + return upload(); + } + } + + /** + * @param {number} assetId + */ + deleteReleaseAsset( assetId ) { + return this.request( + `/repos/${ this.repository }/releases/assets/${ assetId }`, + { method: 'DELETE' } ); } + /** + * @param {string} reference + */ getGitReference( reference ) { return this.request( `/repos/${ this.repository }/git/ref/${ reference }`, @@ -240,30 +383,25 @@ export class GitHubApi { ); } - createGitBlob( content ) { - return this.request( `/repos/${ this.repository }/git/blobs`, { - method: 'POST', - json: { content, encoding: 'utf-8' }, - } ); - } - - createGitTree( path, blobSha ) { + /** + * @param {string} path + * @param {string} content + */ + createGitTree( path, content ) { return this.request( `/repos/${ this.repository }/git/trees`, { method: 'POST', json: { - tree: [ - { - path, - mode: '100644', - type: 'blob', - sha: blobSha, - }, - ], + tree: [ { path, mode: '100644', type: 'blob', content } ], }, } ); } - createGitCommit( message, treeSha, parentSha = null ) { + /** + * @param {string} message + * @param {string} treeSha + * @param {string | null} parentSha + */ + createGitCommit( message, treeSha, parentSha ) { return this.request( `/repos/${ this.repository }/git/commits`, { method: 'POST', json: { @@ -274,27 +412,26 @@ export class GitHubApi { } ); } - createGitReference( reference, sha ) { - return this.request( `/repos/${ this.repository }/git/refs`, { - method: 'POST', - json: { ref: `refs/${ reference }`, sha }, - } ); - } - - updateGitReference( reference, sha ) { - return this.request( - `/repos/${ this.repository }/git/refs/${ reference }`, - { method: 'PATCH', json: { sha, force: true } } - ); - } - - deleteReleaseAsset( assetId ) { - return this.request( - `/repos/${ this.repository }/releases/assets/${ assetId }`, - { method: 'DELETE' } - ); + /** + * @param {string} reference + * @param {string} sha + * @param {boolean} exists + */ + setGitReference( reference, sha, exists ) { + return exists + ? this.request( + `/repos/${ this.repository }/git/refs/${ reference }`, + { method: 'PATCH', json: { sha, force: true } } + ) + : this.request( `/repos/${ this.repository }/git/refs`, { + method: 'POST', + json: { ref: `refs/${ reference }`, sha }, + } ); } + /** + * @param {string} ref + */ listActionCaches( ref ) { return this.pages( `/repos/${ this.repository }/actions/caches?${ query( { ref } ) }`, @@ -302,6 +439,9 @@ export class GitHubApi { ); } + /** + * @param {number} cacheId + */ deleteActionCache( cacheId ) { return this.request( `/repos/${ this.repository }/actions/caches/${ cacheId }`, @@ -309,6 +449,9 @@ export class GitHubApi { ); } + /** + * @param {number} pullRequestNumber + */ async findPreviewComment( pullRequestNumber ) { const comments = await this.pages( `/repos/${ this.repository }/issues/${ pullRequestNumber }/comments` @@ -322,6 +465,10 @@ export class GitHubApi { ); } + /** + * @param {number} pullRequestNumber + * @param {string} body + */ createComment( pullRequestNumber, body ) { return this.request( `/repos/${ this.repository }/issues/${ pullRequestNumber }/comments`, @@ -329,6 +476,10 @@ export class GitHubApi { ); } + /** + * @param {number} commentId + * @param {string} body + */ updateComment( commentId, body ) { return this.request( `/repos/${ this.repository }/issues/comments/${ commentId }`, @@ -336,6 +487,9 @@ export class GitHubApi { ); } + /** + * @param {number} pullRequestNumber + */ removeLabel( pullRequestNumber ) { return this.request( `/repos/${ this.repository }/issues/${ pullRequestNumber }/labels/docs-preview`, diff --git a/.github/scripts/docs-playground-preview/lib/inputs.mjs b/.github/scripts/docs-playground-preview/lib/inputs.mjs index d7c6c7460dd62..d1bb365cf7f33 100644 --- a/.github/scripts/docs-playground-preview/lib/inputs.mjs +++ b/.github/scripts/docs-playground-preview/lib/inputs.mjs @@ -1,68 +1,154 @@ import { createHash } from 'node:crypto'; -import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises'; import path from 'node:path'; -import { - loadDependencies, - makeBaseCacheKey, - resolveWordPressBeta, -} from './config.mjs'; -import { digestTree } from './files.mjs'; +const DEPENDENCY_MANIFEST = '.github/docs-playground-preview/dependencies.json'; +const VERSION_API = + 'https://api.wordpress.org/core/version-check/1.7/?channel=beta'; +const BETA_VERSION = /^\d+\.\d+(?:\.\d+)?-(?:beta\d+|RC\d+)$/; +const STABLE_VERSION = /^\d+\.\d+(?:\.\d+)?$/; +// Anything under these two directories can change what the cached base +// contains, so the cache key covers them whole instead of naming the files that +// shape it. A file that turns out not to shape it costs one rebuild; a file +// left out of such a list would be restored from a stale cache instead. +const BASE_INPUT_ROOTS = [ + '.github/docs-playground-preview', + '.github/scripts/docs-playground-preview', +]; + +// Installed packages are already pinned by package-lock.json, the type-check +// output is not an input, and the tests never run during a build. +const BASE_INPUT_SKIPPED = new Set( [ + 'node_modules', + 'test', + 'tsconfig.tsbuildinfo', +] ); + +/** + * Hashes every file below `directory` under its path relative to `root`, in a + * fixed order so that two checkouts of the same content agree. + * + * @param {import('node:crypto').Hash} digest + * @param {string} root + * @param {string} directory + */ +async function digestTree( digest, root, directory ) { + const entries = await readdir( directory, { withFileTypes: true } ); + for ( const entry of entries.sort( ( left, right ) => + left.name < right.name ? -1 : 1 + ) ) { + if ( BASE_INPUT_SKIPPED.has( entry.name ) ) { + continue; + } + const filename = path.join( directory, entry.name ); + if ( entry.isDirectory() ) { + await digestTree( digest, root, filename ); + } else if ( entry.isFile() ) { + digest.update( path.relative( root, filename ) ); + digest.update( await readFile( filename ) ); + } + } +} + +/** + * The preview runs the WordPress beta channel. Between releases that channel + * offers no beta or RC build, so the preview follows the current stable release + * until the next one appears. + * + * @param {(...args: any[]) => Promise} [fetchImplementation] + */ +export async function resolveWordPressBeta( + fetchImplementation = globalThis.fetch +) { + const response = await fetchImplementation( VERSION_API, { + headers: { Accept: 'application/json' }, + // A stalled connection without a deadline runs until the job timeout + // kills it, leaving a preview reported as still building. + signal: AbortSignal.timeout( 30_000 ), + } ); + if ( ! response.ok ) { + throw new Error( + `WordPress beta API returned HTTP ${ response.status }.` + ); + } + const body = await response.json(); + /** @param {RegExp} versionPattern */ + const offer = ( versionPattern ) => + body?.offers?.find( + /** @param {Record} candidate */ + ( candidate ) => versionPattern.test( candidate?.version ) + ); + const beta = offer( BETA_VERSION ); + if ( beta ) { + return { channel: 'beta', version: beta.version }; + } + const stable = offer( STABLE_VERSION ); + if ( ! stable ) { + throw new Error( + 'The WordPress beta API returned no concrete beta or stable build.' + ); + } + process.stderr.write( + `::notice::The WordPress beta channel offers no beta or RC build; tracking the stable release ${ stable.version } until the next beta appears.\n` + ); + return { channel: 'stable', version: stable.version }; +} + +/** + * Reads the pinned dependencies and resolves what the rest of the build needs, + * including the cache key of the invariant base: two builds share a cached base + * only when every input that shapes it agrees. + * + * @param {{repositoryRoot: string, cacheRoot: string, platform: string, architecture: string, runnerImage: string, fetch?: (...args: any[]) => Promise}} options + */ export async function resolveBuildInputs( options ) { const repositoryRoot = path.resolve( options.repositoryRoot ); - const configRoot = path.join( - repositoryRoot, - '.github/docs-playground-preview' - ); - const scriptsRoot = path.join( - repositoryRoot, - '.github/scripts/docs-playground-preview' - ); - const { dependencies, digest: dependencyDigest } = await loadDependencies( - path.join( configRoot, 'dependencies.json' ) + const manifest = await readFile( + path.join( repositoryRoot, DEPENDENCY_MANIFEST ) ); - const wordpress = await resolveWordPressBeta( options.fetchImplementation ); - const configDigest = await digestTree( - configRoot, - ( relative ) => ! relative.startsWith( 'node_modules/' ) - ); - const scriptsDigest = await digestTree( - scriptsRoot, - ( relative ) => ! relative.startsWith( 'test/' ) - ); - const harnessDigest = await digestTreePair( configDigest, scriptsDigest ); - const cacheInputs = { - cacheSchemaVersion: dependencies.cacheSchemaVersion, - platform: options.platform, - architecture: options.architecture, - runnerImage: options.runnerImage, - phpVersion: dependencies.playground.phpVersion, - wordpressVersion: wordpress.version, - dependencyDigest, - harnessDigest, - }; - const cacheKey = makeBaseCacheKey( cacheInputs ); + const dependencies = JSON.parse( manifest.toString( 'utf8' ) ); + const wordpress = await resolveWordPressBeta( options.fetch ); + const files = createHash( 'sha256' ); + for ( const relative of BASE_INPUT_ROOTS ) { + await digestTree( + files, + repositoryRoot, + path.join( repositoryRoot, relative ) + ); + } + // The PHP version and the pinned dependencies are files under those roots, + // so only what the build reads from outside the checkout is named here. + const identity = createHash( 'sha256' ) + .update( + JSON.stringify( { + platform: options.platform, + architecture: options.architecture, + runnerImage: options.runnerImage, + wordpressVersion: wordpress.version, + filesDigest: files.digest( 'hex' ), + } ) + ) + .digest( 'hex' ); + const cacheKey = `docs-preview-base-v${ dependencies.cacheSchemaVersion }-${ identity }`; return { - schemaVersion: 1, cacheKey, cacheDirectory: path.join( path.resolve( options.cacheRoot ), cacheKey ), - cacheInputs, + dependencyDigest: createHash( 'sha256' ) + .update( manifest ) + .digest( 'hex' ), dependencies, wordpress, }; } -async function digestTreePair( first, second ) { - return createHash( 'sha256' ) - .update( first ) - .update( second ) - .digest( 'hex' ); -} - +/** + * @param {Record} inputs + * @param {string} filename + */ export async function writeResolvedInputs( inputs, filename ) { await mkdir( path.dirname( filename ), { recursive: true } ); await writeFile( filename, `${ JSON.stringify( inputs, null, 2 ) }\n` ); @@ -75,6 +161,9 @@ export async function writeResolvedInputs( inputs, filename ) { } } +/** + * @param {string} filename + */ export async function readResolvedInputs( filename ) { return JSON.parse( await readFile( filename, 'utf8' ) ); } diff --git a/.github/scripts/docs-playground-preview/lib/parser.mjs b/.github/scripts/docs-playground-preview/lib/parser.mjs index 5f1f346c2e4e3..607c2a7546597 100644 --- a/.github/scripts/docs-playground-preview/lib/parser.mjs +++ b/.github/scripts/docs-playground-preview/lib/parser.mjs @@ -1,40 +1,78 @@ -import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import { + copyFile, + mkdir, + readdir, + readFile, + rm, + writeFile, +} from 'node:fs/promises'; +import { existsSync } from 'node:fs'; import path from 'node:path'; -import { stageCorePhp } from './files.mjs'; import { run } from './process.mjs'; -const HOOK_TYPE = /^(?:action|filter)(?:_(?:deprecated|reference))?$/; +// Where the importer reads source from inside the Playground filesystem. const IMPORT_SOURCE_ROOT = '/tmp/docs-preview-source'; const SAFE_VERSION_ROOT = '/tmp/docs-preview-version'; -function hooksIn( value ) { - return Array.isArray( value?.hooks ) ? value.hooks : []; -} +// Bundled plugins and themes are not part of the Core Code Reference. +const EXCLUDED_SOURCE = [ 'wp-content/plugins', 'wp-content/themes' ]; -function countHooks( hooks, counts ) { - for ( const hook of hooks ) { +/** + * Copies the Core PHP the parser reads into its own tree. Only these files are + * ever exposed to the parser and the importer, and none of them is executed. + * + * @param {string} candidate + * @param {string} destination + */ +export async function stageCorePhp( candidate, destination ) { + const root = path.resolve( candidate ); + const source = existsSync( + path.join( root, 'src/wp-includes/version.php' ) + ) + ? path.join( root, 'src' ) + : root; + if ( ! existsSync( path.join( source, 'wp-includes/version.php' ) ) ) { + throw new Error( `No WordPress source tree found beneath ${ root }.` ); + } + await rm( destination, { recursive: true, force: true } ); + // Symbolic links are not files here, so none of them is ever staged. + const entries = await readdir( source, { + recursive: true, + withFileTypes: true, + } ); + let count = 0; + for ( const entry of entries ) { + const relative = path.relative( + source, + path.join( entry.parentPath, entry.name ) + ); if ( - ! hook || - typeof hook !== 'object' || - ! HOOK_TYPE.test( hook.type ) + ! entry.isFile() || + ! relative.endsWith( '.php' ) || + EXCLUDED_SOURCE.some( ( excluded ) => + relative.startsWith( `${ excluded }/` ) + ) ) { - throw new Error( - 'Parser JSON contains a hook with an invalid type.' - ); - } - if ( hook.type.startsWith( 'action' ) ) { - counts.hooks++; - } else { - counts.filters++; + continue; } + const target = path.join( destination, relative ); + await mkdir( path.dirname( target ), { recursive: true } ); + await copyFile( path.join( source, relative ), target ); + count++; } + return count; } +/** + * Counts what the parser produced and points every record at the source tree + * the importer will read it from. + * + * @param {any[]} records + * @param {Record} minimumSymbols + */ export function inspectParserRecords( records, minimumSymbols ) { - if ( ! Array.isArray( records ) ) { - throw new Error( 'Parser JSON must be an array.' ); - } + /** @type {Record} */ const counts = { classes: 0, methods: 0, @@ -42,62 +80,58 @@ export function inspectParserRecords( records, minimumSymbols ) { hooks: 0, filters: 0, }; - for ( const record of records ) { - if ( - ! record || - typeof record !== 'object' || - typeof record.path !== 'string' - ) { - throw new Error( 'Parser JSON contains an invalid file record.' ); - } - if ( - record.path.startsWith( 'wp-content/plugins/' ) || - record.path.startsWith( 'wp-content/themes/' ) - ) { - throw new Error( - `Parser JSON contains excluded source: ${ record.path }.` - ); + /** @param {any[]} [hooks] */ + const countHooks = ( hooks ) => { + for ( const hook of hooks || [] ) { + if ( String( hook.type ).startsWith( 'action' ) ) { + counts.hooks++; + } else { + counts.filters++; + } } - const functions = Array.isArray( record.functions ) - ? record.functions - : []; - const classes = Array.isArray( record.classes ) ? record.classes : []; + }; + for ( const record of records ) { + const functions = record.functions || []; + const classes = record.classes || []; counts.functions += functions.length; counts.classes += classes.length; + countHooks( record.hooks ); for ( const item of functions ) { - countHooks( hooksIn( item ), counts ); + countHooks( item.hooks ); } for ( const item of classes ) { - const methods = Array.isArray( item?.methods ) ? item.methods : []; + const methods = item.methods || []; counts.methods += methods.length; for ( const method of methods ) { - countHooks( hooksIn( method ), counts ); + countHooks( method.hooks ); } } - countHooks( hooksIn( record ), counts ); + // The importer includes the file it records, so the version file has to + // resolve to the empty stub instead of the pull-request source. record.root = record.path === 'wp-includes/version.php' ? SAFE_VERSION_ROOT : IMPORT_SOURCE_ROOT; } - const failures = - records.length === 0 ? [ 'Parser produced no file records.' ] : []; - failures.push( - ...Object.entries( minimumSymbols ) - .filter( ( [ type, minimum ] ) => counts[ type ] < minimum ) - .map( - ( [ type, minimum ] ) => - `Parser produced ${ counts[ type ] } ${ type }; expected at least ${ minimum }.` - ) - ); - return { records: records.length, counts, failures }; + const failures = Object.entries( minimumSymbols ) + .filter( ( [ type, minimum ] ) => counts[ type ] < minimum ) + .map( + ( [ type, minimum ] ) => + `Parser produced ${ counts[ type ] } ${ type }; expected at least ${ minimum }.` + ); + return { counts, failures }; } +/** + * Parses the pull-request Core source into the JSON the importer consumes. + * + * @param {{source: string, stagedSource: string, parser: string, output: string, minimumSymbols: Record}} options + */ export async function generateParserJson( options ) { - const runImplementation = options.runImplementation || run; - const staged = await stageCorePhp( options.source, options.stagedSource ); + const files = await stageCorePhp( options.source, options.stagedSource ); + process.stdout.write( `Staged ${ files } Core PHP files for parsing.\n` ); await mkdir( path.dirname( options.output ), { recursive: true } ); - await runImplementation( + await run( 'php', [ '-d', @@ -108,14 +142,18 @@ export async function generateParserJson( options ) { '-o', options.output, ], - { - capture: Boolean( options.logFile ), - logFile: options.logFile, - label: 'generate Code Reference parser JSON', - } + { label: 'generate Code Reference parser JSON' } ); const records = JSON.parse( await readFile( options.output, 'utf8' ) ); - const inspection = inspectParserRecords( records, options.minimumSymbols ); + const { counts, failures } = inspectParserRecords( + records, + options.minimumSymbols + ); await writeFile( options.output, `${ JSON.stringify( records ) }\n` ); - return { ...inspection, sourceFiles: staged.files }; + process.stdout.write( + `Parsed ${ Object.entries( counts ) + .map( ( [ type, total ] ) => `${ total } ${ type }` ) + .join( ', ' ) }.\n` + ); + return { failures }; } diff --git a/.github/scripts/docs-playground-preview/lib/playground.mjs b/.github/scripts/docs-playground-preview/lib/playground.mjs deleted file mode 100644 index 0fbc15e8231d8..0000000000000 --- a/.github/scripts/docs-playground-preview/lib/playground.mjs +++ /dev/null @@ -1,22 +0,0 @@ -import path from 'node:path'; -import { pathToFileURL } from 'node:url'; - -export const PLAYGROUND_BUILD_WORKERS = 1; -export const PLAYGROUND_SERVER_WORKERS = 6; - -export function createSnapshotArguments( options ) { - return { - ...options, - command: 'build-snapshot', - workers: PLAYGROUND_BUILD_WORKERS, - }; -} - -export async function buildSnapshot( playgroundCli, options ) { - const modulePath = path.resolve( - path.dirname( playgroundCli ), - '../@wp-playground/cli/index.js' - ); - const { runCLI } = await import( pathToFileURL( modulePath ) ); - await runCLI( createSnapshotArguments( options ) ); -} diff --git a/.github/scripts/docs-playground-preview/lib/preview.mjs b/.github/scripts/docs-playground-preview/lib/preview.mjs new file mode 100644 index 0000000000000..4624580dddfd1 --- /dev/null +++ b/.github/scripts/docs-playground-preview/lib/preview.mjs @@ -0,0 +1,256 @@ +// Names, URLs and metadata shared across the preview pipeline. A "preview" is +// one Playground snapshot published as a release asset next to a JSON metadata +// asset of the same stem. + +export const RELEASE_TAG = 'code-reference-playground-preview'; +export const COMMENT_MARKER = ''; +export const TRUNK_POINTER_ASSET = 'code-reference-trunk.json'; +export const TRUNK_POINTER_REF = 'heads/docs-preview-code-reference'; + +// The build writes these two files into the handoff directory that the publish +// job downloads, and the publisher reads nothing else out of it. The published +// asset is renamed after the workflow run, so the build never names it. +export const HANDOFF_METADATA = 'build.json'; +export const HANDOFF_SNAPSHOT = 'snapshot.zip'; + +// What the build writes as `handoffType` when it reuses a published snapshot +// for the same commit instead of building one. +export const HANDOFF_REUSE = 'reuse'; + +const SNAPSHOT_BYTES_LIMIT = 104857600; +const PLAYGROUND_ORIGIN = 'https://playground.wordpress.net'; +const TRUNK_POINTER_BRANCH = TRUNK_POINTER_REF.slice( 'heads/'.length ); +const CORS_PROXY = 'https://wordpress-playground-cors-proxy.net/'; + +/** + * Every asset is named after the run that produced it, so one build cannot + * write over the assets of another pull request or of trunk. Finding the assets + * of a pull request, or of one of its commits, means matching this prefix. + * + * @param {number | null} pullRequestNumber + * @param {string} [sourceSha] + */ +export function assetPrefix( pullRequestNumber, sourceSha ) { + const scope = + pullRequestNumber === null ? 'trunk' : `pr-${ pullRequestNumber }`; + return `code-reference-${ scope }-${ sourceSha ? `${ sourceSha }-` : '' }`; +} + +/** + * @param {Record} context + */ +export function snapshotAssetName( context ) { + return `${ assetPrefix( context.pullRequestNumber, context.sourceSha ) }${ + context.workflowRunId + }-${ context.workflowRunAttempt }.zip`; +} + +/** + * One limit checked twice: the build refuses to hand over an oversized + * snapshot, and the publisher refuses to upload one. + * + * @param {number} bytes + */ +export function assertSnapshotFits( bytes ) { + if ( bytes > SNAPSHOT_BYTES_LIMIT ) { + throw new Error( + `The snapshot is ${ bytes } bytes; the limit is ${ SNAPSHOT_BYTES_LIMIT }.` + ); + } +} + +/** + * @param {string} snapshotName + */ +export function metadataAssetName( snapshotName ) { + return `${ snapshotName.slice( 0, -'.zip'.length ) }.json`; +} + +/** + * The newest snapshot under this prefix that also has its metadata asset. The + * snapshot is uploaded first, so a run that stopped between the two uploads + * leaves a snapshot nothing should point at. + * + * @param {any[]} assets + * @param {string} prefix + * @returns {Record | null} + */ +export function findLatestSnapshot( assets, prefix ) { + return ( + assets + .filter( + ( asset ) => + asset.name.startsWith( prefix ) && + asset.name.endsWith( '.zip' ) && + assets.some( + ( sibling ) => + sibling.name === metadataAssetName( asset.name ) + ) + ) + .sort( ( left, right ) => + right.created_at.localeCompare( left.created_at ) + )[ 0 ] || null + ); +} + +/** + * Playground fetches assets from the browser, where github.com sends no CORS + * headers, so every published URL is read through the Playground proxy. + * + * @param {string} publicUrl + */ +function corsProxyUrl( publicUrl ) { + return `${ CORS_PROXY }${ publicUrl }`; +} + +/** + * @param {string} snapshotUrl + * @param {string} phpVersion + * @param {string} wordPressVersion + * @param {string} description + */ +function createLaunchBlueprint( + snapshotUrl, + phpVersion, + wordPressVersion, + description +) { + return { + meta: { + title: 'WordPress Core Code Reference preview', + author: 'WordPress', + description, + }, + preferredVersions: { php: phpVersion, wp: wordPressVersion }, + landingPage: '/reference/', + login: false, + features: { networking: false }, + steps: [ + { + step: 'unzip', + zipFile: { resource: 'url', url: corsProxyUrl( snapshotUrl ) }, + extractToPath: '/', + }, + ], + }; +} + +/** + * The repository README links this URL, so it must stay stable across trunk + * builds. The Blueprint behind it names the current trunk snapshot. + * + * @param {string} repository + */ +function trunkStableBlueprintUrl( repository ) { + return `https://raw.githubusercontent.com/${ repository }/${ TRUNK_POINTER_BRANCH }/${ TRUNK_POINTER_ASSET }`; +} + +/** + * @param {string} repository + */ +function trunkPlaygroundUrl( repository ) { + return `${ PLAYGROUND_ORIGIN }/?blueprint-url=${ encodeURIComponent( + corsProxyUrl( trunkStableBlueprintUrl( repository ) ) + ) }`; +} + +/** + * Identity comes from the workflow run, description from the build handoff, so + * a build cannot publish metadata that names a commit it did not build. + * + * @param {Record} handoff + * @param {Record} context + * @param {Record} snapshot + */ +export function createPublishedMetadata( handoff, context, snapshot ) { + const snapshotUrl = `https://github.com/${ context.repository }/releases/download/${ RELEASE_TAG }/${ snapshot.filename }`; + const blueprint = createLaunchBlueprint( + snapshotUrl, + handoff.phpVersion, + handoff.resolvedWordPressBeta?.version, + 'Complete Core Code Reference generated from an exact commit.' + ); + /** @type {Record} */ + const publication = { + publishedAt: new Date().toISOString(), + snapshotUrl, + playgroundUrl: `${ PLAYGROUND_ORIGIN }/?blueprint-url=data:application/json,${ encodeURIComponent( + JSON.stringify( blueprint ) + ) }`, + }; + if ( context.pullRequestNumber === null ) { + // Trunk metadata records the stable link that the repository README uses. + publication.stableBlueprintUrl = trunkStableBlueprintUrl( + context.repository + ); + publication.stablePlaygroundUrl = trunkPlaygroundUrl( + context.repository + ); + } + return { + schemaVersion: 1, + sourceRepository: context.sourceRepository, + pullRequestNumber: context.pullRequestNumber, + sourceSha: context.sourceSha, + workflowRunId: context.workflowRunId, + workflowRunAttempt: context.workflowRunAttempt, + runUrl: context.runUrl, + resolvedWordPressBeta: handoff.resolvedWordPressBeta, + phpVersion: handoff.phpVersion, + dependencyManifestDigest: handoff.dependencyManifestDigest, + generationTimestamp: handoff.generationTimestamp, + snapshotFilename: snapshot.filename, + snapshotBytes: snapshot.bytes, + snapshotSha256: snapshot.sha256, + publication, + }; +} + +/** + * @param {Record} published + */ +export function createTrunkBlueprint( published ) { + return createLaunchBlueprint( + published.publication.snapshotUrl, + published.phpVersion, + published.resolvedWordPressBeta?.version, + `Latest successful Core Code Reference from ${ published.sourceRepository }@${ published.sourceSha }. Generated ${ published.generationTimestamp }. ${ published.runUrl }` + ); +} + +/** + * The newest published preview for a pull request, used to keep a working link + * in the comment when the current attempt has nothing to show. + * + * @param {Record} api + * @param {number} pullRequestNumber + * @returns {Promise | null>} + */ +export async function findLatestPublishedPreview( api, pullRequestNumber ) { + const release = await api.getRelease(); + const assets = release ? await api.listReleaseAssets( release.id ) : []; + const snapshot = findLatestSnapshot( + assets, + assetPrefix( pullRequestNumber ) + ); + if ( ! snapshot ) { + return null; + } + // findLatestSnapshot only returns a snapshot whose metadata asset exists. + const metadata = assets.find( + ( /** @type {Record} */ asset ) => + asset.name === metadataAssetName( snapshot.name ) + ); + try { + return await api.readAssetJson( metadata ); + } catch ( error ) { + // A concurrent publication can delete these assets between the + // listing and the read; the caller then reports no preview. + process.stderr.write( + `::warning::Cannot read ${ metadata.name }: ${ + error instanceof Error ? error.message : String( error ) + }\n` + ); + return null; + } +} diff --git a/.github/scripts/docs-playground-preview/lib/process.mjs b/.github/scripts/docs-playground-preview/lib/process.mjs index b8c899a342052..bbaf3ee71eae0 100644 --- a/.github/scripts/docs-playground-preview/lib/process.mjs +++ b/.github/scripts/docs-playground-preview/lib/process.mjs @@ -1,20 +1,26 @@ import { spawn } from 'node:child_process'; -import { writeFile } from 'node:fs/promises'; +/** + * Runs a build tool and rejects with its output when it fails. + * + * @param {string} command + * @param {string[]} args + * @param {{capture?: boolean, cwd?: string, env?: NodeJS.ProcessEnv, label?: string}} [options] + */ export async function run( command, args, options = {} ) { - const capture = options.capture || Boolean( options.logFile ); - if ( ! options.quiet ) { - process.stdout.write( `$ ${ [ command, ...args ].join( ' ' ) }\n` ); - } - + process.stdout.write( `$ ${ [ command, ...args ].join( ' ' ) }\n` ); return new Promise( ( resolve, reject ) => { const child = spawn( command, args, { cwd: options.cwd, env: { ...process.env, ...options.env }, - stdio: capture ? [ 'ignore', 'pipe', 'pipe' ] : 'inherit', + stdio: options.capture ? [ 'ignore', 'pipe', 'pipe' ] : 'inherit', } ); let stdout = ''; let stderr = ''; + // Decoding per chunk would corrupt any character split across a pipe + // boundary, so the streams decode as a whole. + child.stdout?.setEncoding( 'utf8' ); + child.stderr?.setEncoding( 'utf8' ); child.stdout?.on( 'data', ( chunk ) => { stdout += chunk; } ); @@ -23,26 +29,20 @@ export async function run( command, args, options = {} ) { } ); child.once( 'error', reject ); child.once( 'close', ( code, signal ) => { - ( async () => { - if ( options.logFile ) { - await writeFile( - options.logFile, - `${ stdout }${ stderr }` - ); - } - const result = { code, signal, stdout, stderr }; - if ( code !== 0 && ! options.allowFailure ) { - const detail = `${ stdout }${ stderr }`.trim(); - throw new Error( - `${ options.label || command } failed${ - signal - ? ` with signal ${ signal }` - : ` with exit code ${ code }` - }${ detail ? `\n${ detail }` : '' }` - ); - } - return result; - } )().then( resolve, reject ); + if ( code === 0 ) { + resolve( { stdout } ); + return; + } + const detail = `${ stdout }${ stderr }`.trim(); + reject( + new Error( + `${ options.label || command } failed${ + signal + ? ` with signal ${ signal }` + : ` with exit code ${ code }` + }${ detail ? `\n${ detail }` : '' }` + ) + ); } ); } ); } diff --git a/.github/scripts/docs-playground-preview/lib/publication.mjs b/.github/scripts/docs-playground-preview/lib/publication.mjs deleted file mode 100644 index 24e89063c7501..0000000000000 --- a/.github/scripts/docs-playground-preview/lib/publication.mjs +++ /dev/null @@ -1,274 +0,0 @@ -import { createHash } from 'node:crypto'; - -export const RELEASE_TAG = 'code-reference-playground-preview'; -export const COMMENT_MARKER = ''; -export const PLAYGROUND_ORIGIN = 'https://playground.wordpress.net'; -export const CORS_PROXY = 'https://wordpress-playground-cors-proxy.net/'; -export const SNAPSHOT_BYTES_LIMIT = 104857600; - -const FULL_COMMIT = /^[0-9a-f]{40}$/; -const FULL_DIGEST = /^[0-9a-f]{64}$/; -const REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/; - -function positiveInteger( value, label ) { - const number = Number( value ); - if ( ! Number.isSafeInteger( number ) || number < 1 ) { - throw new Error( `${ label } must be a positive integer.` ); - } - return number; -} - -function assertRepository( repository, label ) { - if ( typeof repository !== 'string' || ! REPOSITORY.test( repository ) ) { - throw new Error( `${ label } must be an owner/repository.` ); - } -} - -function assertCommit( sha, label ) { - if ( typeof sha !== 'string' || ! FULL_COMMIT.test( sha ) ) { - throw new Error( `${ label } must be a full lowercase commit hash.` ); - } -} - -export function snapshotAssetName( identity ) { - const pullRequest = positiveInteger( - identity.pullRequestNumber, - 'pullRequestNumber' - ); - const runId = positiveInteger( identity.workflowRunId, 'workflowRunId' ); - const attempt = positiveInteger( - identity.workflowRunAttempt, - 'workflowRunAttempt' - ); - assertCommit( identity.sourceSha, 'sourceSha' ); - return `code-reference-pr-${ pullRequest }-${ identity.sourceSha }-${ runId }-${ attempt }.zip`; -} - -export function metadataAssetName( snapshotName ) { - if ( - typeof snapshotName !== 'string' || - ! snapshotName.endsWith( '.zip' ) - ) { - throw new Error( 'Snapshot asset name must end in .zip.' ); - } - return `${ snapshotName.slice( 0, -4 ) }.json`; -} - -export function releaseAssetUrl( repository, assetName ) { - assertRepository( repository, 'repository' ); - if ( - typeof assetName !== 'string' || - ! /^[A-Za-z0-9_.-]+$/.test( assetName ) - ) { - throw new Error( 'Release asset name is invalid.' ); - } - return `https://github.com/${ repository }/releases/download/${ RELEASE_TAG }/${ assetName }`; -} - -export function corsProxyUrl( publicUrl ) { - const parsed = new URL( publicUrl ); - if ( - parsed.protocol !== 'https:' || - ! [ 'github.com', 'raw.githubusercontent.com' ].includes( - parsed.hostname - ) - ) { - throw new Error( 'Only public GitHub asset URLs may use the proxy.' ); - } - return `${ CORS_PROXY }${ publicUrl }`; -} - -export function createLaunchBlueprint( snapshotUrl, runtime ) { - return { - $schema: runtime.blueprintSchema, - meta: { - title: 'WordPress Core Code Reference preview', - author: 'WordPress', - description: - 'Complete Core Code Reference generated from an exact commit.', - }, - preferredVersions: { - php: runtime.phpVersion, - wp: runtime.wordpressVersion, - }, - landingPage: '/reference/', - login: false, - features: { networking: false }, - steps: [ - { - step: 'unzip', - zipFile: { resource: 'url', url: corsProxyUrl( snapshotUrl ) }, - extractToPath: '/', - }, - ], - }; -} - -export function playgroundUrl( snapshotUrl, runtime ) { - const blueprint = JSON.stringify( - createLaunchBlueprint( snapshotUrl, runtime ) - ); - return `${ PLAYGROUND_ORIGIN }/?blueprint-url=data:application/json,${ encodeURIComponent( - blueprint - ) }`; -} - -export async function validatePublicSnapshot( - publicUrl, - expected, - fetchImplementation = globalThis.fetch -) { - if ( - ! Number.isSafeInteger( expected.bytes ) || - expected.bytes < 1 || - expected.bytes > expected.maximumBytes - ) { - throw new Error( 'Published snapshot size is outside its boundary.' ); - } - if ( ! FULL_DIGEST.test( expected.sha256 || '' ) ) { - throw new Error( 'Published snapshot digest is invalid.' ); - } - const response = await fetchImplementation( corsProxyUrl( publicUrl ), { - headers: { Origin: PLAYGROUND_ORIGIN }, - } ); - if ( response.status !== 200 ) { - throw new Error( - `Public snapshot returned HTTP ${ response.status } through the proxy.` - ); - } - if ( response.headers.get( 'x-playground-cors-proxy' ) !== 'true' ) { - throw new Error( - 'Public snapshot response did not come through the proxy.' - ); - } - const allowedOrigin = response.headers.get( 'access-control-allow-origin' ); - if ( allowedOrigin !== PLAYGROUND_ORIGIN && allowedOrigin !== '*' ) { - throw new Error( - 'Public snapshot response does not allow Playground.' - ); - } - const bytes = Buffer.from( await response.arrayBuffer() ); - if ( bytes.byteLength !== expected.bytes ) { - throw new Error( - `Public snapshot has ${ bytes.byteLength } bytes; expected ${ expected.bytes }.` - ); - } - const digest = createHash( 'sha256' ).update( bytes ).digest( 'hex' ); - if ( digest !== expected.sha256 ) { - throw new Error( - 'Public snapshot digest does not match its metadata.' - ); - } - return { bytes: bytes.byteLength, sha256: digest }; -} - -export function validatePublishedSnapshotMetadata( metadata, expected ) { - if ( - ! metadata || - typeof metadata !== 'object' || - Array.isArray( metadata ) - ) { - throw new Error( 'Published metadata must be an object.' ); - } - if ( metadata.schemaVersion !== 1 ) { - throw new Error( 'Published metadata schema is unsupported.' ); - } - assertRepository( metadata.sourceRepository, 'sourceRepository' ); - assertRepository( expected.sourceRepository, 'expected sourceRepository' ); - assertCommit( metadata.sourceSha, 'sourceSha' ); - assertCommit( expected.sourceSha, 'expected sourceSha' ); - if ( - metadata.sourceRepository !== expected.sourceRepository || - metadata.sourceSha !== expected.sourceSha || - metadata.pullRequestNumber !== expected.pullRequestNumber - ) { - throw new Error( 'Published metadata does not match its source.' ); - } - if ( - metadata.buildStatus !== 'success' || - metadata.validationStatus !== 'passed' - ) { - throw new Error( - 'Published metadata does not describe a valid snapshot.' - ); - } - if ( - ! Number.isSafeInteger( metadata.snapshotBytes ) || - metadata.snapshotBytes < 1 || - metadata.snapshotBytes > expected.maximumBytes || - ! FULL_DIGEST.test( metadata.snapshotSha256 || '' ) - ) { - throw new Error( 'Published snapshot identity is invalid.' ); - } - if ( - metadata.phpVersion !== '8.4' || - metadata.resolvedWordPressBeta?.channel !== 'beta' || - ! /^\d+\.\d+(?:\.\d+)?-(?:beta\d+|RC\d+)$/.test( - metadata.resolvedWordPressBeta.version || '' - ) || - metadata.resolvedWordPressBeta.downloadUrl !== - `https://downloads.wordpress.org/release/wordpress-${ metadata.resolvedWordPressBeta.version }.zip` - ) { - throw new Error( 'Published runtime identity is invalid.' ); - } - if ( ! FULL_DIGEST.test( metadata.dependencyManifestDigest || '' ) ) { - throw new Error( 'Published dependency identity is invalid.' ); - } - if ( - typeof metadata.generationTimestamp !== 'string' || - Number.isNaN( Date.parse( metadata.generationTimestamp ) ) - ) { - throw new Error( 'Published generation timestamp is invalid.' ); - } - return metadata; -} - -export function validateReusableMetadata( metadata, expected ) { - validatePublishedSnapshotMetadata( metadata, { - ...expected, - pullRequestNumber: positiveInteger( - expected.pullRequestNumber, - 'pullRequestNumber' - ), - } ); - const prefix = `code-reference-pr-${ metadata.pullRequestNumber }-${ metadata.sourceSha }-`; - if ( - typeof metadata.snapshotFilename !== 'string' || - ! metadata.snapshotFilename.startsWith( prefix ) || - ! /^code-reference-pr-\d+-[0-9a-f]{40}-\d+-\d+\.zip$/.test( - metadata.snapshotFilename - ) - ) { - throw new Error( 'Published snapshot filename is invalid.' ); - } - return metadata; -} - -export function createReuseHandoff( published, identity ) { - validateReusableMetadata( published, { - ...identity, - maximumBytes: identity.maximumBytes, - } ); - return { - ...published, - handoffType: 'reuse', - sourceRepository: identity.sourceRepository, - pullRequestNumber: positiveInteger( - identity.pullRequestNumber, - 'pullRequestNumber' - ), - sourceSha: identity.sourceSha, - workflowRunId: String( - positiveInteger( identity.workflowRunId, 'workflowRunId' ) - ), - workflowRunAttempt: positiveInteger( - identity.workflowRunAttempt, - 'workflowRunAttempt' - ), - runUrl: identity.runUrl, - reusedSnapshot: { - assetName: published.snapshotFilename, - metadataAssetName: metadataAssetName( published.snapshotFilename ), - }, - }; -} diff --git a/.github/scripts/docs-playground-preview/lib/published.mjs b/.github/scripts/docs-playground-preview/lib/published.mjs deleted file mode 100644 index 99e9750fc26b8..0000000000000 --- a/.github/scripts/docs-playground-preview/lib/published.mjs +++ /dev/null @@ -1,132 +0,0 @@ -import { SNAPSHOT_BYTES_LIMIT } from './publisher.mjs'; -import { - corsProxyUrl, - metadataAssetName, - playgroundUrl, - releaseAssetUrl, - validatePublicSnapshot, - validateReusableMetadata, -} from './publication.mjs'; - -const BLUEPRINT_SCHEMA = - 'https://playground.wordpress.net/blueprint-schema.json'; - -async function fetchPublishedMetadata( asset, fetchImplementation ) { - const response = await fetchImplementation( asset.browser_download_url ); - if ( response.status !== 200 ) { - throw new Error( - `Public metadata returned HTTP ${ response.status }.` - ); - } - return response.json(); -} - -function validatePublishedFields( metadata, repository, snapshotAsset ) { - const snapshotUrl = releaseAssetUrl( repository, snapshotAsset.name ); - if ( - snapshotAsset.browser_download_url !== snapshotUrl || - metadata.publication?.snapshotUrl !== snapshotUrl || - metadata.publication?.snapshotProxyUrl !== - corsProxyUrl( snapshotUrl ) || - metadata.publication?.playgroundUrl !== - playgroundUrl( snapshotUrl, { - blueprintSchema: BLUEPRINT_SCHEMA, - phpVersion: metadata.phpVersion, - wordpressVersion: metadata.resolvedWordPressBeta.version, - } ) || - typeof metadata.publication?.publishedAt !== 'string' || - ! metadata.publication.publishedAt.endsWith( 'Z' ) || - Number.isNaN( Date.parse( metadata.publication.publishedAt ) ) - ) { - throw new Error( 'Published preview pointer metadata is invalid.' ); - } -} - -export async function loadPublishedPreview( - repository, - pullRequestNumber, - metadataAsset, - snapshotAsset, - options -) { - const metadata = validateReusableMetadata( - await fetchPublishedMetadata( - metadataAsset, - options.fetchImplementation - ), - { - sourceRepository: options.sourceRepository, - pullRequestNumber, - sourceSha: options.sourceSha, - maximumBytes: SNAPSHOT_BYTES_LIMIT, - } - ); - if ( - metadataAsset.name !== metadataAssetName( metadata.snapshotFilename ) || - snapshotAsset.name !== metadata.snapshotFilename - ) { - throw new Error( 'Published release asset names are invalid.' ); - } - validatePublishedFields( metadata, repository, snapshotAsset ); - await validatePublicSnapshot( - snapshotAsset.browser_download_url, - { - bytes: metadata.snapshotBytes, - sha256: metadata.snapshotSha256, - maximumBytes: SNAPSHOT_BYTES_LIMIT, - }, - options.fetchImplementation - ); - return metadata; -} - -export async function findPreviousPreview( session, excluded = new Set() ) { - const release = await session.api.getRelease(); - if ( ! release ) { - return null; - } - const assets = await session.api.listReleaseAssets( release.id ); - const prefix = `code-reference-pr-${ session.context.pullRequestNumber }-`; - const candidates = assets - .filter( - ( asset ) => - asset.name.startsWith( prefix ) && - asset.name.endsWith( '.json' ) && - ! excluded.has( asset.name ) - ) - .sort( ( left, right ) => - right.created_at.localeCompare( left.created_at ) - ); - for ( const metadataAsset of candidates ) { - try { - const published = await fetchPublishedMetadata( - metadataAsset, - session.fetchImplementation - ); - const snapshotAsset = assets.find( - ( asset ) => - asset.name === published.snapshotFilename && - ! excluded.has( asset.name ) - ); - if ( ! snapshotAsset ) { - throw new Error( 'Published snapshot asset is missing.' ); - } - return await loadPublishedPreview( - session.repository, - session.context.pullRequestNumber, - metadataAsset, - snapshotAsset, - { - sourceRepository: published.sourceRepository, - sourceSha: published.sourceSha, - fetchImplementation: session.fetchImplementation, - } - ); - } catch ( error ) { - session.warning( - `Cannot retain ${ metadataAsset.name }: ${ error.message }` - ); - } - } - return null; -} diff --git a/.github/scripts/docs-playground-preview/lib/publisher.mjs b/.github/scripts/docs-playground-preview/lib/publisher.mjs deleted file mode 100644 index 22e87dfb29f71..0000000000000 --- a/.github/scripts/docs-playground-preview/lib/publisher.mjs +++ /dev/null @@ -1,370 +0,0 @@ -import { stat } from 'node:fs/promises'; -import path from 'node:path'; - -import { isDeploymentEnabled } from './config.mjs'; -import { sha256File } from './files.mjs'; -import { - COMMENT_MARKER, - corsProxyUrl, - metadataAssetName, - playgroundUrl, - releaseAssetUrl, - snapshotAssetName, - validateReusableMetadata, -} from './publication.mjs'; - -export const BUILD_WORKFLOW_NAME = 'Code Reference Playground Preview Build'; -export const SNAPSHOT_BYTES_LIMIT = 104857600; - -const FULL_COMMIT = /^[0-9a-f]{40}$/; -const FULL_DIGEST = /^[0-9a-f]{64}$/; -const BLUEPRINT_SCHEMA = - 'https://playground.wordpress.net/blueprint-schema.json'; - -function positiveInteger( value, label ) { - const number = Number( value ); - if ( ! Number.isSafeInteger( number ) || number < 1 ) { - throw new Error( `${ label } must be a positive integer.` ); - } - return number; -} - -function timestamp( value, label ) { - if ( - typeof value !== 'string' || - Number.isNaN( Date.parse( value ) ) || - ! value.endsWith( 'Z' ) - ) { - throw new Error( `${ label } must be a UTC timestamp.` ); - } - return value; -} - -function headRepository( value ) { - return value?.full_name || value?.repo?.full_name || null; -} - -export function validatePublicationContext( context ) { - if ( - ! isDeploymentEnabled( context.repository, context.stagingVariable ) - ) { - throw new Error( - 'Docs preview publication is disabled in this repository.' - ); - } - const runId = positiveInteger( context.run?.id, 'Workflow run id' ); - const runAttempt = positiveInteger( - context.run?.run_attempt, - 'Workflow run attempt' - ); - if ( - runId !== positiveInteger( context.triggerRunId, 'Trigger run id' ) || - runAttempt !== - positiveInteger( context.triggerRunAttempt, 'Trigger run attempt' ) - ) { - throw new Error( - 'The triggering workflow attempt is no longer current.' - ); - } - if ( - context.run.name !== BUILD_WORKFLOW_NAME || - context.run.event !== 'pull_request' - ) { - throw new Error( 'The source workflow identity is invalid.' ); - } - if ( context.run.status !== 'completed' ) { - throw new Error( 'The source workflow is not terminal.' ); - } - const pullRequestNumber = positiveInteger( - context.pullRequest?.number, - 'Pull request number' - ); - if ( context.pullRequest.base?.ref !== 'trunk' ) { - throw new Error( 'The pull request does not target trunk.' ); - } - const sourceSha = context.pullRequest.head?.sha; - if ( - ! FULL_COMMIT.test( sourceSha || '' ) || - context.run.head_sha !== sourceSha - ) { - throw new Error( - 'The workflow run does not match the pull request head.' - ); - } - const sourceRepository = headRepository( context.pullRequest.head ); - if ( - ! sourceRepository || - headRepository( context.run.head_repository ) !== sourceRepository - ) { - throw new Error( - 'The workflow run does not match the pull request repository.' - ); - } - return { - ...context, - pullRequestNumber, - sourceRepository, - sourceSha, - workflowRunId: runId, - workflowRunAttempt: runAttempt, - runUrl: `https://github.com/${ context.repository }/actions/runs/${ runId }`, - }; -} - -export function assertLatestAuthorized( context ) { - const current = validatePublicationContext( context ); - if ( - context.latestRun?.id !== current.workflowRunId || - context.latestRun?.run_attempt !== current.workflowRunAttempt - ) { - throw new Error( 'A newer docs preview run superseded this attempt.' ); - } - if ( current.pullRequest.state !== 'open' ) { - throw new Error( 'The pull request is no longer open.' ); - } - const hasLabel = current.pullRequest.labels?.some( - ( label ) => label.name === 'docs-preview' - ); - if ( ! hasLabel && current.workflowRunAttempt === 1 ) { - throw new Error( 'The docs-preview request is no longer authorized.' ); - } - return current; -} - -function validateCommonHandoff( metadata, context ) { - if ( - ! metadata || - typeof metadata !== 'object' || - Array.isArray( metadata ) - ) { - throw new Error( 'Handoff metadata must be an object.' ); - } - if ( metadata.schemaVersion !== 1 ) { - throw new Error( 'Handoff metadata schema is unsupported.' ); - } - if ( - metadata.sourceRepository !== context.sourceRepository || - metadata.pullRequestNumber !== context.pullRequestNumber || - metadata.sourceSha !== context.sourceSha || - String( metadata.workflowRunId ) !== String( context.workflowRunId ) || - metadata.workflowRunAttempt !== context.workflowRunAttempt || - metadata.runUrl !== context.runUrl - ) { - throw new Error( 'Handoff metadata does not match the workflow run.' ); - } - timestamp( metadata.generationTimestamp, 'Generation timestamp' ); -} - -function validateSnapshotIdentity( metadata ) { - if ( - ! Number.isSafeInteger( metadata.snapshotBytes ) || - metadata.snapshotBytes < 1 || - metadata.snapshotBytes > SNAPSHOT_BYTES_LIMIT - ) { - throw new Error( 'Snapshot size is outside the 100 MiB boundary.' ); - } - if ( ! FULL_DIGEST.test( metadata.snapshotSha256 || '' ) ) { - throw new Error( 'Snapshot digest is invalid.' ); - } -} - -export function inspectHandoff( metadata, rawContext ) { - const context = validatePublicationContext( rawContext ); - validateCommonHandoff( metadata, context ); - if ( metadata.buildStatus === 'failed' ) { - return { kind: 'failed', metadata, context }; - } - if ( metadata.buildStatus !== 'success' ) { - throw new Error( 'Handoff build status is invalid.' ); - } - if ( metadata.validationStatus === 'failed' ) { - return { kind: 'invalid', metadata, context }; - } - if ( metadata.validationStatus !== 'passed' ) { - throw new Error( 'Handoff validation status is invalid.' ); - } - validateSnapshotIdentity( metadata ); - validateReusableMetadata( metadata, { - sourceRepository: context.sourceRepository, - pullRequestNumber: context.pullRequestNumber, - sourceSha: context.sourceSha, - maximumBytes: SNAPSHOT_BYTES_LIMIT, - } ); - if ( metadata.handoffType === 'reuse' ) { - if ( - metadata.reusedSnapshot?.assetName !== metadata.snapshotFilename || - metadata.reusedSnapshot.metadataAssetName !== - metadataAssetName( metadata.snapshotFilename ) - ) { - throw new Error( 'Reusable asset identity is invalid.' ); - } - return { kind: 'reuse', metadata, context }; - } - if ( metadata.handoffType !== undefined ) { - throw new Error( 'Handoff type is invalid.' ); - } - const expectedFilename = snapshotAssetName( context ); - if ( metadata.snapshotFilename !== expectedFilename ) { - throw new Error( 'Candidate snapshot filename is invalid.' ); - } - return { kind: 'candidate', metadata, context }; -} - -export async function validateCandidateFile( metadata, filename ) { - if ( path.basename( filename ) !== metadata.snapshotFilename ) { - throw new Error( - 'Candidate file does not match its metadata filename.' - ); - } - const candidate = await stat( filename ); - if ( ! candidate.isFile() || candidate.size !== metadata.snapshotBytes ) { - throw new Error( 'Candidate file size does not match its metadata.' ); - } - const digest = await sha256File( filename ); - if ( digest !== metadata.snapshotSha256 ) { - throw new Error( 'Candidate file digest does not match its metadata.' ); - } - return { bytes: candidate.size, sha256: digest }; -} - -export function createPublishedMetadata( metadata, repository, publishedAt ) { - timestamp( publishedAt, 'Publication timestamp' ); - const snapshotUrl = releaseAssetUrl( - repository, - metadata.snapshotFilename - ); - return { - ...metadata, - publication: { - publishedAt, - snapshotUrl, - snapshotProxyUrl: corsProxyUrl( snapshotUrl ), - playgroundUrl: playgroundUrl( snapshotUrl, { - blueprintSchema: BLUEPRINT_SCHEMA, - phpVersion: metadata.phpVersion, - wordpressVersion: metadata.resolvedWordPressBeta.version, - } ), - }, - }; -} - -function commitLink( repository, sha ) { - return `[${ sha }](https://github.com/${ repository }/commit/${ sha })`; -} - -function previousLink( previous ) { - if ( ! previous ) { - return ''; - } - return `\n\n[Latest successful docs preview](${ - previous.publication.playgroundUrl - }) — built from ${ commitLink( - previous.sourceRepository, - previous.sourceSha - ) }.`; -} - -function commentHeader( source ) { - const identity = source - ? `\n` - : ''; - return `${ COMMENT_MARKER }${ identity }\n## Code Reference documentation preview`; -} - -export function readPreviewCommentSource( body ) { - if ( typeof body !== 'string' ) { - return null; - } - const match = body.match( - // - ); - return match ? { repository: match[ 1 ], sha: match[ 2 ] } : null; -} - -export function readPreviewCommentSuccess( body ) { - if ( typeof body !== 'string' ) { - return null; - } - const link = body.match( - /\[(?:Open Code Reference preview|Latest successful docs preview)\]\((https:\/\/playground\.wordpress\.net\/[^\s)]+)\)/ - ); - if ( ! link ) { - return null; - } - const previousSource = body.match( - /Latest successful docs preview\]\([^)]+\) — built from \[([0-9a-f]{40})\]\(https:\/\/github\.com\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)\/commit\/\1\)/ - ); - const source = previousSource - ? { repository: previousSource[ 2 ], sha: previousSource[ 1 ] } - : readPreviewCommentSource( body ); - if ( ! source ) { - return null; - } - return { - sourceRepository: source.repository, - sourceSha: source.sha, - publication: { playgroundUrl: link[ 1 ] }, - }; -} - -export function renderPreviewComment( state ) { - if ( state.status === 'ready' ) { - const header = commentHeader( { - repository: state.preview.sourceRepository, - sha: state.preview.sourceSha, - } ); - return `${ header }\n\n**Status:** Ready\n\n[Open Code Reference preview](${ - state.preview.publication.playgroundUrl - })\n\nSource: ${ commitLink( - state.preview.sourceRepository, - state.preview.sourceSha - ) } \nPublished: ${ - state.preview.publication.publishedAt - } \n[GitHub Actions run](${ state.runUrl })`; - } - if ( state.status === 'failed' ) { - const header = commentHeader( { - repository: state.sourceRepository, - sha: state.sourceSha, - } ); - return `${ header }\n\n**Status:** Latest attempt failed\n\nThe latest attempt for ${ commitLink( - state.sourceRepository, - state.sourceSha - ) } failed at ${ state.at }. [View the GitHub Actions run](${ - state.runUrl - }).${ previousLink( state.previous ) }`; - } - if ( state.status === 'stale' ) { - const header = commentHeader( { - repository: state.preview.sourceRepository, - sha: state.preview.sourceSha, - } ); - return `${ header }\n\n**Status:** Stale\n\nThe latest successful preview was built from ${ commitLink( - state.preview.sourceRepository, - state.preview.sourceSha - ) }, but the pull request is now at ${ commitLink( - state.currentRepository, - state.currentSha - ) }.\n\n[Latest successful docs preview](${ - state.preview.publication.playgroundUrl - })\n\nAdd the \`docs-preview\` label again to build the current commit.`; - } - if ( state.status === 'stale-unavailable' ) { - const header = commentHeader( { - repository: state.previousRepository, - sha: state.previousSha, - } ); - return `${ header }\n\n**Status:** Stale\n\nThe latest preview attempt was for ${ commitLink( - state.previousRepository, - state.previousSha - ) }, but no healthy docs preview is available. The pull request is now at ${ commitLink( - state.currentRepository, - state.currentSha - ) }.\n\nAdd the \`docs-preview\` label again to build the current commit.`; - } - if ( state.status === 'expired' ) { - const header = commentHeader(); - return `${ header }\n\n**Status:** Expired\n\nThis pull request is closed or merged. Its Code Reference preview expired and is no longer live.`; - } - throw new Error( `Unknown preview comment state ${ state.status }.` ); -} diff --git a/.github/scripts/docs-playground-preview/lib/runtime.mjs b/.github/scripts/docs-playground-preview/lib/runtime.mjs deleted file mode 100644 index a37701e0e3e67..0000000000000 --- a/.github/scripts/docs-playground-preview/lib/runtime.mjs +++ /dev/null @@ -1,127 +0,0 @@ -const FULL_COMMIT = /^[0-9a-f]{40}$/; -const REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/; -const RUN_URL = - /^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/actions\/runs\/\d+$/; - -function phpString( value ) { - return JSON.stringify( value ); -} - -export function validateProvenance( provenance ) { - if ( ! REPOSITORY.test( provenance?.sourceRepository || '' ) ) { - throw new Error( 'Preview provenance repository is invalid.' ); - } - if ( ! FULL_COMMIT.test( provenance.sourceSha || '' ) ) { - throw new Error( 'Preview provenance commit is invalid.' ); - } - if ( - Number.isNaN( Date.parse( provenance.generationTimestamp ) ) || - ! provenance.generationTimestamp.endsWith( 'Z' ) - ) { - throw new Error( 'Preview provenance timestamp must be UTC.' ); - } - if ( - provenance.runUrl !== null && - ! RUN_URL.test( provenance.runUrl || '' ) - ) { - throw new Error( 'Preview provenance run URL is invalid.' ); - } - return provenance; -} - -export function renderRuntimePlugin( candidate ) { - const provenance = validateProvenance( candidate ); - const runUrl = - provenance.runUrl === null ? 'null' : phpString( provenance.runUrl ); - return ` ${ phpString( provenance.sourceRepository ) }, - 'sourceSha' => ${ phpString( provenance.sourceSha ) }, - 'generationTimestamp' => ${ phpString( provenance.generationTimestamp ) }, - 'runUrl' => ${ runUrl }, -); - -add_filter( - 'pre_http_request', - static function() { - return new WP_Error( - 'docs_preview_network_disabled', - 'Outbound networking is disabled in the Code Reference preview.' - ); - }, - PHP_INT_MAX -); - -$wporg_docs_preview_banner_rendered = false; -$wporg_docs_preview_render_banner = static function() use ( &$wporg_docs_preview_banner_rendered, $wporg_docs_preview_provenance ) { - if ( $wporg_docs_preview_banner_rendered || is_admin() ) { - return; - } - $wporg_docs_preview_banner_rendered = true; - $commit_url = sprintf( - 'https://github.com/%s/commit/%s', - $wporg_docs_preview_provenance['sourceRepository'], - $wporg_docs_preview_provenance['sourceSha'] - ); - ?> - - 'GET', - 'permission_callback' => '__return_true', - 'callback' => static function() use ( $wporg_docs_preview_provenance ) { - $marker_file = WP_CONTENT_DIR . '/docs-preview-import.json'; - $marker = file_exists( $marker_file ) - ? json_decode( file_get_contents( $marker_file ), true ) - : null; - $network = wp_remote_get( 'https://api.wordpress.org/' ); - return array( - 'provenance' => $wporg_docs_preview_provenance, - 'import' => $marker, - 'outboundNetworkDisabled' => is_wp_error( $network ) && 'docs_preview_network_disabled' === $network->get_error_code(), - 'constants' => array( - 'DISABLE_WP_CRON' => defined( 'DISABLE_WP_CRON' ) && true === DISABLE_WP_CRON, - 'AUTOMATIC_UPDATER_DISABLED' => defined( 'AUTOMATIC_UPDATER_DISABLED' ) && true === AUTOMATIC_UPDATER_DISABLED, - 'WP_AUTO_UPDATE_CORE' => defined( 'WP_AUTO_UPDATE_CORE' ) && false === WP_AUTO_UPDATE_CORE, - 'DISALLOW_FILE_MODS' => defined( 'DISALLOW_FILE_MODS' ) && true === DISALLOW_FILE_MODS, - ), - ); - }, - ) - ); - } -); -`; -} diff --git a/.github/scripts/docs-playground-preview/lib/snapshot.mjs b/.github/scripts/docs-playground-preview/lib/snapshot.mjs new file mode 100644 index 0000000000000..eedd4db9d5ddf --- /dev/null +++ b/.github/scripts/docs-playground-preview/lib/snapshot.mjs @@ -0,0 +1,206 @@ +import { copyFile, mkdir, rm, stat, writeFile } from 'node:fs/promises'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import path from 'node:path'; + +import { assertSnapshotFits } from './preview.mjs'; + +const PHP_ROOT = path.resolve( + path.dirname( fileURLToPath( import.meta.url ) ), + '../php' +); + +/** + * Builds a Playground snapshot with the pinned CLI. The caller supplies the + * Blueprint, the output file and anything to mount; everything else is the same + * for every snapshot this build produces. + * + * @param {Record} inputs + * @param {string} playgroundCli + * @param {Record} options + */ +export async function buildSnapshot( inputs, playgroundCli, options ) { + const modulePath = path.resolve( + path.dirname( playgroundCli ), + '../@wp-playground/cli/index.js' + ); + const { runCLI } = await import( pathToFileURL( modulePath ).href ); + await runCLI( { + command: 'build-snapshot', + php: inputs.dependencies.playground.phpVersion, + wp: inputs.wordpress.version, + // Every Blueprint here writes files bundled next to it. + 'blueprint-may-read-adjacent-files': true, + // Parallel workers write the same snapshot filesystem, so builds use one. + workers: 1, + verbosity: 'normal', + ...options, + } ); +} + +/** + * @param {string | null} value + */ +function phpValue( value ) { + return value === null + ? 'null' + : `'${ value.replace( /\\/g, '\\\\' ).replace( /'/g, "\\'" ) }'`; +} + +/** + * Renders the provenance php/preview-runtime.php reads for its banner. + * + * @param {{sourceRepository: string, sourceSha: string, generationTimestamp: string, runUrl: string | null}} provenance + */ +function renderProvenance( provenance ) { + return ` ${ phpValue( provenance.sourceRepository ) }, + 'sha' => ${ phpValue( provenance.sourceSha ) }, + 'generated' => ${ phpValue( provenance.generationTimestamp ) }, + 'runUrl' => ${ phpValue( provenance.runUrl ) }, +); +`; +} + +/** + * @param {Record} inputs + */ +function finalBlueprint( inputs ) { + return { + meta: { + title: 'WordPress Core Code Reference preview', + author: 'WordPress', + description: + 'Complete Core Code Reference imported into the cached site.', + }, + preferredVersions: { + php: inputs.dependencies.playground.phpVersion, + wp: inputs.wordpress.version, + }, + landingPage: '/reference/', + login: false, + features: { networking: false }, + extraLibraries: [ 'wp-cli' ], + steps: [ + { + step: 'unzip', + zipFile: { resource: 'bundled', path: 'base.zip' }, + extractToPath: '/', + }, + { step: 'mkdir', path: '/tmp/docs-preview-version' }, + { step: 'mkdir', path: '/tmp/docs-preview-version/wp-includes' }, + { + step: 'writeFile', + path: '/tmp/reference.json', + data: { resource: 'bundled', path: 'reference.json' }, + }, + // The importer includes every file it records. Records for + // wp-includes/version.php point at this empty stub instead of the + // pull-request file, which is only ever read as text. + { + step: 'writeFile', + path: '/tmp/docs-preview-version/wp-includes/version.php', + data: { resource: 'bundled', path: 'safe-version.php' }, + }, + { + step: 'wp-cli', + command: + 'wp parser import /tmp/reference.json --quick --user=1', + }, + { + step: 'writeFile', + path: '/tmp/docs-preview-complete-import.php', + data: { resource: 'bundled', path: 'complete-import.php' }, + }, + { + step: 'wp-cli', + command: 'wp eval-file /tmp/docs-preview-complete-import.php', + }, + { + step: 'rmdir', + path: '/wordpress/wp-content/plugins/phpdoc-parser', + }, + { + step: 'writeFile', + path: '/wordpress/wp-content/docs-preview-provenance.php', + data: { resource: 'bundled', path: 'provenance.php' }, + }, + { + step: 'writeFile', + path: '/wordpress/wp-content/mu-plugins/001-docs-preview-runtime.php', + data: { resource: 'bundled', path: 'preview-runtime.php' }, + }, + { + step: 'defineWpConfigConsts', + method: 'rewrite-wp-config', + consts: { + DISABLE_WP_CRON: true, + AUTOMATIC_UPDATER_DISABLED: true, + WP_AUTO_UPDATE_CORE: false, + DISALLOW_FILE_MODS: true, + }, + }, + ], + }; +} + +/** + * Imports the parsed Code Reference into the cached base and packages the + * result as the snapshot the preview publishes. + * + * @param {Record} inputs + * @param {{workDirectory: string, output: string, referenceJson: string, stagedSource: string, playgroundCli: string, provenance: any}} options + */ +export async function packageSnapshot( inputs, options ) { + const work = path.resolve( options.workDirectory ); + const output = path.resolve( options.output ); + await rm( work, { recursive: true, force: true } ); + await mkdir( work, { recursive: true } ); + await mkdir( path.dirname( output ), { recursive: true } ); + await copyFile( + path.join( inputs.cacheDirectory, 'base.zip' ), + path.join( work, 'base.zip' ) + ); + await copyFile( + options.referenceJson, + path.join( work, 'reference.json' ) + ); + for ( const script of [ + 'complete-import.php', + 'preview-runtime.php', + 'safe-version.php', + ] ) { + await copyFile( + path.join( PHP_ROOT, script ), + path.join( work, script ) + ); + } + await writeFile( + path.join( work, 'provenance.php' ), + renderProvenance( options.provenance ) + ); + const blueprint = path.join( work, 'final-blueprint.json' ); + await writeFile( + blueprint, + `${ JSON.stringify( finalBlueprint( inputs ), null, 2 ) }\n` + ); + await buildSnapshot( inputs, options.playgroundCli, { + blueprint, + mount: [ + { + hostPath: path.resolve( options.stagedSource ), + vfsPath: '/tmp/docs-preview-source', + }, + ], + outfile: output, + } ); + const snapshot = await stat( output ); + assertSnapshotFits( snapshot.size ); + process.stdout.write( + `Packaged ${ path.basename( output ) } at ${ snapshot.size } bytes.\n` + ); +} diff --git a/.github/scripts/docs-playground-preview/lib/trunk.mjs b/.github/scripts/docs-playground-preview/lib/trunk.mjs deleted file mode 100644 index 86638cd5cc82d..0000000000000 --- a/.github/scripts/docs-playground-preview/lib/trunk.mjs +++ /dev/null @@ -1,311 +0,0 @@ -import { isDeepStrictEqual } from 'node:util'; - -import { isDeploymentEnabled } from './config.mjs'; -import { - PLAYGROUND_ORIGIN, - SNAPSHOT_BYTES_LIMIT, - corsProxyUrl, - createLaunchBlueprint, - playgroundUrl, - releaseAssetUrl, - validatePublishedSnapshotMetadata, -} from './publication.mjs'; - -export const TRUNK_BUILD_WORKFLOW_NAME = - 'Code Reference Playground Preview Build'; -export const TRUNK_POINTER_ASSET = 'code-reference-trunk.json'; -export const TRUNK_POINTER_REF = 'heads/docs-preview-code-reference'; - -const BLUEPRINT_SCHEMA = - 'https://playground.wordpress.net/blueprint-schema.json'; -const FULL_COMMIT = /^[0-9a-f]{40}$/; -const MAXIMUM_BLUEPRINT_BYTES = 65536; - -function positiveInteger( value, label ) { - const number = Number( value ); - if ( ! Number.isSafeInteger( number ) || number < 1 ) { - throw new Error( `${ label } must be a positive integer.` ); - } - return number; -} - -function timestamp( value, label ) { - if ( - typeof value !== 'string' || - Number.isNaN( Date.parse( value ) ) || - ! value.endsWith( 'Z' ) - ) { - throw new Error( `${ label } must be a UTC timestamp.` ); - } - return value; -} - -export function trunkSnapshotAssetName( identity ) { - if ( ! FULL_COMMIT.test( identity.sourceSha || '' ) ) { - throw new Error( 'sourceSha must be a full lowercase commit hash.' ); - } - const runId = positiveInteger( identity.workflowRunId, 'workflowRunId' ); - const attempt = positiveInteger( - identity.workflowRunAttempt, - 'workflowRunAttempt' - ); - return `code-reference-trunk-${ identity.sourceSha }-${ runId }-${ attempt }.zip`; -} - -export function trunkStableBlueprintUrl( repository ) { - return `https://raw.githubusercontent.com/${ repository }/docs-preview-code-reference/${ TRUNK_POINTER_ASSET }`; -} - -export function trunkBlueprintCommitUrl( repository, commitSha ) { - if ( ! FULL_COMMIT.test( commitSha || '' ) ) { - throw new Error( 'commitSha must be a full lowercase commit hash.' ); - } - return `https://raw.githubusercontent.com/${ repository }/${ commitSha }/${ TRUNK_POINTER_ASSET }`; -} - -export function trunkPlaygroundUrl( repository ) { - return `${ PLAYGROUND_ORIGIN }/?blueprint-url=${ encodeURIComponent( - corsProxyUrl( trunkStableBlueprintUrl( repository ) ) - ) }`; -} - -export function validateTrunkPublicationContext( context ) { - if ( - ! isDeploymentEnabled( context.repository, context.stagingVariable ) - ) { - throw new Error( - 'Docs preview publication is disabled in this repository.' - ); - } - const runId = positiveInteger( context.run?.id, 'Workflow run id' ); - const runAttempt = positiveInteger( - context.run?.run_attempt, - 'Workflow run attempt' - ); - if ( - runId !== positiveInteger( context.triggerRunId, 'Trigger run id' ) || - runAttempt !== - positiveInteger( context.triggerRunAttempt, 'Trigger run attempt' ) - ) { - throw new Error( - 'The triggering workflow attempt is no longer current.' - ); - } - if ( - context.run.name !== TRUNK_BUILD_WORKFLOW_NAME || - context.run.event !== 'push' || - context.run.status !== 'completed' || - context.run.head_branch !== 'trunk' - ) { - throw new Error( 'The trunk source workflow identity is invalid.' ); - } - const sourceSha = context.run.head_sha; - if ( - ! FULL_COMMIT.test( sourceSha || '' ) || - context.run.head_repository?.full_name !== context.repository - ) { - throw new Error( 'The trunk workflow source identity is invalid.' ); - } - return { - ...context, - sourceRepository: context.repository, - sourceSha, - workflowRunId: runId, - workflowRunAttempt: runAttempt, - runUrl: `https://github.com/${ context.repository }/actions/runs/${ runId }`, - }; -} - -export function assertLatestTrunkAuthorized( context ) { - const current = validateTrunkPublicationContext( context ); - if ( - context.latestRun?.id !== current.workflowRunId || - context.latestRun?.run_attempt !== current.workflowRunAttempt || - context.trunkHeadSha !== current.sourceSha - ) { - throw new Error( 'A newer trunk build superseded this attempt.' ); - } - return current; -} - -function validateCommonHandoff( metadata, context ) { - if ( - ! metadata || - typeof metadata !== 'object' || - Array.isArray( metadata ) - ) { - throw new Error( 'Handoff metadata must be an object.' ); - } - if ( - metadata.schemaVersion !== 1 || - metadata.sourceRepository !== context.sourceRepository || - metadata.pullRequestNumber !== null || - metadata.sourceSha !== context.sourceSha || - String( metadata.workflowRunId ) !== String( context.workflowRunId ) || - metadata.workflowRunAttempt !== context.workflowRunAttempt || - metadata.runUrl !== context.runUrl - ) { - throw new Error( 'Handoff metadata does not match the trunk run.' ); - } - timestamp( metadata.generationTimestamp, 'Generation timestamp' ); -} - -export function inspectTrunkHandoff( metadata, rawContext ) { - const context = validateTrunkPublicationContext( rawContext ); - validateCommonHandoff( metadata, context ); - if ( metadata.buildStatus === 'failed' ) { - return { kind: 'failed', metadata, context }; - } - if ( metadata.buildStatus !== 'success' ) { - throw new Error( 'Handoff build status is invalid.' ); - } - if ( metadata.validationStatus === 'failed' ) { - return { kind: 'invalid', metadata, context }; - } - if ( metadata.validationStatus !== 'passed' ) { - throw new Error( 'Handoff validation status is invalid.' ); - } - validatePublishedSnapshotMetadata( metadata, { - sourceRepository: context.sourceRepository, - sourceSha: context.sourceSha, - pullRequestNumber: null, - maximumBytes: SNAPSHOT_BYTES_LIMIT, - } ); - if ( - metadata.handoffType !== undefined || - metadata.snapshotFilename !== trunkSnapshotAssetName( context ) - ) { - throw new Error( 'Candidate trunk snapshot filename is invalid.' ); - } - return { kind: 'candidate', metadata, context }; -} - -export function createTrunkPublishedMetadata( - metadata, - repository, - publishedAt -) { - timestamp( publishedAt, 'Publication timestamp' ); - const snapshotUrl = releaseAssetUrl( - repository, - metadata.snapshotFilename - ); - return { - ...metadata, - publication: { - publishedAt, - snapshotUrl, - snapshotProxyUrl: corsProxyUrl( snapshotUrl ), - playgroundUrl: playgroundUrl( snapshotUrl, { - blueprintSchema: BLUEPRINT_SCHEMA, - phpVersion: metadata.phpVersion, - wordpressVersion: metadata.resolvedWordPressBeta.version, - } ), - stableBlueprintUrl: trunkStableBlueprintUrl( repository ), - stablePlaygroundUrl: trunkPlaygroundUrl( repository ), - }, - }; -} - -export function validatePublishedTrunkMetadata( - metadata, - repository, - sourceSha -) { - validatePublishedSnapshotMetadata( metadata, { - sourceRepository: repository, - sourceSha, - pullRequestNumber: null, - maximumBytes: SNAPSHOT_BYTES_LIMIT, - } ); - if ( - metadata.snapshotFilename !== - trunkSnapshotAssetName( { - sourceSha, - workflowRunId: metadata.workflowRunId, - workflowRunAttempt: metadata.workflowRunAttempt, - } ) - ) { - throw new Error( 'Published trunk snapshot filename is invalid.' ); - } - const snapshotUrl = releaseAssetUrl( - repository, - metadata.snapshotFilename - ); - if ( - metadata.publication?.snapshotUrl !== snapshotUrl || - metadata.publication?.snapshotProxyUrl !== - corsProxyUrl( snapshotUrl ) || - metadata.publication?.playgroundUrl !== - playgroundUrl( snapshotUrl, { - blueprintSchema: BLUEPRINT_SCHEMA, - phpVersion: metadata.phpVersion, - wordpressVersion: metadata.resolvedWordPressBeta.version, - } ) || - metadata.publication?.stableBlueprintUrl !== - trunkStableBlueprintUrl( repository ) || - metadata.publication?.stablePlaygroundUrl !== - trunkPlaygroundUrl( repository ) - ) { - throw new Error( 'Published trunk pointer metadata is invalid.' ); - } - timestamp( metadata.publication?.publishedAt, 'Publication timestamp' ); - return metadata; -} - -export function createTrunkBlueprint( metadata ) { - const blueprint = createLaunchBlueprint( metadata.publication.snapshotUrl, { - blueprintSchema: BLUEPRINT_SCHEMA, - phpVersion: metadata.phpVersion, - wordpressVersion: metadata.resolvedWordPressBeta.version, - } ); - blueprint.meta.description = `Latest successful Core Code Reference from ${ metadata.sourceRepository }@${ metadata.sourceSha }. Generated ${ metadata.generationTimestamp }. ${ metadata.runUrl }`; - return blueprint; -} - -export async function readPublicBlueprint( - publicUrl, - fetchImplementation = globalThis.fetch -) { - const response = await fetchImplementation( corsProxyUrl( publicUrl ), { - headers: { Origin: PLAYGROUND_ORIGIN }, - } ); - if ( response.status !== 200 ) { - throw new Error( - `Public Blueprint returned HTTP ${ response.status } through the proxy.` - ); - } - if ( response.headers.get( 'x-playground-cors-proxy' ) !== 'true' ) { - throw new Error( 'Public Blueprint did not come through the proxy.' ); - } - const allowedOrigin = response.headers.get( 'access-control-allow-origin' ); - if ( allowedOrigin !== PLAYGROUND_ORIGIN && allowedOrigin !== '*' ) { - throw new Error( 'Public Blueprint does not allow Playground.' ); - } - const bytes = Buffer.from( await response.arrayBuffer() ); - if ( bytes.byteLength < 1 || bytes.byteLength > MAXIMUM_BLUEPRINT_BYTES ) { - throw new Error( 'Public Blueprint size is outside its boundary.' ); - } - let blueprint; - try { - blueprint = JSON.parse( bytes.toString( 'utf8' ) ); - } catch { - throw new Error( 'Public Blueprint is not JSON.' ); - } - return blueprint; -} - -export async function validatePublicBlueprint( - publicUrl, - expected, - fetchImplementation = globalThis.fetch -) { - const blueprint = await readPublicBlueprint( - publicUrl, - fetchImplementation - ); - if ( ! isDeepStrictEqual( blueprint, expected ) ) { - throw new Error( 'Public Blueprint does not identify the candidate.' ); - } - return blueprint; -} diff --git a/.github/scripts/docs-playground-preview/lib/validate.mjs b/.github/scripts/docs-playground-preview/lib/validate.mjs index 0e23b74433263..d9aed5682a0e3 100644 --- a/.github/scripts/docs-playground-preview/lib/validate.mjs +++ b/.github/scripts/docs-playground-preview/lib/validate.mjs @@ -1,19 +1,33 @@ import { spawn } from 'node:child_process'; import { copyFile, mkdir, rm, writeFile } from 'node:fs/promises'; -import { createServer } from 'node:net'; import path from 'node:path'; -import { PLAYGROUND_SERVER_WORKERS } from './playground.mjs'; - const BANNER_ID = 'wporg-code-reference-preview-provenance'; +const HEALTH_ROUTE = '/wp-json/docs-preview/v1/health'; +// The validation server is the only service the build job runs. +const PORT = 9400; +const BASE_URL = `http://127.0.0.1:${ PORT }`; +const BOOT_TIMEOUT_MS = 120_000; + +/** + * @param {number} milliseconds + */ +function delay( milliseconds ) { + return new Promise( ( resolve ) => setTimeout( resolve, milliseconds ) ); +} -function bundled( resourcePath ) { - return { resource: 'bundled', path: resourcePath }; +/** + * @param {unknown} error + */ +function message( error ) { + return error instanceof Error ? error.message : String( error ); } -export function createValidationBlueprint( inputs ) { +/** + * @param {Record} inputs + */ +function validationBlueprint( inputs ) { return { - $schema: inputs.dependencies.playground.blueprintSchema, preferredVersions: { php: inputs.dependencies.playground.phpVersion, wp: inputs.wordpress.version, @@ -24,117 +38,21 @@ export function createValidationBlueprint( inputs ) { steps: [ { step: 'unzip', - zipFile: bundled( 'snapshot.zip' ), + zipFile: { resource: 'bundled', path: 'snapshot.zip' }, extractToPath: '/', }, ], }; } -function delay( milliseconds ) { - return new Promise( ( resolve ) => setTimeout( resolve, milliseconds ) ); -} - -async function availablePort() { - const server = createServer(); - await new Promise( ( resolve, reject ) => { - server.once( 'error', reject ); - server.listen( 0, '127.0.0.1', resolve ); - } ); - const port = server.address().port; - await new Promise( ( resolve ) => server.close( resolve ) ); - return port; -} - -function startServer( command, args ) { - const child = spawn( command, args, { - stdio: [ 'ignore', 'pipe', 'pipe' ], - } ); - let output = ''; - child.stdout.on( 'data', ( chunk ) => { - output += chunk; - } ); - child.stderr.on( 'data', ( chunk ) => { - output += chunk; - } ); - const closed = new Promise( ( resolve ) => { - child.once( 'error', ( error ) => resolve( { error } ) ); - child.once( 'close', ( code, signal ) => resolve( { code, signal } ) ); - } ); - return { child, closed, output: () => output }; -} - -export function createValidationServerArguments( - inputs, - blueprint, - baseUrl, - port -) { - return [ - 'server', - '--php', - inputs.dependencies.playground.phpVersion, - '--wp', - inputs.wordpress.version, - '--blueprint', - blueprint, - '--blueprint-may-read-adjacent-files', - '--site-url', - baseUrl, - '--port', - String( port ), - '--workers', - String( PLAYGROUND_SERVER_WORKERS ), - '--verbosity', - 'normal', - ]; -} - -async function stopServer( server ) { - if ( server.child.exitCode !== null || server.child.signalCode !== null ) { - return; - } - server.child.kill( 'SIGTERM' ); - const stopped = await Promise.race( [ - server.closed.then( () => true ), - delay( 5000 ).then( () => false ), - ] ); - if ( ! stopped ) { - server.child.kill( 'SIGKILL' ); - await server.closed; - } -} - -async function waitForBoot( baseUrl, server, fetchImplementation ) { - const deadline = Date.now() + 120000; - while ( Date.now() < deadline ) { - const state = await Promise.race( [ - server.closed, - delay( 250 ).then( () => null ), - ] ); - if ( state ) { - throw new Error( - `Playground exited before boot (${ - state.code ?? state.signal ?? state.error?.message - }).\n${ server.output() }` - ); - } - try { - const response = await fetchImplementation( - `${ baseUrl }/wp-json/docs-preview/v1/health` - ); - if ( response.ok ) { - return; - } - } catch { - // The server is not accepting requests yet. - } - } - throw new Error( - `Playground did not boot within 120 seconds.\n${ server.output() }` - ); -} - +/** + * Serves the finished snapshot from a temporary directory for the duration of + * `inspect`. + * + * @param {Record} inputs + * @param {Record} options + * @param {(baseUrl: string) => Promise} inspect + */ async function withPlaygroundServer( inputs, options, inspect ) { const work = path.resolve( options.workDirectory ); await rm( work, { recursive: true, force: true } ); @@ -143,37 +61,96 @@ async function withPlaygroundServer( inputs, options, inspect ) { const blueprint = path.join( work, 'validation-blueprint.json' ); await writeFile( blueprint, - `${ JSON.stringify( createValidationBlueprint( inputs ), null, 2 ) }\n` + `${ JSON.stringify( validationBlueprint( inputs ), null, 2 ) }\n` ); - const port = await availablePort(); - const baseUrl = `http://127.0.0.1:${ port }`; - const server = startServer( + + const server = spawn( options.playgroundCli, - createValidationServerArguments( inputs, blueprint, baseUrl, port ) + [ + 'server', + '--php', + inputs.dependencies.playground.phpVersion, + '--wp', + inputs.wordpress.version, + '--blueprint', + blueprint, + '--blueprint-may-read-adjacent-files', + '--site-url', + BASE_URL, + '--port', + String( PORT ), + // Enough workers that one slow request cannot stall the checks. + '--workers', + '6', + '--verbosity', + 'normal', + ], + { stdio: [ 'ignore', 'pipe', 'pipe' ] } ); + let log = ''; + server.stdout?.setEncoding( 'utf8' ); + server.stderr?.setEncoding( 'utf8' ); + server.stdout?.on( 'data', ( chunk ) => { + log += chunk; + } ); + server.stderr?.on( 'data', ( chunk ) => { + log += chunk; + } ); + let running = true; + const closed = new Promise( ( resolve ) => { + server.once( 'error', ( error ) => { + log += message( error ); + resolve( undefined ); + } ); + server.once( 'close', resolve ); + } ).then( () => { + running = false; + } ); + try { - await waitForBoot( baseUrl, server, options.fetchImplementation ); - return await inspect( baseUrl ); + const deadline = Date.now() + BOOT_TIMEOUT_MS; + for (;;) { + if ( ! running ) { + throw new Error( `Playground exited before boot.\n${ log }` ); + } + if ( Date.now() > deadline ) { + throw new Error( + `Playground did not boot within ${ + BOOT_TIMEOUT_MS / 1000 + } seconds.\n${ log }` + ); + } + try { + if ( ( await fetch( `${ BASE_URL }${ HEALTH_ROUTE }` ) ).ok ) { + break; + } + } catch { + // The server is not accepting requests yet. + } + await delay( 250 ); + } + return await inspect( BASE_URL ); } finally { - await stopServer( server ); - await rm( work, { recursive: true, force: true } ); - } -} - -function provenanceFailure( actual, expected ) { - for ( const name of [ - 'sourceRepository', - 'sourceSha', - 'generationTimestamp', - 'runUrl', - ] ) { - if ( actual?.[ name ] !== expected[ name ] ) { - return `Health provenance ${ name } does not match the build.`; + server.kill(); + // A server that ignored SIGTERM would hold the job to its timeout. + const stopped = await Promise.race( [ + closed.then( () => true ), + delay( 5000 ).then( () => false ), + ] ); + if ( ! stopped ) { + server.kill( 'SIGKILL' ); + await closed; } + await rm( work, { recursive: true, force: true } ); } - return null; } +/** + * @param {string} name + * @param {string} body + * @param {Record} provenance + * @param {string[]} failures + */ function checkBanner( name, body, provenance, failures ) { const timestamp = provenance.generationTimestamp .slice( 0, 19 ) @@ -183,54 +160,33 @@ function checkBanner( name, body, provenance, failures ) { provenance.sourceRepository, provenance.sourceSha, `${ timestamp } UTC`, + ...( provenance.runUrl ? [ provenance.runUrl ] : [] ), ]; - if ( provenance.runUrl ) { - expected.push( provenance.runUrl ); - } - for ( const value of expected ) { - if ( ! body.includes( value ) ) { - failures.push( - `${ name } route has incomplete provenance banner.` - ); - } + const missing = expected.filter( ( value ) => ! body.includes( value ) ); + if ( missing.length > 0 ) { + failures.push( + `${ name } page banner is missing ${ missing.join( ', ' ) }.` + ); } } -export async function inspectSnapshotBehavior( inputs, options ) { +/** + * Requests the pages a reader of the preview actually opens and reports what + * is wrong with them. + * + * @param {Record} inputs + * @param {string} baseUrl + * @param {Record} provenance + */ +export async function inspectSnapshotBehavior( inputs, baseUrl, provenance ) { + /** @type {string[]} */ const failures = []; - const checks = {}; - const request = async ( route ) => { - const response = await options.fetchImplementation( - `${ options.baseUrl }${ route }` - ); - return { - status: response.status, - url: response.url, - body: await response.text(), - }; - }; - let health; try { - const response = await request( '/wp-json/docs-preview/v1/health' ); - checks.health = response.status; - health = JSON.parse( response.body ); - if ( response.status !== 200 ) { - failures.push( `Health route returned HTTP ${ response.status }.` ); - } - } catch ( error ) { - failures.push( `Health route failed: ${ error.message }` ); - } - if ( health ) { - const mismatch = provenanceFailure( - health.provenance, - options.provenance - ); - if ( mismatch ) { - failures.push( mismatch ); - } + const response = await fetch( `${ baseUrl }${ HEALTH_ROUTE }` ); + const health = await response.json(); if ( health.import?.stage !== 'complete-import' ) { - failures.push( 'Complete import terminal stage is missing.' ); + failures.push( 'The Code Reference import did not complete.' ); } if ( health.outboundNetworkDisabled !== true ) { failures.push( 'WordPress outbound networking is not disabled.' ); @@ -247,82 +203,74 @@ export async function inspectSnapshotBehavior( inputs, options ) { ); } } + } catch ( error ) { + failures.push( `Health route failed: ${ message( error ) }` ); } - for ( const [ name, target ] of Object.entries( - inputs.dependencies.validation.routes - ) ) { + const { routes, search } = inputs.dependencies.validation; + const checks = [ + ...Object.entries( routes ).map( ( [ name, route ] ) => ( { + name, + stablePath: true, + ...route, + } ) ), + { name: 'search', stablePath: false, ...search }, + ]; + for ( const check of checks ) { try { - const response = await request( target.path ); - checks[ name ] = response.status; + const response = await fetch( `${ baseUrl }${ check.path }` ); + const body = await response.text(); if ( response.status !== 200 ) { + // A Playground page that fails reports why in its own output, + // and the build job has no other copy of that page. failures.push( - `${ name } route returned HTTP ${ response.status }.` + `${ check.name } route returned HTTP ${ + response.status + }: ${ body.replace( /\s+/g, ' ' ).trim().slice( 0, 300 ) }` ); } - const requested = new URL( target.path, options.baseUrl ); if ( - ! response.url || - new URL( response.url ).pathname !== requested.pathname + check.stablePath && + new URL( response.url ).pathname !== + new URL( check.path, baseUrl ).pathname ) { failures.push( - `${ name } route redirected away from its stable path.` + `${ check.name } route redirected away from its stable path.` ); } - if ( ! response.body.includes( target.expectedText ) ) { - failures.push( - `${ name } route is missing its representative symbol.` - ); + for ( const expected of [ + check.expectedText, + check.expectedPath, + ] ) { + if ( expected && ! body.includes( expected ) ) { + failures.push( + `${ check.name } route is missing ${ expected }.` + ); + } } - checkBanner( name, response.body, options.provenance, failures ); + checkBanner( check.name, body, provenance, failures ); } catch ( error ) { - failures.push( `${ name } route failed: ${ error.message }` ); - } - } - - try { - const search = inputs.dependencies.validation.search; - const response = await request( search.path ); - checks.search = response.status; - if ( - response.status !== 200 || - ! response.body.includes( search.expectedText ) || - ! response.body.includes( search.expectedPath ) - ) { failures.push( - 'Local Code Reference search did not return the expected result.' + `${ check.name } route failed: ${ message( error ) }` ); } - checkBanner( 'search', response.body, options.provenance, failures ); - } catch ( error ) { - failures.push( - `Local Code Reference search failed: ${ error.message }` - ); } - if ( options.requireRunUrl && ! options.provenance.runUrl ) { - failures.push( 'Published previews require an Actions run URL.' ); - } - return { failures, checks }; + return { failures }; } +/** + * @param {Record} inputs + * @param {{snapshot: string, workDirectory: string, playgroundCli: string, provenance: Record}} options + */ export async function validateSnapshot( inputs, options ) { - const fetchImplementation = options.fetchImplementation || globalThis.fetch; try { - return await ( options.serveImplementation || withPlaygroundServer )( - inputs, - { ...options, fetchImplementation }, - ( baseUrl ) => - inspectSnapshotBehavior( inputs, { - ...options, - baseUrl, - fetchImplementation, - } ) + return await withPlaygroundServer( inputs, options, ( baseUrl ) => + inspectSnapshotBehavior( inputs, baseUrl, options.provenance ) ); } catch ( error ) { return { - failures: [ `Playground boot failed: ${ error.message }` ], - checks: {}, + failures: [ `Playground boot failed: ${ message( error ) }` ], }; } } diff --git a/.github/scripts/docs-playground-preview/lifecycle.mjs b/.github/scripts/docs-playground-preview/lifecycle.mjs index 1daf868ac6e6a..a2be96a5c2ea9 100644 --- a/.github/scripts/docs-playground-preview/lifecycle.mjs +++ b/.github/scripts/docs-playground-preview/lifecycle.mjs @@ -1,285 +1,135 @@ #!/usr/bin/env node -import { readFile } from 'node:fs/promises'; +// Keeps the preview comment honest for the rest of a pull request's life: a push +// marks the published preview stale, and closing the pull request deletes its +// snapshots and caches. + import { pathToFileURL } from 'node:url'; -import { isDeploymentEnabled } from './lib/config.mjs'; -import { GitHubApi } from './lib/github.mjs'; -import { findPreviousPreview } from './lib/published.mjs'; import { - readPreviewCommentSuccess, + expiredComment, readPreviewCommentSource, - renderPreviewComment, -} from './lib/publisher.mjs'; + staleComment, + staleUnavailableComment, +} from './lib/comment.mjs'; +import { + GitHubApi, + assertDeploymentEnabled, + readWorkflowEvent, +} from './lib/github.mjs'; +import { assetPrefix, findLatestPublishedPreview } from './lib/preview.mjs'; -const FULL_COMMIT = /^[0-9a-f]{40}$/; const CACHE_PREFIX = 'docs-preview-base-'; -class LifecycleSuperseded extends Error {} - -function isTerminalComment( body ) { - return ( - typeof body === 'string' && - ( body.includes( '\n\n**Status:** Ready\n\n' ) || - body.includes( '\n\n**Status:** Latest attempt failed\n\n' ) ) - ); -} - -function positiveInteger( value, label ) { - const number = Number( value ); - if ( ! Number.isSafeInteger( number ) || number < 1 ) { - throw new Error( `${ label } must be a positive integer.` ); - } - return number; -} - -function lifecycleContext( options ) { - if ( - ! isDeploymentEnabled( options.repository, options.stagingVariable ) - ) { - throw new Error( - 'Docs preview lifecycle is disabled in this repository.' - ); - } - const action = options.event?.action; - if ( action !== 'synchronize' && action !== 'closed' ) { - throw new Error( `Unsupported pull request action ${ action }.` ); - } - const pullRequest = options.event.pull_request; - const pullRequestNumber = positiveInteger( - pullRequest?.number, - 'Pull request number' - ); - if ( - pullRequest.base?.ref !== 'trunk' || - ! FULL_COMMIT.test( pullRequest.head?.sha || '' ) - ) { - throw new Error( 'Pull request lifecycle identity is invalid.' ); - } - const sourceRepository = pullRequest.head.repo?.full_name || null; - if ( action === 'synchronize' && ! sourceRepository ) { - throw new Error( 'Open pull request head repository is missing.' ); - } - return { - action, - pullRequestNumber, - sourceRepository, - sourceSha: pullRequest.head.sha, - }; -} - -function assertCurrent( context, pullRequest, state, labelMustBeAbsent ) { +/** + * @param {Record} api + * @param {Record} context + * @param {Record} pullRequest + */ +async function markStale( api, context, pullRequest ) { + // A labelled pull request is already building a preview for the new head, + // and that build replaces the comment when it publishes. if ( - pullRequest.number !== context.pullRequestNumber || - pullRequest.state !== state || - pullRequest.base?.ref !== 'trunk' || - pullRequest.head?.sha !== context.sourceSha || - ( context.sourceRepository && - pullRequest.head.repo?.full_name !== context.sourceRepository ) || - ( labelMustBeAbsent && - pullRequest.labels?.some( - ( label ) => label.name === 'docs-preview' - ) ) + pullRequest.labels?.some( + ( /** @type {Record} */ label ) => + label.name === 'docs-preview' + ) ) { - throw new Error( 'A newer pull request state superseded this event.' ); - } - return pullRequest; -} - -function sessionFrom( options ) { - const context = lifecycleContext( options ); - const api = - options.api || - new GitHubApi( - options.repository, - options.token, - options.fetchImplementation - ); - const authorize = async ( state, labelMustBeAbsent = false ) => { - const pullRequest = await api.getPullRequest( - context.pullRequestNumber - ); - try { - return assertCurrent( - context, - pullRequest, - state, - labelMustBeAbsent - ); - } catch ( error ) { - throw new LifecycleSuperseded( error.message ); - } - }; - return { - api, - context, - repository: options.repository, - fetchImplementation: options.fetchImplementation || globalThis.fetch, - warning: - options.warning || - ( ( message ) => process.stderr.write( `${ message }\n` ) ), - authorize, - }; -} - -async function authorizeStaleMutation( session, expectedComment ) { - await session.authorize( 'open', true ); - const currentComment = await session.api.findPreviewComment( - session.context.pullRequestNumber - ); - if ( - currentComment?.id !== expectedComment.id || - currentComment.body !== expectedComment.body - ) { - throw new LifecycleSuperseded( - 'A newer preview state superseded this stale event.' - ); - } -} - -async function markStale( session ) { - try { - await session.authorize( 'open', true ); - } catch ( error ) { - if ( error instanceof LifecycleSuperseded ) { - return { status: 'ignored' }; - } - throw error; + return { status: 'ignored' }; } - const comment = await session.api.findPreviewComment( - session.context.pullRequestNumber - ); + const comment = await api.findPreviewComment( context.pullRequestNumber ); if ( ! comment ) { return { status: 'ignored' }; } - const commentSource = readPreviewCommentSource( comment.body ); + const source = readPreviewCommentSource( comment.body ); if ( - isTerminalComment( comment.body ) && - commentSource?.repository === session.context.sourceRepository && - commentSource.sha === session.context.sourceSha + source?.repository === context.sourceRepository && + source?.sha === context.sourceSha ) { return { status: 'ignored' }; } - let preview = await findPreviousPreview( session ); - if ( ! preview ) { - preview = readPreviewCommentSuccess( comment.body ); - } - if ( ! preview ) { - const previous = commentSource; - if ( ! previous ) { - return { status: 'unavailable' }; - } - await authorizeStaleMutation( session, comment ); - await session.api.updateComment( - comment.id, - renderPreviewComment( { - status: 'stale-unavailable', - previousRepository: previous.repository, - previousSha: previous.sha, - currentRepository: session.context.sourceRepository, - currentSha: session.context.sourceSha, - } ) - ); + const preview = await findLatestPublishedPreview( + api, + context.pullRequestNumber + ); + if ( preview ) { + await api.updateComment( comment.id, staleComment( preview, context ) ); return { status: 'stale' }; } - await authorizeStaleMutation( session, comment ); - await session.api.updateComment( + if ( ! source ) { + return { status: 'unavailable' }; + } + await api.updateComment( comment.id, - renderPreviewComment( { - status: 'stale', - preview, - currentRepository: session.context.sourceRepository, - currentSha: session.context.sourceSha, - } ) + staleUnavailableComment( source, context ) ); return { status: 'stale' }; } -async function settle( operations ) { - const errors = []; - for ( const operation of operations ) { - try { - await operation(); - } catch ( error ) { - errors.push( error ); - } - } - return errors; -} - -async function expire( session ) { - try { - await session.authorize( 'closed' ); - } catch ( error ) { - if ( error instanceof LifecycleSuperseded ) { - return { status: 'superseded' }; - } - throw error; - } - const release = await session.api.getRelease(); - const prefix = `code-reference-pr-${ session.context.pullRequestNumber }-`; +/** + * @param {Record} api + * @param {Record} context + */ +async function expire( api, context ) { + const release = await api.getRelease(); + const prefix = assetPrefix( context.pullRequestNumber ); const assets = release - ? ( await session.api.listReleaseAssets( release.id ) ).filter( - ( asset ) => asset.name.startsWith( prefix ) + ? ( await api.listReleaseAssets( release.id ) ).filter( + ( /** @type {Record} */ asset ) => + asset.name.startsWith( prefix ) ) : []; - const cacheRef = `refs/pull/${ session.context.pullRequestNumber }/merge`; - const caches = ( await session.api.listActionCaches( cacheRef ) ).filter( - ( cache ) => - cache.ref === cacheRef && cache.key.startsWith( CACHE_PREFIX ) - ); - const comment = await session.api.findPreviewComment( - session.context.pullRequestNumber + for ( const asset of assets ) { + await api.deleteReleaseAsset( asset.id ); + } + const caches = ( + await api.listActionCaches( + `refs/pull/${ context.pullRequestNumber }/merge` + ) + ).filter( ( /** @type {Record} */ cache ) => + cache.key.startsWith( CACHE_PREFIX ) ); - const assetErrors = await settle( - assets.map( ( asset ) => async () => { - await session.authorize( 'closed' ); - await session.api.deleteReleaseAsset( asset.id ); - } ) - ); - const cacheErrors = await settle( - caches.map( ( cache ) => async () => { - await session.authorize( 'closed' ); - await session.api.deleteActionCache( cache.id ); - } ) - ); - let commentErrors = []; - if ( comment && assetErrors.length === 0 ) { - commentErrors = await settle( [ - async () => { - await session.authorize( 'closed' ); - await session.api.updateComment( - comment.id, - renderPreviewComment( { status: 'expired' } ) - ); - }, - ] ); + for ( const cache of caches ) { + await api.deleteActionCache( cache.id ); } - const errors = [ ...assetErrors, ...cacheErrors, ...commentErrors ]; - if ( errors.length > 0 ) { - throw new AggregateError( errors, 'Pull request cleanup failed.' ); + // Last, so that a comment saying the preview expired is only posted once + // the preview really is gone. + const comment = await api.findPreviewComment( context.pullRequestNumber ); + if ( comment ) { + await api.updateComment( comment.id, expiredComment() ); } - return { - status: 'expired', - deletedAssets: assets.length, - deletedCaches: caches.length, - }; + return { status: 'expired' }; } +/** + * @param {Record} options + * @returns {Promise>} + */ export async function managePullRequest( options ) { - const session = sessionFrom( options ); - return session.context.action === 'synchronize' - ? markStale( session ) - : expire( session ); + assertDeploymentEnabled( options.repository, options.stagingVariable ); + const action = options.event?.action; + if ( action !== 'synchronize' && action !== 'closed' ) { + throw new Error( `Unsupported pull request action ${ action }.` ); + } + const pullRequest = options.event.pull_request; + const api = + options.api || new GitHubApi( options.repository, options.token ); + const context = { + pullRequestNumber: pullRequest.number, + sourceRepository: pullRequest.head.repo?.full_name || null, + sourceSha: pullRequest.head.sha, + }; + return action === 'synchronize' + ? markStale( api, context, pullRequest ) + : expire( api, context ); } async function main() { - const event = JSON.parse( await readFile( process.env.GITHUB_EVENT_PATH ) ); const result = await managePullRequest( { repository: process.env.GITHUB_REPOSITORY, stagingVariable: process.env.DOCS_PREVIEW_STAGING, token: process.env.GITHUB_TOKEN, - event, + event: await readWorkflowEvent(), } ); process.stdout.write( `Code Reference lifecycle: ${ result.status }.\n` ); } diff --git a/.github/scripts/docs-playground-preview/php/base-policy.php b/.github/scripts/docs-playground-preview/php/base-policy.php index eee16d1a53607..0341596257704 100644 --- a/.github/scripts/docs-playground-preview/php/base-policy.php +++ b/.github/scripts/docs-playground-preview/php/base-policy.php @@ -13,31 +13,7 @@ define( 'WP_ENVIRONMENT_TYPE', 'local' ); } +// The wporg parent theme renders its header and footer only when this is set. if ( ! defined( 'FEATURE_2021_GLOBAL_HEADER_FOOTER' ) ) { define( 'FEATURE_2021_GLOBAL_HEADER_FOOTER', true ); } - -add_filter( - 'pre_option_blog_public', - static function () { - return '0'; - } -); - -add_filter( 'wp_is_application_passwords_available', '__return_false' ); - -foreach ( array( 'update_core', 'update_plugins', 'update_themes' ) as $transient ) { - add_filter( - "pre_site_transient_{$transient}", - static function () { - return (object) array( - 'last_checked' => time(), - 'checked' => array(), - 'no_update' => array(), - 'response' => array(), - 'translations' => array(), - 'updates' => array(), - ); - } - ); -} diff --git a/.github/scripts/docs-playground-preview/php/complete-import.php b/.github/scripts/docs-playground-preview/php/complete-import.php index 4fab5650fbcda..a3c5bcf3cc1bd 100644 --- a/.github/scripts/docs-playground-preview/php/complete-import.php +++ b/.github/scripts/docs-playground-preview/php/complete-import.php @@ -3,46 +3,29 @@ * Completes the Code Reference import before final runtime policy is applied. */ -if ( ! class_exists( 'DevHub_Parser' ) ) { - throw new RuntimeException( 'DevHub_Parser is unavailable after import.' ); -} - update_option( 'wp_parser_root_import_dir', '/tmp/docs-preview-source' ); if ( ! DevHub_Parser::cache_source_code() ) { throw new RuntimeException( 'Source-code caching did not complete.' ); } -$active = array_values( - array_diff( - get_option( 'active_plugins', array() ), - array( 'phpdoc-parser/plugin.php' ) +// The Blueprint deletes the parser plugin once the import is done. +update_option( + 'active_plugins', + array_values( + array_diff( + get_option( 'active_plugins', array() ), + array( 'phpdoc-parser/plugin.php' ) + ) ) ); -sort( $active ); -update_option( 'active_plugins', $active ); - -$post_types = array( - 'classes' => 'wp-parser-class', - 'methods' => 'wp-parser-method', - 'functions' => 'wp-parser-function', - 'hooks' => 'wp-parser-hook', -); -$counts = array(); -foreach ( $post_types as $name => $post_type ) { - $counts[ $name ] = (int) wp_count_posts( $post_type )->publish; -} -$marker = array( - 'schemaVersion' => 1, - 'stage' => 'complete-import', - 'counts' => $counts, -); -$written = file_put_contents( +/* + * The build reads this marker through the health route: it is the only proof + * that the import ran to the end rather than stopping partway. + */ +file_put_contents( WP_CONTENT_DIR . '/docs-preview-import.json', - wp_json_encode( $marker, JSON_PRETTY_PRINT ) . "\n" + wp_json_encode( array( 'stage' => 'complete-import' ) ) . "\n" ); -if ( false === $written ) { - throw new RuntimeException( 'Unable to record the completed import.' ); -} flush_rewrite_rules( false ); diff --git a/.github/scripts/docs-playground-preview/php/configure-base.php b/.github/scripts/docs-playground-preview/php/configure-base.php index eb8cfa0cc48c0..0a5ee8457b25e 100644 --- a/.github/scripts/docs-playground-preview/php/configure-base.php +++ b/.github/scripts/docs-playground-preview/php/configure-base.php @@ -11,6 +11,7 @@ 'posts-to-posts/posts-to-posts.php', ); +// Writing active_plugins directly would skip a bundle that failed to unpack. foreach ( $active_plugins as $plugin ) { $result = validate_plugin( $plugin ); if ( is_wp_error( $result ) ) { @@ -18,16 +19,11 @@ } } -sort( $active_plugins ); update_option( 'active_plugins', $active_plugins ); switch_theme( 'wporg-developer-2023' ); update_option( 'permalink_structure', '/%year%/%monthnum%/%postname%/' ); -$ensure_page = static function ( $slug, $title ) { - $page = get_page_by_path( $slug, OBJECT, 'page' ); - if ( $page ) { - return $page->ID; - } +$create_page = static function ( $slug, $title ) { $result = wp_insert_post( array( 'post_type' => 'page', @@ -44,49 +40,24 @@ }; update_option( 'show_on_front', 'page' ); -update_option( 'page_on_front', $ensure_page( 'home', 'Home' ) ); -$ensure_page( 'reference', 'Reference' ); - -$navigation = ''; -$existing = get_page_by_path( 'reference-api-menu', OBJECT, 'wp_navigation' ); -$nav_args = array( - 'post_title' => 'Reference API Menu', - 'post_name' => 'reference-api-menu', - 'post_type' => 'wp_navigation', - 'post_status' => 'publish', - 'post_content' => $navigation, +update_option( 'page_on_front', $create_page( 'home', 'Home' ) ); +$create_page( 'reference', 'Reference' ); + +// The theme templates reference this navigation menu by its wordpress.org ID, +// so the menu has to be created with that exact post ID. +$result = wp_insert_post( + array( + 'import_id' => 148843, + 'post_title' => 'Reference API Menu', + 'post_name' => 'reference-api-menu', + 'post_type' => 'wp_navigation', + 'post_status' => 'publish', + 'post_content' => '', + ), + true ); -if ( $existing ) { - $nav_args['ID'] = $existing->ID; - $result = wp_update_post( $nav_args, true ); -} else { - $nav_args['import_id'] = 148843; - $result = wp_insert_post( $nav_args, true ); -} if ( is_wp_error( $result ) ) { throw new RuntimeException( $result->get_error_message() ); } -global $wpdb; -$reference_types = array( - 'wp-parser-class', - 'wp-parser-function', - 'wp-parser-hook', - 'wp-parser-method', - 'wp-parser-source-file', -); -$count = $wpdb->get_var( - $wpdb->prepare( - "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type IN ( %s, %s, %s, %s, %s )", - $reference_types[0], - $reference_types[1], - $reference_types[2], - $reference_types[3], - $reference_types[4] - ) -); -if ( 0 !== (int) $count ) { - throw new RuntimeException( 'Invariant base contains generated reference posts.' ); -} - flush_rewrite_rules( false ); diff --git a/.github/scripts/docs-playground-preview/php/preview-runtime.php b/.github/scripts/docs-playground-preview/php/preview-runtime.php new file mode 100644 index 0000000000000..2cf8c49f3d1c8 --- /dev/null +++ b/.github/scripts/docs-playground-preview/php/preview-runtime.php @@ -0,0 +1,93 @@ + + + 'GET', + 'permission_callback' => '__return_true', + 'callback' => static function () { + $marker_file = WP_CONTENT_DIR . '/docs-preview-import.json'; + $network = wp_remote_get( 'https://api.wordpress.org/' ); + + return array( + 'import' => file_exists( $marker_file ) + ? json_decode( file_get_contents( $marker_file ), true ) + : null, + 'outboundNetworkDisabled' => is_wp_error( $network ) && 'docs_preview_network_disabled' === $network->get_error_code(), + 'constants' => array( + 'DISABLE_WP_CRON' => defined( 'DISABLE_WP_CRON' ) && true === DISABLE_WP_CRON, + 'AUTOMATIC_UPDATER_DISABLED' => defined( 'AUTOMATIC_UPDATER_DISABLED' ) && true === AUTOMATIC_UPDATER_DISABLED, + 'WP_AUTO_UPDATE_CORE' => defined( 'WP_AUTO_UPDATE_CORE' ) && false === WP_AUTO_UPDATE_CORE, + 'DISALLOW_FILE_MODS' => defined( 'DISALLOW_FILE_MODS' ) && true === DISALLOW_FILE_MODS, + ), + ); + }, + ) + ); + } +); diff --git a/.github/scripts/docs-playground-preview/publish-trunk.mjs b/.github/scripts/docs-playground-preview/publish-trunk.mjs deleted file mode 100644 index e7c942c5bc2df..0000000000000 --- a/.github/scripts/docs-playground-preview/publish-trunk.mjs +++ /dev/null @@ -1,351 +0,0 @@ -#!/usr/bin/env node - -import { isDeepStrictEqual } from 'node:util'; -import { readFile } from 'node:fs/promises'; -import { pathToFileURL } from 'node:url'; -import path from 'node:path'; - -import { GitHubApi } from './lib/github.mjs'; -import { - metadataAssetName, - releaseAssetUrl, - validatePublicSnapshot, -} from './lib/publication.mjs'; -import { validateCandidateFile } from './lib/publisher.mjs'; -import { - TRUNK_POINTER_ASSET, - TRUNK_POINTER_REF, - assertLatestTrunkAuthorized, - createTrunkBlueprint, - createTrunkPublishedMetadata, - inspectTrunkHandoff, - trunkBlueprintCommitUrl, - trunkStableBlueprintUrl, - validatePublicBlueprint, - validatePublishedTrunkMetadata, - validateTrunkPublicationContext, -} from './lib/trunk.mjs'; - -class SupersededTrunkRun extends Error {} - -async function readHandoff( directory ) { - return JSON.parse( - await readFile( path.join( directory, 'build.json' ), 'utf8' ) - ); -} - -async function establishSession( options ) { - const api = - options.api || - new GitHubApi( - options.repository, - options.token, - options.fetchImplementation - ); - const run = await api.getRun( options.triggerRunId ); - const base = { - repository: options.repository, - stagingVariable: options.stagingVariable, - triggerRunId: options.triggerRunId, - triggerRunAttempt: options.triggerRunAttempt, - run, - }; - const context = validateTrunkPublicationContext( base ); - return { - api, - context, - repository: options.repository, - fetchImplementation: options.fetchImplementation || globalThis.fetch, - warning: - options.warning || - ( ( message ) => process.stderr.write( `${ message }\n` ) ), - now: options.now || ( () => new Date().toISOString() ), - handoffDirectory: path.resolve( options.handoffDirectory ), - authorize: async () => { - const currentRun = await api.getRun( options.triggerRunId ); - const latestRun = await api.latestTrunkPreviewRun(); - const trunkHeadSha = await api.getTrunkHeadSha(); - try { - return assertLatestTrunkAuthorized( { - ...base, - run: currentRun, - latestRun, - trunkHeadSha, - } ); - } catch ( error ) { - throw new SupersededTrunkRun( error.message ); - } - }, - }; -} - -async function ensureRelease( session ) { - let release = await session.api.getRelease(); - if ( release ) { - return release; - } - await session.authorize(); - try { - release = await session.api.createRelease(); - } catch ( error ) { - release = await session.api.getRelease(); - if ( ! release ) { - throw error; - } - } - return release; -} - -async function validatePublicMetadata( session, asset, expected ) { - const response = await session.fetchImplementation( - asset.browser_download_url - ); - if ( response.status !== 200 ) { - throw new Error( - `Public trunk metadata returned HTTP ${ response.status }.` - ); - } - const metadata = validatePublishedTrunkMetadata( - await response.json(), - session.repository, - session.context.sourceSha - ); - if ( ! isDeepStrictEqual( metadata, expected ) ) { - throw new Error( - 'Public trunk metadata does not match the candidate.' - ); - } - return metadata; -} - -async function cleanupUploaded( session, uploaded ) { - for ( const asset of uploaded ) { - try { - await session.api.deleteReleaseAsset( asset.id ); - } catch ( error ) { - session.warning( - `Cannot remove failed trunk candidate ${ asset.name }: ${ error.message }` - ); - } - } -} - -function referenceSha( reference ) { - return reference?.object?.sha || null; -} - -async function createPointerCommit( session, previousRef, blueprint ) { - const content = `${ JSON.stringify( blueprint, null, 2 ) }\n`; - await session.authorize(); - const blob = await session.api.createGitBlob( content ); - await session.authorize(); - const tree = await session.api.createGitTree( - TRUNK_POINTER_ASSET, - blob.sha - ); - await session.authorize(); - const commit = await session.api.createGitCommit( - `Code Reference preview for ${ session.context.sourceSha }`, - tree.sha, - referenceSha( previousRef ) - ); - await validatePublicBlueprint( - trunkBlueprintCommitUrl( session.repository, commit.sha ), - blueprint, - session.fetchImplementation - ); - return commit; -} - -async function resolvePointerMutation( session, previousSha, candidateSha ) { - let current; - try { - current = referenceSha( - await session.api.getGitReference( TRUNK_POINTER_REF ) - ); - } catch ( error ) { - error.pointerStateUnknown = true; - throw error; - } - if ( current === candidateSha ) { - return true; - } - if ( current === previousSha ) { - return false; - } - const error = new Error( 'The stable trunk pointer has unknown state.' ); - error.pointerStateUnknown = true; - throw error; -} - -async function moveStablePointer( session, previousRef, commit, transaction ) { - const previousSha = referenceSha( previousRef ); - let mutationError = null; - try { - await session.authorize(); - if ( previousSha ) { - await session.api.updateGitReference( - TRUNK_POINTER_REF, - commit.sha - ); - } else { - await session.api.createGitReference( - TRUNK_POINTER_REF, - commit.sha - ); - } - } catch ( error ) { - mutationError = error; - } - const moved = await resolvePointerMutation( - session, - previousSha, - commit.sha - ); - if ( ! moved ) { - const error = - mutationError || - new Error( 'The stable trunk pointer mutation has unknown state.' ); - error.pointerStateUnknown = true; - throw error; - } - transaction.pointerPublished = true; -} - -async function cleanupOldTrunkAssets( session, release, keepIds ) { - const assets = await session.api.listReleaseAssets( release.id ); - for ( const asset of assets ) { - if ( - asset.name.startsWith( 'code-reference-trunk-' ) && - ! keepIds.has( asset.id ) - ) { - await session.authorize(); - await session.api.deleteReleaseAsset( asset.id ); - } - } -} - -async function publishCandidate( session, metadata, uploaded, transaction ) { - const candidateFile = path.join( - session.handoffDirectory, - metadata.snapshotFilename - ); - await validateCandidateFile( metadata, candidateFile ); - const release = await ensureRelease( session ); - const previousRef = await session.api.getGitReference( TRUNK_POINTER_REF ); - - const snapshotBytes = await readFile( candidateFile ); - await session.authorize(); - const snapshotAsset = await session.api.uploadReleaseAsset( - release.id, - metadata.snapshotFilename, - snapshotBytes, - 'application/zip' - ); - uploaded.push( snapshotAsset ); - - const published = createTrunkPublishedMetadata( - metadata, - session.repository, - session.now() - ); - const metadataBytes = Buffer.from( - `${ JSON.stringify( published, null, 2 ) }\n` - ); - await session.authorize(); - const metadataAsset = await session.api.uploadReleaseAsset( - release.id, - metadataAssetName( metadata.snapshotFilename ), - metadataBytes, - 'application/json' - ); - uploaded.push( metadataAsset ); - - const blueprint = createTrunkBlueprint( published ); - await validatePublicSnapshot( - releaseAssetUrl( session.repository, snapshotAsset.name ), - { - bytes: metadata.snapshotBytes, - sha256: metadata.snapshotSha256, - maximumBytes: 104857600, - }, - session.fetchImplementation - ); - await validatePublicMetadata( session, metadataAsset, published ); - const pointerCommit = await createPointerCommit( - session, - previousRef, - blueprint - ); - await moveStablePointer( session, previousRef, pointerCommit, transaction ); - await validatePublicBlueprint( - trunkStableBlueprintUrl( session.repository ), - blueprint, - session.fetchImplementation - ); - await cleanupOldTrunkAssets( - session, - release, - new Set( [ snapshotAsset.id, metadataAsset.id ] ) - ); - return { status: 'ready', published, pointerCommit: pointerCommit.sha }; -} - -export async function publishTrunk( options ) { - const session = await establishSession( options ); - const uploaded = []; - const transaction = { pointerPublished: false }; - try { - await session.authorize(); - if ( options.artifactAvailable === false ) { - throw new Error( 'The trunk publisher handoff is unavailable.' ); - } - const metadata = await readHandoff( session.handoffDirectory ); - const handoff = inspectTrunkHandoff( metadata, session.context ); - if ( handoff.kind === 'failed' ) { - throw new Error( - metadata.buildError || 'Trunk preview build failed.' - ); - } - if ( handoff.kind === 'invalid' ) { - return { status: 'invalid' }; - } - return await publishCandidate( - session, - metadata, - uploaded, - transaction - ); - } catch ( error ) { - if ( ! transaction.pointerPublished && ! error.pointerStateUnknown ) { - await cleanupUploaded( session, uploaded ); - } - if ( error instanceof SupersededTrunkRun ) { - return { status: 'superseded' }; - } - throw error; - } -} - -async function main() { - const event = JSON.parse( await readFile( process.env.GITHUB_EVENT_PATH ) ); - const result = await publishTrunk( { - repository: process.env.GITHUB_REPOSITORY, - stagingVariable: process.env.DOCS_PREVIEW_STAGING, - triggerRunId: event.workflow_run.id, - triggerRunAttempt: event.workflow_run.run_attempt, - handoffDirectory: process.argv[ 2 ] || 'handoff', - artifactAvailable: process.env.HANDOFF_DOWNLOAD_RESULT === 'success', - token: process.env.GITHUB_TOKEN, - } ); - process.stdout.write( - `Code Reference trunk publisher: ${ result.status }.\n` - ); -} - -if ( import.meta.url === pathToFileURL( process.argv[ 1 ] ).href ) { - main().catch( ( error ) => { - process.stderr.write( `${ error.stack || error }\n` ); - process.exitCode = 1; - } ); -} diff --git a/.github/scripts/docs-playground-preview/publish.mjs b/.github/scripts/docs-playground-preview/publish.mjs index 3a7cfd92496fe..38a56ab2acd80 100644 --- a/.github/scripts/docs-playground-preview/publish.mjs +++ b/.github/scripts/docs-playground-preview/publish.mjs @@ -1,403 +1,506 @@ #!/usr/bin/env node +// Publishes one Code Reference snapshot built by the preview build workflow. +// Publishing a pull request preview and publishing the trunk preview are the +// same operation over a different identity: a pull request preview is announced +// in a comment on the pull request, and the trunk preview is announced by moving +// the stable Blueprint that the repository README links. +// +// This runs from the default branch with write access, and the handoff it reads +// was produced by the build job from pull request code. Nothing the handoff says +// about its own identity is used: asset names, the published commit and the +// comment all come from the workflow run instead. The four fields it does take +// describe the runtime and the build, and each is held to a shape first. + +import { createHash } from 'node:crypto'; import { readFile } from 'node:fs/promises'; import { pathToFileURL } from 'node:url'; import path from 'node:path'; -import { GitHubApi } from './lib/github.mjs'; +import { failedComment, readyComment } from './lib/comment.mjs'; +import { + GitHubApi, + assertDeploymentEnabled, + readWorkflowEvent, +} from './lib/github.mjs'; import { - assertLatestAuthorized, + HANDOFF_METADATA, + HANDOFF_REUSE, + HANDOFF_SNAPSHOT, + TRUNK_POINTER_ASSET, + TRUNK_POINTER_REF, + assertSnapshotFits, + assetPrefix, createPublishedMetadata, - inspectHandoff, - readPreviewCommentSuccess, - renderPreviewComment, - validateCandidateFile, - validatePublicationContext, -} from './lib/publisher.mjs'; -import { metadataAssetName } from './lib/publication.mjs'; -import { findPreviousPreview, loadPublishedPreview } from './lib/published.mjs'; + createTrunkBlueprint, + findLatestPublishedPreview, + findLatestSnapshot, + metadataAssetName, + snapshotAssetName, +} from './lib/preview.mjs'; -class SupersededRun extends Error {} +// A trunk generation is one snapshot and its metadata, named after the run that +// produced them. +const TRUNK_GENERATION = + /^(code-reference-trunk-[0-9a-f]{40}-(\d+)-(\d+))\.(?:zip|json)$/; -export function formatFailure( error ) { - const description = error.stack || String( error ); - if ( ! ( error instanceof AggregateError ) ) { - return description; - } - return [ - description, - ...error.errors.map( - ( cause, index ) => - `Cause ${ index + 1 }:\n${ formatFailure( cause ) }` - ), - ].join( '\n' ); -} +class Superseded extends Error {} -async function readHandoff( directory ) { - return JSON.parse( - await readFile( path.join( directory, 'build.json' ), 'utf8' ) - ); +/** + * These four values come from a file the build wrote while running pull request + * code. The first two are stringified into the Playground URL that the comment + * renders inside a markdown link, where an unescaped ")" would end the link and + * spill the rest into the comment body; all four are copied into the published + * metadata asset, which is this preview's record of what it ran. + * + * @param {Record} handoff + */ +function assertPublishableHandoff( handoff ) { + /** @type {[string, unknown, RegExp][]} */ + const fields = [ + [ 'phpVersion', handoff.phpVersion, /^\d+\.\d+(?:\.\d+)?$/ ], + [ + 'resolvedWordPressBeta.version', + handoff.resolvedWordPressBeta?.version, + /^\d+\.\d+(?:\.\d+)?(?:-(?:beta|RC)\d+)?$/, + ], + [ + 'dependencyManifestDigest', + handoff.dependencyManifestDigest, + /^[0-9a-f]{64}$/, + ], + [ + 'generationTimestamp', + handoff.generationTimestamp, + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/, + ], + ]; + for ( const [ name, value, shape ] of fields ) { + if ( typeof value !== 'string' || ! shape.test( value ) ) { + throw new Error( `The build handoff reports no valid ${ name }.` ); + } + } } -async function establishSession( options ) { - const api = - options.api || - new GitHubApi( - options.repository, - options.token, - options.fetchImplementation - ); +/** + * The run that triggered this publication must still be the current attempt and + * the newest build for its source, and a pull request must still be asking for + * a preview. Anything else means a later run owns the published preview. + * + * @param {Record} api + * @param {Record} options + */ +async function resolveContext( api, options ) { const run = await api.getRun( options.triggerRunId ); - const pullRequest = await api.findPullRequestForRun( run ); - if ( ! pullRequest ) { - return null; + if ( run.run_attempt !== Number( options.triggerRunAttempt ) ) { + throw new Superseded( 'The build run has a newer attempt.' ); + } + const pullRequest = options.trunk + ? null + : await api.findPullRequestForRun( run ); + if ( ! options.trunk && ! pullRequest ) { + throw new Superseded( + 'The build run matches no open pull request against trunk.' + ); + } + const latest = options.trunk + ? await api.latestTrunkBuildRun() + : await api.latestPullRequestBuildRun( run ); + if ( latest?.id !== run.id ) { + throw new Superseded( 'A newer build superseded this attempt.' ); + } + // The label is the request to publish, and publishing removes it, so only a + // first attempt is required to still carry it. + if ( + pullRequest && + run.run_attempt === 1 && + ! pullRequest.labels?.some( + ( /** @type {Record} */ label ) => + label.name === 'docs-preview' + ) + ) { + throw new Superseded( 'The docs-preview label was removed.' ); } - const base = { - repository: options.repository, - stagingVariable: options.stagingVariable, - triggerRunId: options.triggerRunId, - triggerRunAttempt: options.triggerRunAttempt, - run, - pullRequest, - }; - const context = validatePublicationContext( base ); return { - api, - context, repository: options.repository, - fetchImplementation: options.fetchImplementation || globalThis.fetch, - warning: - options.warning || - ( ( message ) => process.stderr.write( `${ message }\n` ) ), - now: options.now || ( () => new Date().toISOString() ), - handoffDirectory: path.resolve( options.handoffDirectory ), - authorize: async () => { - const currentRun = await api.getRun( options.triggerRunId ); - const currentPullRequest = await api.getPullRequest( - context.pullRequestNumber - ); - const latestRun = await api.latestPreviewRun( currentRun ); - try { - return assertLatestAuthorized( { - ...base, - run: currentRun, - pullRequest: currentPullRequest, - latestRun, - } ); - } catch ( error ) { - throw new SupersededRun( error.message ); - } - }, + pullRequestNumber: pullRequest ? pullRequest.number : null, + sourceRepository: pullRequest + ? pullRequest.head.repo.full_name + : options.repository, + sourceSha: pullRequest ? pullRequest.head.sha : run.head_sha, + workflowRunId: run.id, + workflowRunAttempt: run.run_attempt, + runUrl: `https://github.com/${ options.repository }/actions/runs/${ run.id }`, }; } -async function ensureRelease( session ) { - let release = await session.api.getRelease(); +/** + * @param {Record} api + */ +async function ensureRelease( api ) { + const release = await api.getRelease(); if ( release ) { return release; } - await session.authorize(); try { - release = await session.api.createRelease(); + return await api.createRelease(); } catch ( error ) { - release = await session.api.getRelease(); - if ( ! release ) { + // A pull request and a trunk publication can race to create the shared + // release; the loser reads the winner's release. + const created = await api.getRelease(); + if ( ! created ) { throw error; } + return created; } - return release; } -async function upsertComment( session, body ) { - const comment = await session.api.findPreviewComment( - session.context.pullRequestNumber - ); - await session.authorize(); +/** + * @param {Record} api + * @param {number} pullRequestNumber + * @param {string} body + */ +async function upsertComment( api, pullRequestNumber, body ) { + const comment = await api.findPreviewComment( pullRequestNumber ); if ( comment ) { - await session.api.updateComment( comment.id, body ); - } else { - await session.api.createComment( - session.context.pullRequestNumber, - body - ); - } -} - -async function removeRequestLabel( session ) { - await session.authorize(); - await session.api.removeLabel( session.context.pullRequestNumber ); -} - -async function cleanupOldAssets( session, assets, keep ) { - const prefix = `code-reference-pr-${ session.context.pullRequestNumber }-`; - for ( const asset of assets ) { - if ( asset.name.startsWith( prefix ) && ! keep.has( asset.name ) ) { - await session.authorize(); - await session.api.deleteReleaseAsset( asset.id ); + try { + await api.updateComment( comment.id, body ); + return; + } catch ( error ) { + // The comment was deleted between the two calls. Post a + // replacement rather than report a publication that worked as a + // failure. + if ( /** @type {any} */ ( error )?.status !== 404 ) { + throw error; + } } } + await api.createComment( pullRequestNumber, body ); } -async function cleanupFailedCandidate( session, assets ) { - for ( const asset of assets ) { - await session.authorize(); - await session.api.deleteReleaseAsset( asset.id ); +/** + * The label is the request to build, and this run has answered it. A push while + * this run was publishing carried the label with it, so it queued a build of + * its own, and that build is allowed to publish only while the label is still + * there. The label therefore goes only while the head is still the commit this + * run answered. + * + * @param {Record} api + * @param {Record} context + */ +async function releaseRequestLabel( api, context ) { + const pullRequest = await api.getPullRequest( context.pullRequestNumber ); + if ( pullRequest.head.sha === context.sourceSha ) { + await api.removeLabel( context.pullRequestNumber ); } } -async function runIndependently( operations, message ) { - const errors = []; - for ( const operation of operations ) { - try { - await operation(); - } catch ( error ) { - errors.push( error ); +/** + * @param {Record} api + * @param {Record} context + * @param {Record} release + * @param {Record} published + * @param {Set} keep + */ +async function announceOnPullRequest( api, context, release, published, keep ) { + // The comment first, so that no window has it naming an asset that is + // already deleted, or the label gone with nothing announced. + await upsertComment( + api, + context.pullRequestNumber, + readyComment( published, context.runUrl ) + ); + // The preview is live once the comment names it. Deleting the superseded + // assets and giving back the label are housekeeping, and a failure in + // either must not replace that announcement with a failure report. + try { + const prefix = assetPrefix( context.pullRequestNumber ); + for ( const asset of await api.listReleaseAssets( release.id ) ) { + if ( asset.name.startsWith( prefix ) && ! keep.has( asset.name ) ) { + await api.deleteReleaseAsset( asset.id ); + } } - } - if ( - errors.length === 1 || - ( errors.length > 1 && - errors.every( ( error ) => error instanceof SupersededRun ) ) - ) { - throw errors[ 0 ]; - } - if ( errors.length > 1 ) { - throw new AggregateError( errors, message ); + await releaseRequestLabel( api, context ); + } catch ( error ) { + process.stderr.write( + `::warning::The preview is published, but tidying up after it failed: ${ + error instanceof Error ? error.message : String( error ) + }\n` + ); } } -async function reportFailure( session, excluded = new Set() ) { - let previous = await findPreviousPreview( session, excluded ); - if ( ! previous ) { - const comment = await session.api.findPreviewComment( - session.context.pullRequestNumber - ); - previous = readPreviewCommentSuccess( comment?.body ); - } - const body = renderPreviewComment( { - status: 'failed', - sourceRepository: session.context.sourceRepository, - sourceSha: session.context.sourceSha, - at: session.now(), - runUrl: session.context.runUrl, - previous, - } ); - await runIndependently( - [ - () => upsertComment( session, body ), - () => removeRequestLabel( session ), - ], - 'Failure comment and label removal both failed.' +/** + * Points the stable Blueprint at the new snapshot by committing it to the + * pointer branch. The repository README links the raw URL of that file. + * + * @param {Record} api + * @param {Record} context + * @param {Record} release + * @param {Record} published + */ +async function announceOnTrunk( api, context, release, published ) { + const blueprint = createTrunkBlueprint( published ); + const previous = await api.getGitReference( TRUNK_POINTER_REF ); + const tree = await api.createGitTree( + TRUNK_POINTER_ASSET, + `${ JSON.stringify( blueprint, null, 2 ) }\n` ); + const commit = await api.createGitCommit( + `Code Reference preview for ${ context.sourceSha }`, + tree.sha, + previous?.object?.sha || null + ); + await api.setGitReference( + TRUNK_POINTER_REF, + commit.sha, + Boolean( previous ) + ); + await pruneTrunkGenerations( api, release ); + return commit.sha; } -async function finishReady( session, assets, keep ) { - try { - await runIndependently( - [ - () => cleanupOldAssets( session, assets, keep ), - () => removeRequestLabel( session ), - ], - 'Old-asset cleanup and label removal both failed.' - ); - } catch ( error ) { - error.preserveReadyComment = true; - throw error; +/** + * @param {Record} api + * @param {Record} release + */ +async function pruneTrunkGenerations( api, release ) { + /** @type {Map} */ + const generations = new Map(); + for ( const asset of await api.listReleaseAssets( release.id ) ) { + const match = asset.name.match( TRUNK_GENERATION ); + if ( ! match ) { + continue; + } + const generation = generations.get( match[ 1 ] ) || { + runId: Number( match[ 2 ] ), + runAttempt: Number( match[ 3 ] ), + assets: [], + }; + generation.assets.push( asset ); + generations.set( match[ 1 ], generation ); + } + // A warm CDN copy of the stable Blueprint can name the previous snapshot for + // minutes after the pointer moves, so the two newest generations survive. + const disposable = [ ...generations.values() ] + .sort( + ( left, right ) => + right.runId - left.runId || right.runAttempt - left.runAttempt + ) + .slice( 2 ); + for ( const generation of disposable ) { + for ( const asset of generation.assets ) { + await api.deleteReleaseAsset( asset.id ); + } } } -async function publishCandidate( session, metadata, uploadedAssets ) { - const candidateFile = path.join( - session.handoffDirectory, - metadata.snapshotFilename +/** + * @param {Record} api + * @param {Record} context + * @param {Record} handoff + * @param {string} handoffDirectory + */ +async function publishCandidate( api, context, handoff, handoffDirectory ) { + // The published name comes from this workflow run rather than from the + // build, so one build cannot write over another pull request's assets. + assertPublishableHandoff( handoff ); + const filename = snapshotAssetName( context ); + const bytes = await readFile( + path.join( handoffDirectory, HANDOFF_SNAPSHOT ) ); - await validateCandidateFile( metadata, candidateFile ); - const release = await ensureRelease( session ); - const oldAssets = await session.api.listReleaseAssets( release.id ); - const snapshotBytes = await readFile( candidateFile ); - await session.authorize(); - const snapshotAsset = await session.api.uploadReleaseAsset( + assertSnapshotFits( bytes.byteLength ); + const release = await ensureRelease( api ); + const snapshotAsset = await api.uploadReleaseAsset( release.id, - metadata.snapshotFilename, - snapshotBytes, + filename, + bytes, 'application/zip' ); - uploadedAssets.push( snapshotAsset ); - const published = createPublishedMetadata( - metadata, - session.repository, - session.now() - ); - const publishedBytes = Buffer.from( - `${ JSON.stringify( published, null, 2 ) }\n` - ); - await session.authorize(); - const metadataAsset = await session.api.uploadReleaseAsset( + const published = createPublishedMetadata( handoff, context, { + filename, + bytes: bytes.byteLength, + sha256: createHash( 'sha256' ).update( bytes ).digest( 'hex' ), + } ); + const metadataAsset = await api.uploadReleaseAsset( release.id, - metadataAssetName( metadata.snapshotFilename ), - publishedBytes, + metadataAssetName( filename ), + Buffer.from( `${ JSON.stringify( published, null, 2 ) }\n` ), 'application/json' ); - uploadedAssets.push( metadataAsset ); - const preview = await loadPublishedPreview( - session.repository, - session.context.pullRequestNumber, - metadataAsset, - snapshotAsset, - { - sourceRepository: session.context.sourceRepository, - sourceSha: session.context.sourceSha, - fetchImplementation: session.fetchImplementation, - } - ); - await upsertComment( - session, - renderPreviewComment( { - status: 'ready', - preview, - runUrl: session.context.runUrl, - } ) + if ( context.pullRequestNumber === null ) { + const pointerCommit = await announceOnTrunk( + api, + context, + release, + published + ); + return { status: 'ready', published, pointerCommit }; + } + await announceOnPullRequest( + api, + context, + release, + published, + new Set( [ snapshotAsset.name, metadataAsset.name ] ) ); - const keep = new Set( [ snapshotAsset.name, metadataAsset.name ] ); - await finishReady( session, oldAssets, keep ); - return { status: 'ready', preview }; + return { status: 'ready', published }; } -async function reuseCandidate( session, metadata ) { - const release = await session.api.getRelease(); - if ( ! release ) { - throw new Error( 'Reusable release is missing.' ); - } - const assets = await session.api.listReleaseAssets( release.id ); - const snapshotAsset = assets.find( - ( asset ) => asset.name === metadata.reusedSnapshot.assetName - ); - const metadataAsset = assets.find( - ( asset ) => asset.name === metadata.reusedSnapshot.metadataAssetName - ); - if ( ! snapshotAsset || ! metadataAsset ) { - throw new Error( 'Reusable release assets are missing.' ); - } - const preview = await loadPublishedPreview( - session.repository, - session.context.pullRequestNumber, - metadataAsset, - snapshotAsset, - { - sourceRepository: session.context.sourceRepository, - sourceSha: session.context.sourceSha, - fetchImplementation: session.fetchImplementation, - } +/** + * The build skipped because this commit already has a published preview, so + * only the comment has to catch up. The preview is looked up from the pull + * request and commit of this run, so a handoff cannot name another one. + * + * @param {Record} api + * @param {Record} context + */ +async function republishExisting( api, context ) { + const release = await api.getRelease(); + const assets = release ? await api.listReleaseAssets( release.id ) : []; + const snapshot = findLatestSnapshot( + assets, + assetPrefix( context.pullRequestNumber, context.sourceSha ) ); - for ( const name of [ - 'snapshotFilename', - 'snapshotBytes', - 'snapshotSha256', - 'generationTimestamp', - 'dependencyManifestDigest', - ] ) { - if ( preview[ name ] !== metadata[ name ] ) { - throw new Error( - `Reusable ${ name } does not match public metadata.` - ); - } + if ( ! snapshot ) { + throw new Error( 'The reused preview is no longer published.' ); } - await upsertComment( - session, - renderPreviewComment( { - status: 'ready', - preview, - runUrl: session.context.runUrl, - } ) + // findLatestSnapshot only returns a snapshot whose metadata asset exists. + const metadataAsset = assets.find( + ( /** @type {Record} */ asset ) => + asset.name === metadataAssetName( snapshot.name ) ); - await finishReady( - session, - assets, - new Set( [ snapshotAsset.name, metadataAsset.name ] ) + const published = await api.readAssetJson( metadataAsset ); + await announceOnPullRequest( + api, + context, + release, + published, + new Set( [ snapshot.name, metadataAsset.name ] ) ); - return { status: 'reused', preview }; + return { status: 'reused', published }; } -export async function publishPullRequest( options ) { - const session = await establishSession( options ); - if ( ! session ) { - return { status: 'superseded' }; +/** + * Best effort: the caller rethrows the failure that brought it here, so a + * problem reporting the failure must not replace it. + * + * @param {Record} api + * @param {Record} context + */ +async function reportFailure( api, context ) { + if ( context.pullRequestNumber === null ) { + return; + } + try { + const previous = await findLatestPublishedPreview( + api, + context.pullRequestNumber + ); + await upsertComment( + api, + context.pullRequestNumber, + failedComment( context, previous ) + ); + await releaseRequestLabel( api, context ); + } catch ( error ) { + process.stderr.write( + `::warning::Cannot report the failed preview: ${ + error instanceof Error ? error.message : String( error ) + }\n` + ); } - if ( await session.api.isSkippedPreviewBuild( session.context.run ) ) { - return { status: 'ignored' }; +} + +/** + * @param {Record} options + * @returns {Promise>} + */ +export async function publishPreview( options ) { + assertDeploymentEnabled( options.repository, options.stagingVariable ); + const api = + options.api || new GitHubApi( options.repository, options.token ); + let context; + try { + context = await resolveContext( api, options ); + } catch ( error ) { + if ( error instanceof Superseded ) { + process.stdout.write( `${ error.message }\n` ); + return { status: 'superseded' }; + } + throw error; } - const uploadedAssets = []; try { - await session.authorize(); if ( options.artifactAvailable === false ) { throw new Error( 'The publisher handoff artifact is unavailable.' ); } - const metadata = await readHandoff( session.handoffDirectory ); - const handoff = inspectHandoff( metadata, { - ...session.context, - latestRun: session.context.run, - } ); - if ( handoff.kind === 'failed' ) { - throw new Error( metadata.buildError || 'Preview build failed.' ); + const handoff = JSON.parse( + await readFile( + path.join( options.handoffDirectory, HANDOFF_METADATA ), + 'utf8' + ) + ); + if ( handoff.buildStatus === 'failed' ) { + throw new Error( + handoff.buildError || 'The preview build failed.' + ); } - if ( handoff.kind === 'invalid' ) { - await reportFailure( session ); + if ( handoff.validationStatus === 'failed' ) { + await reportFailure( api, context ); return { status: 'invalid' }; } - if ( handoff.kind === 'reuse' ) { - return await reuseCandidate( session, metadata ); + if ( handoff.handoffType === HANDOFF_REUSE ) { + return await republishExisting( api, context ); } - return await publishCandidate( session, metadata, uploadedAssets ); - } catch ( error ) { - if ( error instanceof SupersededRun ) { - return { status: 'superseded' }; - } - if ( error.preserveReadyComment ) { - throw error; - } - const excluded = new Set( - uploadedAssets.map( ( asset ) => asset.name ) + return await publishCandidate( + api, + context, + handoff, + options.handoffDirectory ); - try { - await cleanupFailedCandidate( session, uploadedAssets ); - } catch ( cleanupError ) { - session.warning( - `Cannot remove failed candidate: ${ cleanupError.message }` - ); - } - try { - await reportFailure( session, excluded ); - } catch ( reportError ) { - if ( reportError instanceof SupersededRun ) { - return { status: 'superseded' }; - } - throw new AggregateError( - [ error, reportError ], - 'Publication and failure reporting both failed.' - ); - } + } catch ( error ) { + await reportFailure( api, context ); throw error; } } -export function enforceValidationResult( result, enforcementVariable ) { - if ( result.status === 'invalid' && enforcementVariable === 'true' ) { +/** + * Fork pull request jobs cannot read the base repository's Actions variables, + * so the trusted publisher applies enforcement after it has reported the + * invalid candidate and preserved the live link. + * + * @param {Record} result + * @param {string | undefined} enforcementVariable + */ +export function enforceValidation( result, enforcementVariable ) { + if ( result.status !== 'invalid' ) { + return result; + } + if ( enforcementVariable === 'true' ) { throw new Error( 'Code Reference validation failed with DOCS_PREVIEW_ENFORCE enabled.' ); } + process.stdout.write( + '::warning::Code Reference validation failed; the published preview still points at the previous build.\n' + ); return result; } async function main() { - const event = JSON.parse( await readFile( process.env.GITHUB_EVENT_PATH ) ); - const result = enforceValidationResult( - await publishPullRequest( { + const [ mode, handoffDirectory = 'handoff' ] = process.argv.slice( 2 ); + if ( mode !== 'trunk' && mode !== 'pull-request' ) { + throw new Error( 'The first argument must be trunk or pull-request.' ); + } + const event = await readWorkflowEvent(); + const result = enforceValidation( + await publishPreview( { repository: process.env.GITHUB_REPOSITORY, stagingVariable: process.env.DOCS_PREVIEW_STAGING, + trunk: mode === 'trunk', triggerRunId: event.workflow_run.id, triggerRunAttempt: event.workflow_run.run_attempt, - handoffDirectory: process.argv[ 2 ] || 'handoff', + handoffDirectory: path.resolve( handoffDirectory ), artifactAvailable: process.env.HANDOFF_DOWNLOAD_RESULT === 'success', token: process.env.GITHUB_TOKEN, @@ -409,7 +512,7 @@ async function main() { if ( import.meta.url === pathToFileURL( process.argv[ 1 ] ).href ) { main().catch( ( error ) => { - process.stderr.write( `${ formatFailure( error ) }\n` ); + process.stderr.write( `${ error.stack || error }\n` ); process.exitCode = 1; } ); } diff --git a/.github/scripts/docs-playground-preview/reuse.mjs b/.github/scripts/docs-playground-preview/reuse.mjs index 05ebe2d952f7d..5ff429fa0d512 100644 --- a/.github/scripts/docs-playground-preview/reuse.mjs +++ b/.github/scripts/docs-playground-preview/reuse.mjs @@ -1,180 +1,85 @@ #!/usr/bin/env node +// Runs in the build job. When the commit under build already has a published +// preview, it writes a handoff that tells the publisher to point the comment at +// the existing assets, and the build workflow skips the rest of the build. +// +// Every failure here is reported as "not reusable" so that a problem reading the +// release only costs a rebuild. + import { mkdir, writeFile } from 'node:fs/promises'; import { pathToFileURL } from 'node:url'; import path from 'node:path'; +import { GitHubApi, readWorkflowEvent } from './lib/github.mjs'; import { - RELEASE_TAG, - createReuseHandoff, - metadataAssetName, - validatePublicSnapshot, - validateReusableMetadata, -} from './lib/publication.mjs'; - -function argumentsFrom( values ) { - const options = {}; - for ( let index = 0; index < values.length; index += 2 ) { - const name = values[ index ]; - const value = values[ index + 1 ]; - if ( ! name?.startsWith( '--' ) || ! value ) { - throw new Error( `Invalid argument ${ name || '(missing)' }.` ); - } - options[ - name - .slice( 2 ) - .replace( /-([a-z])/g, ( _, letter ) => letter.toUpperCase() ) - ] = value; - } - return options; -} - -async function apiJson( url, token, fetchImplementation ) { - const response = await fetchImplementation( url, { - headers: { - Accept: 'application/vnd.github+json', - Authorization: `Bearer ${ token }`, - 'X-GitHub-Api-Version': '2022-11-28', - }, - } ); - if ( response.status === 404 ) { - return null; - } - if ( ! response.ok ) { - throw new Error( - `GitHub returned HTTP ${ response.status } for ${ url }.` - ); - } - return response.json(); -} + HANDOFF_METADATA, + HANDOFF_REUSE, + assetPrefix, + findLatestSnapshot, +} from './lib/preview.mjs'; -async function releaseAssets( - repository, - releaseId, - token, - fetchImplementation +/** + * Hands the newest published snapshot for this pull request and commit to the + * publisher and reports the reuse to the build workflow, which then skips the + * build. Returns the reused asset name, or null when there is nothing to reuse. + * + * @param {Record} api + * @param {{number: number, head: {sha: string}}} pullRequest + * @param {string} handoffDirectory + * @returns {Promise} + */ +export async function reusePublishedSnapshot( + api, + pullRequest, + handoffDirectory ) { - const assets = []; - for ( let page = 1; ; page++ ) { - const batch = await apiJson( - `https://api.github.com/repos/${ repository }/releases/${ releaseId }/assets?per_page=100&page=${ page }`, - token, - fetchImplementation - ); - assets.push( ...batch ); - if ( batch.length < 100 ) { - return assets; - } - } -} - -export async function findReusablePreview( options ) { - const fetchImplementation = options.fetchImplementation || globalThis.fetch; - const release = await apiJson( - `https://api.github.com/repos/${ options.repository }/releases/tags/${ RELEASE_TAG }`, - options.token, - fetchImplementation + const release = await api.getRelease(); + const assets = release ? await api.listReleaseAssets( release.id ) : []; + const snapshot = findLatestSnapshot( + assets, + assetPrefix( pullRequest.number, pullRequest.head.sha ) ); - if ( ! release ) { + if ( ! snapshot ) { return null; } - const assets = await releaseAssets( - options.repository, - release.id, - options.token, - fetchImplementation + await mkdir( handoffDirectory, { recursive: true } ); + await writeFile( + path.join( handoffDirectory, HANDOFF_METADATA ), + `${ JSON.stringify( { handoffType: HANDOFF_REUSE }, null, 2 ) }\n` ); - const prefix = `code-reference-pr-${ options.pullRequestNumber }-${ options.sourceSha }-`; - const candidates = assets - .filter( - ( asset ) => - asset.name.startsWith( prefix ) && - asset.name.endsWith( '.json' ) - ) - .sort( ( left, right ) => - right.created_at.localeCompare( left.created_at ) - ); - for ( const candidate of candidates ) { - try { - const metadataResponse = await fetchImplementation( - candidate.browser_download_url - ); - if ( metadataResponse.status !== 200 ) { - throw new Error( - `Published metadata returned HTTP ${ metadataResponse.status }.` - ); - } - const published = validateReusableMetadata( - await metadataResponse.json(), - options - ); - if ( - candidate.name !== - metadataAssetName( published.snapshotFilename ) - ) { - throw new Error( - 'Published metadata asset name does not match.' - ); - } - const snapshot = assets.find( - ( asset ) => asset.name === published.snapshotFilename - ); - if ( ! snapshot ) { - throw new Error( 'Published snapshot asset is missing.' ); - } - await validatePublicSnapshot( - snapshot.browser_download_url, - { - bytes: published.snapshotBytes, - sha256: published.snapshotSha256, - maximumBytes: options.maximumBytes, - }, - fetchImplementation - ); - return createReuseHandoff( published, options ); - } catch ( error ) { - options.warning?.( - `Cannot reuse ${ candidate.name }: ${ error.message }` - ); - } + // The build workflow skips the build when this is set. A run that reused + // nothing, or failed before here, sets nothing and the build goes ahead. + if ( process.env.GITHUB_OUTPUT ) { + await writeFile( process.env.GITHUB_OUTPUT, 'reused=true\n', { + flag: 'a', + } ); } - return null; + return snapshot.name; } async function main() { - const options = argumentsFrom( process.argv.slice( 2 ) ); - const handoff = await findReusablePreview( { - ...options, - maximumBytes: Number( options.maximumBytes ), - token: process.env.GITHUB_TOKEN, - warning: ( message ) => - process.stderr.write( `::warning::${ message }\n` ), - } ); - if ( handoff ) { - await mkdir( path.dirname( options.output ), { recursive: true } ); - await writeFile( - options.output, - `${ JSON.stringify( handoff, null, 2 ) }\n` - ); - } - if ( process.env.GITHUB_OUTPUT ) { - await writeFile( - process.env.GITHUB_OUTPUT, - `reused=${ handoff ? 'true' : 'false' }\n`, - { flag: 'a' } + const [ handoffDirectory = 'handoff' ] = process.argv.slice( 2 ); + const { pull_request: pullRequest } = await readWorkflowEvent(); + const assetName = await reusePublishedSnapshot( + new GitHubApi( + process.env.GITHUB_REPOSITORY, + process.env.GITHUB_TOKEN + ), + pullRequest, + handoffDirectory + ); + if ( assetName ) { + process.stdout.write( + `Reusing the published snapshot ${ assetName }.\n` ); } } if ( import.meta.url === pathToFileURL( process.argv[ 1 ] ).href ) { - main().catch( async ( error ) => { + main().catch( ( error ) => { process.stderr.write( `::warning::Same-SHA reuse check failed: ${ error.message }\n` ); - if ( process.env.GITHUB_OUTPUT ) { - await writeFile( process.env.GITHUB_OUTPUT, 'reused=false\n', { - flag: 'a', - } ).catch( () => {} ); - } } ); } diff --git a/.github/scripts/docs-playground-preview/test/base.test.mjs b/.github/scripts/docs-playground-preview/test/base.test.mjs deleted file mode 100644 index 690e3a6bbe190..0000000000000 --- a/.github/scripts/docs-playground-preview/test/base.test.mjs +++ /dev/null @@ -1,175 +0,0 @@ -import assert from 'node:assert/strict'; -import { mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises'; -import os from 'node:os'; -import path from 'node:path'; -import { test } from 'node:test'; - -import { - createInvariantBaseBlueprint, - dependencyBuildPlan, - ensureInvariantBase, - prunePreviewFonts, -} from '../lib/base.mjs'; -import { zipDirectory } from '../lib/archive.mjs'; -import { run } from '../lib/process.mjs'; - -function inputs( cacheDirectory ) { - return { - cacheKey: 'docs-preview-base-v1-' + 'a'.repeat( 64 ), - cacheDirectory, - cacheInputs: { - dependencyDigest: 'b'.repeat( 64 ), - harnessDigest: 'c'.repeat( 64 ), - }, - wordpress: { - channel: 'beta', - version: '7.2-beta1', - downloadUrl: - 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', - }, - dependencies: { - playground: { - blueprintSchema: - 'https://playground.wordpress.net/blueprint-schema.json', - phpVersion: '8.4', - }, - }, - }; -} - -test( 'the invariant Blueprint installs dependencies without importing source', () => { - const blueprint = createInvariantBaseBlueprint( inputs( '/cache' ) ); - assert.deepEqual( blueprint.preferredVersions, { - php: '8.4', - wp: '7.2-beta1', - } ); - assert.deepEqual( blueprint.features, { networking: false } ); - assert.equal( blueprint.login, false ); - assert.equal( - blueprint.steps.some( ( step ) => - /import|reference\.json/.test( JSON.stringify( step ) ) - ), - false - ); - assert.deepEqual( - blueprint.steps - .filter( ( step ) => step.step === 'installTheme' ) - .map( ( step ) => step.themeData.path ), - [ 'bundles/wporg-parent-2021.zip', 'bundles/wporg-developer-2023.zip' ] - ); -} ); - -test( 'dependency build plan uses pinned harness tools and upstream locks', () => { - const roots = { - phpdocParser: '/repos/parser', - wporgParent2021: '/repos/parent', - wporgDeveloperTheme: '/repos/developer/theme', - wporgMuPlugins: '/repos/mu', - postsToPosts: '/repos/p2p', - codeSyntaxBlock: '/repos/syntax', - }; - const plan = dependencyBuildPlan( roots, '/tools/composer.phar' ); - assert.equal( plan.length, 10 ); - assert.deepEqual( plan[ 0 ].args.slice( 0, 2 ), [ - '/tools/composer.phar', - 'install', - ] ); - assert.match( plan[ 1 ].command, /node_modules\/\.bin\/yarn$/ ); - assert.deepEqual( plan[ 1 ].args, [ - 'install', - '--frozen-lockfile', - '--ignore-scripts', - ] ); - assert.match( plan[ 3 ].command, /node_modules\/\.bin\/wp-scripts$/ ); - assert.deepEqual( plan[ 4 ].args.slice( 0, 2 ), [ - 'ci', - '--ignore-scripts', - ] ); -} ); - -test( 'the invariant base marker is written last and enables exact reuse', async () => { - const cache = await mkdtemp( - path.join( os.tmpdir(), 'docs-preview-base-' ) - ); - const resolved = inputs( path.join( cache, 'entry' ) ); - let builds = 0; - const buildImplementation = async ( current ) => { - builds++; - await writeFile( - path.join( current.cacheDirectory, 'base.zip' ), - 'base' - ); - await mkdir( path.join( current.cacheDirectory, 'parser' ) ); - await writeFile( - path.join( - current.cacheDirectory, - 'parser/generate-json-manually.php' - ), - ' { - const policy = await readFile( - new URL( '../php/base-policy.php', import.meta.url ), - 'utf8' - ); - assert.doesNotMatch( - policy, - /DISALLOW_FILE_MODS|AUTOMATIC_UPDATER_DISABLED/ - ); -} ); - -test( 'dependency bundles have the requested install root and omit build tools', async () => { - const temporary = await mkdtemp( - path.join( os.tmpdir(), 'docs-preview-bundle-' ) - ); - const source = path.join( temporary, 'source' ); - await mkdir( path.join( source, 'node_modules' ), { recursive: true } ); - await writeFile( path.join( source, 'plugin.php' ), ' { - const root = await mkdtemp( - path.join( os.tmpdir(), 'docs-preview-fonts-' ) - ); - const fonts = path.join( root, 'global-fonts' ); - await mkdir( path.join( fonts, 'NotoSerif' ), { recursive: true } ); - await writeFile( path.join( fonts, 'NotoSerif/font.woff2' ), 'large' ); - await writeFile( - path.join( fonts, 'style.css' ), - '@import "./NotoSerif/NotoSerifJP/style.css";\nbody { color: black; }\n' - ); - await prunePreviewFonts( root ); - await assert.rejects( - readFile( path.join( fonts, 'NotoSerif/font.woff2' ) ) - ); - assert.equal( - await readFile( path.join( fonts, 'style.css' ), 'utf8' ), - 'body { color: black; }\n' - ); -} ); diff --git a/.github/scripts/docs-playground-preview/test/build.test.mjs b/.github/scripts/docs-playground-preview/test/build.test.mjs deleted file mode 100644 index 2bf0a30bb36fe..0000000000000 --- a/.github/scripts/docs-playground-preview/test/build.test.mjs +++ /dev/null @@ -1,292 +0,0 @@ -import assert from 'node:assert/strict'; -import { mkdtemp, readFile } from 'node:fs/promises'; -import os from 'node:os'; -import path from 'node:path'; -import { test } from 'node:test'; - -import { - buildCodeReferencePreview, - createBuildMetadata, - createFailureMetadata, - parseArguments, - verifyToolchain, -} from '../build.mjs'; - -const SHA = 'a'.repeat( 40 ); - -function resolved( cacheDirectory ) { - return { - schemaVersion: 1, - cacheKey: 'docs-preview-base-v1-test', - cacheDirectory, - cacheInputs: { dependencyDigest: 'b'.repeat( 64 ) }, - dependencies: { - toolchain: { nodeVersion: '20.20.2', npmVersion: '10.8.2' }, - playground: { phpVersion: '8.4' }, - validation: { - minimumSymbols: { - classes: 1, - methods: 1, - functions: 1, - hooks: 1, - filters: 1, - }, - }, - }, - wordpress: { - channel: 'beta', - version: '7.2-beta1', - downloadUrl: - 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', - }, - }; -} - -function parser( failures = [] ) { - return { - sourceFiles: 2, - records: 1, - counts: { classes: 1, methods: 1, functions: 1, hooks: 1, filters: 1 }, - failures, - }; -} - -test( 'arguments recognize enforcement only at the exact true value', () => { - assert.equal( - parseArguments( [], { DOCS_PREVIEW_ENFORCE: 'true' } ).enforce, - true - ); - assert.equal( - parseArguments( [], { DOCS_PREVIEW_ENFORCE: 'TRUE' } ).enforce, - false - ); - assert.throws( - () => parseArguments( [ '--unknown', 'value' ] ), - /Unknown option/ - ); -} ); - -test( 'the cache cannot claim a toolchain version that did not run', async () => { - const commands = []; - await verifyToolchain( resolved( '/cache' ), { - nodeVersion: '20.20.2', - runImplementation: async ( command ) => { - commands.push( command ); - return { stdout: command === 'npm' ? '10.8.2\n' : '8.4' }; - }, - } ); - assert.deepEqual( commands, [ 'npm', 'php' ] ); - await assert.rejects( - verifyToolchain( resolved( '/cache' ), { - nodeVersion: '22.23.2', - } ), - /Node 20\.20\.2 is required/ - ); -} ); - -test( 'build metadata contains the complete publisher handoff identity', () => { - const metadata = createBuildMetadata( - { - sourceRepository: 'example/wordpress-develop', - pullRequestNumber: 12, - sourceSha: SHA, - workflowRunId: '123', - workflowRunAttempt: 2, - runUrl: 'https://github.com/example/wordpress-develop/actions/runs/123', - }, - resolved( '/cache' ), - { cacheHit: true }, - parser(), - { filename: 'snapshot.zip', bytes: 42, sha256: 'c'.repeat( 64 ) }, - { failures: [], checks: { index: 200 } }, - '2026-08-09T12:34:56.000Z' - ); - assert.equal( metadata.schemaVersion, 1 ); - assert.equal( metadata.sourceSha, SHA ); - assert.equal( metadata.resolvedWordPressBeta.version, '7.2-beta1' ); - assert.equal( metadata.phpVersion, '8.4' ); - assert.equal( metadata.dependencyManifestDigest, 'b'.repeat( 64 ) ); - assert.equal( metadata.snapshotFilename, 'snapshot.zip' ); - assert.equal( metadata.snapshotBytes, 42 ); - assert.equal( metadata.snapshotSha256, 'c'.repeat( 64 ) ); - assert.equal( metadata.buildStatus, 'success' ); - assert.equal( metadata.validationStatus, 'passed' ); - assert.deepEqual( metadata.behavior.checks, { index: 200 } ); - assert.equal( metadata.generationTimestamp, '2026-08-09T12:34:56.000Z' ); -} ); - -test( 'failed build metadata retains the available trusted handoff identity', () => { - const metadata = createFailureMetadata( - { - sourceRepository: 'example/wordpress-develop', - pullRequestNumber: 12, - sourceSha: SHA, - workflowRunId: '123', - workflowRunAttempt: 2, - runUrl: 'https://github.com/example/wordpress-develop/actions/runs/123', - }, - resolved( '/cache' ), - new Error( 'import broke' ), - '2026-08-09T12:34:56.000Z' - ); - assert.equal( metadata.buildStatus, 'failed' ); - assert.equal( metadata.validationStatus, 'not-run' ); - assert.equal( metadata.sourceSha, SHA ); - assert.equal( metadata.resolvedWordPressBeta.version, '7.2-beta1' ); - assert.equal( metadata.snapshotFilename, null ); - assert.equal( metadata.buildError, 'import broke' ); -} ); - -test( 'resolve-only records the exact cache inputs without building', async () => { - const root = await mkdtemp( - path.join( os.tmpdir(), 'docs-preview-resolve-' ) - ); - const expected = resolved( path.join( root, 'cache/exact' ) ); - let written; - const result = await buildCodeReferencePreview( - { - source: process.cwd(), - sourceRepository: 'example/wordpress-develop', - sourceSha: SHA, - cacheRoot: path.join( root, 'cache' ), - workRoot: path.join( root, 'work' ), - resolveOnly: true, - }, - { - resolveBuildInputs: async () => expected, - verifyToolchain: async () => {}, - writeResolvedInputs: async ( inputs, filename ) => { - written = { inputs, filename }; - }, - } - ); - assert.equal( result.inputs, expected ); - assert.equal( written.inputs, expected ); - assert.equal( - written.filename, - path.join( root, 'work/resolved-inputs.json' ) - ); -} ); - -async function fixture( failures = [], enforce = false ) { - const root = await mkdtemp( - path.join( os.tmpdir(), 'docs-preview-build-' ) - ); - const cache = path.join( root, 'cache/exact' ); - const calls = []; - const overrides = { - run: async () => {}, - verifyToolchain: async () => {}, - readResolvedInputs: async () => resolved( cache ), - ensureInvariantBase: async () => { - calls.push( 'base' ); - return { cacheHit: true }; - }, - generateParserJson: async ( options ) => { - calls.push( 'parser' ); - assert.equal( options.parser, path.join( cache, 'parser' ) ); - return parser( failures ); - }, - packageFinalSnapshot: async () => { - calls.push( 'package' ); - return { - filename: 'snapshot.zip', - bytes: 42, - sha256: 'c'.repeat( 64 ), - }; - }, - validateSnapshot: async () => { - calls.push( 'validate' ); - return { failures: [], checks: { index: 200 } }; - }, - }; - const options = { - source: process.cwd(), - sourceRepository: 'example/wordpress-develop', - sourceSha: SHA, - resolvedInputs: path.join( root, 'inputs.json' ), - workRoot: path.join( root, 'work' ), - output: path.join( root, 'snapshot.zip' ), - metadata: path.join( root, 'build.json' ), - enforce, - }; - return { root, calls, overrides, options }; -} - -test( 'one command builds the base, parser data, package, and handoff in order', async () => { - const current = await fixture(); - const result = await buildCodeReferencePreview( - current.options, - current.overrides - ); - assert.deepEqual( current.calls, [ - 'base', - 'parser', - 'package', - 'validate', - ] ); - assert.equal( result.metadata.validationStatus, 'passed' ); - const written = JSON.parse( - await readFile( path.join( current.root, 'build.json' ), 'utf8' ) - ); - assert.equal( written.snapshotSha256, 'c'.repeat( 64 ) ); -} ); - -test( 'fatal packaging writes failed terminal metadata and still rejects', async () => { - const current = await fixture(); - current.overrides.packageFinalSnapshot = async () => { - throw new Error( 'snapshot is oversized' ); - }; - await assert.rejects( - buildCodeReferencePreview( current.options, current.overrides ), - /snapshot is oversized/ - ); - const written = JSON.parse( - await readFile( path.join( current.root, 'build.json' ), 'utf8' ) - ); - assert.equal( written.buildStatus, 'failed' ); - assert.equal( written.sourceSha, SHA ); - assert.equal( written.snapshotFilename, null ); - assert.equal( written.buildError, 'snapshot is oversized' ); -} ); - -test( 'advisory failures are recorded without rejecting the build', async () => { - const current = await fixture( [ 'catastrophic undercount' ] ); - const result = await buildCodeReferencePreview( - current.options, - current.overrides - ); - assert.equal( result.metadata.validationStatus, 'failed' ); - assert.deepEqual( result.metadata.validationFailures, [ - 'catastrophic undercount', - ] ); -} ); - -test( 'behavioral failures prevent passed metadata in advisory mode', async () => { - const current = await fixture(); - current.overrides.validateSnapshot = async () => ( { - failures: [ 'class route returned HTTP 404.' ], - checks: { class: 404 }, - } ); - const result = await buildCodeReferencePreview( - current.options, - current.overrides - ); - assert.equal( result.metadata.validationStatus, 'failed' ); - assert.deepEqual( result.metadata.validationFailures, [ - 'class route returned HTTP 404.', - ] ); - assert.deepEqual( result.metadata.behavior.checks, { class: 404 } ); -} ); - -test( 'enforcement rejects only after failed validation metadata is written', async () => { - const current = await fixture( [ 'catastrophic undercount' ], true ); - await assert.rejects( - buildCodeReferencePreview( current.options, current.overrides ), - /catastrophic undercount/ - ); - const written = JSON.parse( - await readFile( path.join( current.root, 'build.json' ), 'utf8' ) - ); - assert.equal( written.validationStatus, 'failed' ); -} ); diff --git a/.github/scripts/docs-playground-preview/test/comment.test.mjs b/.github/scripts/docs-playground-preview/test/comment.test.mjs new file mode 100644 index 0000000000000..3b8d49df519e4 --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/comment.test.mjs @@ -0,0 +1,97 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; + +import { + expiredComment, + failedComment, + readPreviewCommentSource, + readyComment, + staleComment, + staleUnavailableComment, +} from '../lib/comment.mjs'; +import { COMMENT_MARKER } from '../lib/preview.mjs'; + +const sourceRepository = 'contributor/wordpress-develop'; +const sourceSha = 'a'.repeat( 40 ); +const previousSha = 'b'.repeat( 40 ); +const runUrl = + 'https://github.com/WordPress/wordpress-develop/actions/runs/456'; +const preview = { + sourceRepository, + sourceSha, + publication: { + publishedAt: '2026-09-04T12:00:00.000Z', + playgroundUrl: 'https://playground.wordpress.net/?blueprint-url=x', + }, +}; +const context = { sourceRepository, sourceSha, runUrl }; + +test( 'every state carries the marker that identifies the one preview comment', () => { + for ( const body of [ + readyComment( preview, runUrl ), + failedComment( context, null ), + staleComment( preview, context ), + staleUnavailableComment( + { repository: sourceRepository, sha: previousSha }, + context + ), + expiredComment(), + ] ) { + assert.ok( body.includes( COMMENT_MARKER ) ); + } +} ); + +test( 'a ready comment links the preview and its source commit', () => { + const body = readyComment( preview, runUrl ); + + assert.match( body, /\*\*Status:\*\* Ready/ ); + assert.ok( + body.includes( + '[Open Code Reference preview](https://playground.wordpress.net/?blueprint-url=x)' + ) + ); + assert.ok( body.includes( `/commit/${ sourceSha }` ) ); + assert.ok( body.includes( runUrl ) ); +} ); + +test( 'a failure comment offers the last working preview when there is one', () => { + assert.doesNotMatch( + failedComment( context, null ), + /Latest successful docs preview/ + ); + assert.match( + failedComment( context, { + sourceRepository, + sourceSha: previousSha, + publication: { + playgroundUrl: + 'https://playground.wordpress.net/?blueprint-url=y', + }, + } ), + /\[Latest successful docs preview\]\(https:\/\/playground\.wordpress\.net\/\?blueprint-url=y\)/ + ); +} ); + +test( 'the source marker survives a round trip and expired comments carry none', () => { + assert.deepEqual( + readPreviewCommentSource( readyComment( preview, runUrl ) ), + { + repository: sourceRepository, + sha: sourceSha, + } + ); + assert.equal( readPreviewCommentSource( expiredComment() ), null ); + assert.equal( readPreviewCommentSource( undefined ), null ); +} ); + +test( 'a stale comment names both the built commit and the current one', () => { + const body = staleComment( preview, { + sourceRepository, + sourceSha: previousSha, + } ); + + assert.match( body, /\*\*Status:\*\* Stale/ ); + assert.ok( body.includes( `/commit/${ sourceSha }` ) ); + assert.ok( body.includes( `/commit/${ previousSha }` ) ); + assert.match( body, /Add the `docs-preview` label again/ ); +} ); diff --git a/.github/scripts/docs-playground-preview/test/config.test.mjs b/.github/scripts/docs-playground-preview/test/config.test.mjs deleted file mode 100644 index 3b43da2b2e364..0000000000000 --- a/.github/scripts/docs-playground-preview/test/config.test.mjs +++ /dev/null @@ -1,245 +0,0 @@ -import assert from 'node:assert/strict'; -import { readFile } from 'node:fs/promises'; -import { test } from 'node:test'; - -import { - isDeploymentEnabled, - makeBaseCacheKey, - resolveWordPressBeta, - validateDependencies, -} from '../lib/config.mjs'; - -const dependenciesFile = new URL( - '../../../docs-playground-preview/dependencies.json', - import.meta.url -); -const packageFile = new URL( - '../../../docs-playground-preview/package.json', - import.meta.url -); -const nodeVersionFile = new URL( - '../../../docs-playground-preview/.nvmrc', - import.meta.url -); -const buildWorkflowFile = new URL( - '../../../workflows/docs-playground-preview-build.yml', - import.meta.url -); -const publishWorkflowFile = new URL( - '../../../workflows/docs-playground-preview-publish.yml', - import.meta.url -); -const lifecycleWorkflowFile = new URL( - '../../../workflows/docs-playground-preview-lifecycle.yml', - import.meta.url -); - -async function manifest() { - return JSON.parse( await readFile( dependenciesFile, 'utf8' ) ); -} - -test( 'the checked-in dependency manifest is valid', async () => { - const dependencies = validateDependencies( await manifest() ); - const packageManifest = JSON.parse( await readFile( packageFile, 'utf8' ) ); - assert.equal( dependencies.schemaVersion, 1 ); - assert.equal( - ( await readFile( nodeVersionFile, 'utf8' ) ).trim(), - dependencies.toolchain.nodeVersion - ); - assert.equal( - packageManifest.engines.node, - dependencies.toolchain.nodeVersion - ); - assert.equal( - packageManifest.dependencies[ '@wp-playground/cli' ], - dependencies.playground.cliVersion - ); - assert.equal( - packageManifest.dependencies.yarn, - dependencies.toolchain.yarnVersion - ); - assert.equal( - packageManifest.dependencies[ '@wordpress/scripts' ], - dependencies.toolchain.wpScriptsVersion - ); - assert.equal( - packageManifest.dependencies[ '@wordpress/i18n' ], - dependencies.toolchain.wpI18nVersion - ); -} ); - -test( 'dependency repositories and commits are fixed', async () => { - const dependencies = await manifest(); - dependencies.repositories.phpdocParser.repository = 'attacker/parser'; - assert.throws( - () => validateDependencies( dependencies ), - /must use WordPress\/phpdoc-parser/ - ); - - const invalidCommit = await manifest(); - invalidCommit.repositories.wporgDeveloper.commit = 'trunk'; - assert.throws( - () => validateDependencies( invalidCommit ), - /must be a full commit hash/ - ); -} ); - -test( 'toolchain versions are exact and required', async () => { - for ( const name of [ - 'nodeVersion', - 'npmVersion', - 'composerVersion', - 'yarnVersion', - 'wpScriptsVersion', - 'wpI18nVersion', - ] ) { - const dependencies = await manifest(); - delete dependencies.toolchain[ name ]; - assert.throws( - () => validateDependencies( dependencies ), - new RegExp( `toolchain\\.${ name } must be an exact version` ) - ); - } -} ); - -test( 'the beta resolver records a concrete official build', async () => { - const resolved = await resolveWordPressBeta( async ( url ) => { - assert.equal( - url, - 'https://api.wordpress.org/core/version-check/1.7/?channel=beta' - ); - return { - ok: true, - json: async () => ( { - offers: [ - { - response: 'development', - version: '7.2-beta1', - download: - 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', - }, - ], - } ), - }; - } ); - assert.deepEqual( resolved, { - channel: 'beta', - version: '7.2-beta1', - downloadUrl: - 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', - } ); -} ); - -test( 'the beta resolver rejects an unbound download URL', async () => { - await assert.rejects( - resolveWordPressBeta( async () => ( { - ok: true, - json: async () => ( { - offers: [ - { - response: 'autoupdate', - version: '7.2-beta1', - download: 'https://example.com/wordpress.zip', - }, - ], - } ), - } ) ), - /no concrete beta build/ - ); -} ); - -test( 'the cache key changes with every material base input', () => { - const inputs = { - cacheSchemaVersion: 1, - platform: 'linux', - architecture: 'x64', - runnerImage: 'ubuntu-24.04', - phpVersion: '8.4', - wordpressVersion: '7.2-beta1', - dependencyDigest: 'a'.repeat( 64 ), - harnessDigest: 'b'.repeat( 64 ), - }; - const baseline = makeBaseCacheKey( inputs ); - for ( const [ name, value ] of Object.entries( { - cacheSchemaVersion: 2, - platform: 'darwin', - architecture: 'arm64', - runnerImage: 'macos-15', - phpVersion: '8.5', - wordpressVersion: '7.2-beta2', - dependencyDigest: 'c'.repeat( 64 ), - harnessDigest: 'd'.repeat( 64 ), - } ) ) { - assert.notEqual( - makeBaseCacheKey( { ...inputs, [ name ]: value } ), - baseline - ); - } -} ); - -test( 'deployment is inert outside the primary and opted-in staging repositories', () => { - assert.equal( isDeploymentEnabled( 'WordPress/wordpress-develop' ), true ); - assert.equal( isDeploymentEnabled( 'sirreal/wordpress-develop' ), false ); - assert.equal( - isDeploymentEnabled( 'sirreal/wordpress-develop', 'true' ), - true - ); - assert.equal( - isDeploymentEnabled( 'somebody/wordpress-develop', 'true' ), - false - ); - assert.equal( - isDeploymentEnabled( 'sirreal/wordpress-develop', 'TRUE' ), - false - ); -} ); - -test( 'only the untrusted staging pull-request build bypasses staging activation', async () => { - const buildWorkflow = await readFile( buildWorkflowFile, 'utf8' ); - const publishWorkflow = await readFile( publishWorkflowFile, 'utf8' ); - const lifecycleWorkflow = await readFile( lifecycleWorkflowFile, 'utf8' ); - const trunkJobOffset = buildWorkflow.indexOf( '\n build-trunk:' ); - - assert.notEqual( trunkJobOffset, -1 ); - assert.match( - buildWorkflow.slice( 0, trunkJobOffset ), - /github\.repository == 'sirreal\/wordpress-develop' / - ); - assert.doesNotMatch( - buildWorkflow.slice( 0, trunkJobOffset ), - /vars\.DOCS_PREVIEW_STAGING/ - ); - - for ( const trustedWorkflow of [ - buildWorkflow.slice( trunkJobOffset ), - publishWorkflow, - lifecycleWorkflow, - ] ) { - assert.match( - trustedWorkflow, - /github\.repository == 'sirreal\/wordpress-develop' && vars\.DOCS_PREVIEW_STAGING == 'true'/ - ); - } - assert.match( - publishWorkflow, - /DOCS_PREVIEW_ENFORCE: \$\{\{ vars\.DOCS_PREVIEW_ENFORCE \}\}/ - ); - const trunkPublisherOffset = publishWorkflow.indexOf( - '\n publish-trunk:' - ); - assert.notEqual( trunkPublisherOffset, -1 ); - const pullRequestPublisher = publishWorkflow.slice( - 0, - trunkPublisherOffset - ); - assert.match( - pullRequestPublisher, - /publish:\n[\s\S]*?permissions:\n[\s\S]*?pull-requests: write/ - ); - assert.doesNotMatch( pullRequestPublisher, /issues: write/ ); - assert.equal( - lifecycleWorkflow.match( /pull-requests: write/g )?.length, - 2 - ); - assert.doesNotMatch( lifecycleWorkflow, /issues: write/ ); -} ); diff --git a/.github/scripts/docs-playground-preview/test/fake-github.mjs b/.github/scripts/docs-playground-preview/test/fake-github.mjs new file mode 100644 index 0000000000000..55bd1d982152c --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/fake-github.mjs @@ -0,0 +1,226 @@ +// One in-memory stand-in for GitHubApi. The API client is the only boundary the +// publisher, the reuse check and the lifecycle reach the network through, so +// faking it is enough to exercise every outcome. + +export const repository = 'WordPress/wordpress-develop'; +export const sourceRepository = 'contributor/wordpress-develop'; +export const sourceSha = 'a'.repeat( 40 ); + +/** + * @param {Record} [overrides] + */ +export function buildRun( overrides = {} ) { + return { id: 456, run_attempt: 1, head_sha: sourceSha, ...overrides }; +} + +/** + * @param {Record} [overrides] + */ +export function pullRequest( overrides = {} ) { + return { + number: 123, + head: { sha: sourceSha, repo: { full_name: sourceRepository } }, + labels: [ { name: 'docs-preview' } ], + ...overrides, + }; +} + +/** + * @param {number} id + * @param {string} name + * @param {string} [createdAt] + */ +export function releaseAsset( + id, + name, + createdAt = '2026-08-08T13:00:00.000Z' +) { + return { id, name, created_at: createdAt }; +} + +export class FakeGitHub { + /** + * @param {Record} [state] + */ + constructor( state = {} ) { + this.repository = repository; + this.run = state.run || buildRun(); + this.pullRequest = + state.pullRequest === undefined ? pullRequest() : state.pullRequest; + this.latestRun = state.latestRun || this.run; + this.release = state.release === undefined ? { id: 9 } : state.release; + /** @type {any[]} */ + this.assets = state.assets || []; + /** @type {Map} */ + this.assetJson = state.assetJson || new Map(); + /** @type {any[]} */ + this.comments = state.comments || []; + /** @type {any[]} */ + this.caches = state.caches || []; + this.gitReference = state.gitReference || null; + /** @type {any[]} */ + this.uploads = []; + /** @type {any[]} */ + this.deletedAssets = []; + /** @type {any[]} */ + this.deletedCaches = []; + /** @type {any[]} */ + this.commits = []; + /** @type {any[]} */ + this.trees = []; + this.labelRemovals = 0; + this.nextAssetId = 100; + } + + async getRun() { + return this.run; + } + + async findPullRequestForRun() { + return this.pullRequest; + } + + async getPullRequest() { + return this.pullRequest; + } + + async latestPullRequestBuildRun() { + return this.latestRun; + } + + async latestTrunkBuildRun() { + return this.latestRun; + } + + async getRelease() { + return this.release; + } + + async createRelease() { + this.release = { id: 9 }; + return this.release; + } + + async listReleaseAssets() { + return [ ...this.assets ]; + } + + /** + * @param {number} releaseId + * @param {string} name + * @param {Uint8Array} bytes + * @param {string} contentType + */ + async uploadReleaseAsset( releaseId, name, bytes, contentType ) { + const asset = releaseAsset( + this.nextAssetId++, + name, + '2026-09-04T12:00:00.000Z' + ); + this.uploads.push( { name, contentType, bytes } ); + this.assets.push( asset ); + if ( contentType === 'application/json' ) { + this.assetJson.set( + name, + JSON.parse( Buffer.from( bytes ).toString( 'utf8' ) ) + ); + } + return asset; + } + + /** + * @param {number} assetId + */ + async deleteReleaseAsset( assetId ) { + this.deletedAssets.push( assetId ); + this.assets = this.assets.filter( ( asset ) => asset.id !== assetId ); + return null; + } + + /** + * @param {Record} asset + */ + async readAssetJson( asset ) { + const json = this.assetJson.get( asset.name ); + if ( ! json ) { + throw new Error( `No published metadata for ${ asset.name }.` ); + } + return json; + } + + async findPreviewComment() { + return this.comments[ 0 ] || null; + } + + /** + * @param {number} pullRequestNumber + * @param {string} body + */ + async createComment( pullRequestNumber, body ) { + this.comments.push( { id: 1, body } ); + return this.comments[ 0 ]; + } + + /** + * @param {number} commentId + * @param {string} body + */ + async updateComment( commentId, body ) { + this.comments[ 0 ] = { id: commentId, body }; + return this.comments[ 0 ]; + } + + async removeLabel() { + this.labelRemovals++; + return null; + } + + async getGitReference() { + return this.gitReference; + } + + /** + * @param {string} path + * @param {string} content + */ + async createGitTree( path, content ) { + this.trees.push( { path, content } ); + return { sha: 'c'.repeat( 40 ) }; + } + + /** + * @param {string} message + * @param {string} treeSha + * @param {string | null} parentSha + */ + async createGitCommit( message, treeSha, parentSha ) { + this.commits.push( { message, treeSha, parentSha } ); + return { sha: 'd'.repeat( 40 ) }; + } + + /** + * @param {string} reference + * @param {string} sha + * @param {boolean} exists + */ + async setGitReference( reference, sha, exists ) { + this.gitReference = { + ref: reference, + object: { sha }, + existed: exists, + }; + return this.gitReference; + } + + async listActionCaches() { + return [ ...this.caches ]; + } + + /** + * @param {number} cacheId + */ + async deleteActionCache( cacheId ) { + this.deletedCaches.push( cacheId ); + return null; + } +} diff --git a/.github/scripts/docs-playground-preview/test/files.test.mjs b/.github/scripts/docs-playground-preview/test/files.test.mjs deleted file mode 100644 index e814e9f4c0541..0000000000000 --- a/.github/scripts/docs-playground-preview/test/files.test.mjs +++ /dev/null @@ -1,183 +0,0 @@ -import assert from 'node:assert/strict'; -import { mkdir, mkdtemp, readFile, symlink, writeFile } from 'node:fs/promises'; -import os from 'node:os'; -import path from 'node:path'; -import { test } from 'node:test'; - -import { - acquireRepositories, - digestTree, - exists, - stageCorePhp, -} from '../lib/files.mjs'; -import { resolveBuildInputs } from '../lib/inputs.mjs'; -import { run } from '../lib/process.mjs'; - -const repositoryRoot = path.resolve( - path.dirname( new URL( import.meta.url ).pathname ), - '../../../..' -); - -test( 'run captures output and reports ordinary child failure', async () => { - const result = await run( - process.execPath, - [ '-e', 'process.stdout.write("ready")' ], - { capture: true, quiet: true } - ); - assert.equal( result.stdout, 'ready' ); - await assert.rejects( - run( - process.execPath, - [ '-e', 'process.stderr.write("broken");process.exit(2)' ], - { - capture: true, - quiet: true, - } - ), - /broken/ - ); -} ); - -test( 'stageCorePhp copies only eligible Core PHP source', async () => { - const root = await mkdtemp( - path.join( os.tmpdir(), 'docs-preview-source-' ) - ); - const source = path.join( root, 'src' ); - for ( const relative of [ - 'wp-includes/version.php', - 'wp-admin/admin.php', - 'wp-content/plugins/hello.php', - 'wp-content/themes/twentytwenty/functions.php', - ] ) { - await mkdir( path.dirname( path.join( source, relative ) ), { - recursive: true, - } ); - await writeFile( - path.join( source, relative ), - ` { - const root = await mkdtemp( - path.join( os.tmpdir(), 'docs-preview-digest-' ) - ); - await writeFile( path.join( root, 'a' ), 'one' ); - await writeFile( path.join( root, 'ignored' ), 'first' ); - const first = await digestTree( - root, - ( relative ) => relative !== 'ignored' - ); - await writeFile( path.join( root, 'ignored' ), 'second' ); - assert.equal( - await digestTree( root, ( relative ) => relative !== 'ignored' ), - first - ); - await writeFile( path.join( root, 'a' ), 'two' ); - assert.notEqual( await digestTree( root ), first ); -} ); - -test( 'acquireRepositories uses Git with each validated full commit', async () => { - const root = await mkdtemp( - path.join( os.tmpdir(), 'docs-preview-repos-' ) - ); - const calls = []; - const fakeRun = async ( command, args, options ) => { - calls.push( { command, args, options } ); - if ( args[ 0 ] === 'clone' ) { - await mkdir( path.join( args.at( -1 ), '.git' ), { - recursive: true, - } ); - } - }; - const repositories = { - parser: { - repository: 'WordPress/phpdoc-parser', - commit: 'a'.repeat( 40 ), - path: '.', - }, - }; - const acquired = await acquireRepositories( repositories, root, fakeRun ); - assert.equal( acquired.parser, path.join( root, 'parser' ) ); - assert.deepEqual( calls[ 0 ].args.slice( 0, 3 ), [ - 'clone', - '--quiet', - '--no-checkout', - ] ); - assert.deepEqual( calls[ 1 ].args, [ - 'fetch', - '--quiet', - '--depth=1', - 'origin', - 'a'.repeat( 40 ), - ] ); - assert.equal( calls[ 2 ].args.at( -1 ), 'a'.repeat( 40 ) ); - - calls.length = 0; - repositories.parser.commit = 'b'.repeat( 40 ); - await acquireRepositories( repositories, root, fakeRun ); - assert.equal( calls.length, 2 ); - assert.equal( calls[ 0 ].args[ 0 ], 'fetch' ); - assert.equal( calls[ 0 ].args.at( -1 ), 'b'.repeat( 40 ) ); - assert.equal( calls[ 1 ].args.at( -1 ), 'b'.repeat( 40 ) ); -} ); - -test( 'resolveBuildInputs binds the concrete beta and runner to an exact cache', async () => { - const cacheRoot = await mkdtemp( - path.join( os.tmpdir(), 'docs-preview-cache-' ) - ); - const fetchImplementation = async () => ( { - ok: true, - json: async () => ( { - offers: [ - { - response: 'development', - version: '7.2-beta1', - download: - 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', - }, - ], - } ), - } ); - const inputs = await resolveBuildInputs( { - repositoryRoot, - cacheRoot, - platform: 'linux', - architecture: 'x64', - runnerImage: 'ubuntu-24.04', - fetchImplementation, - } ); - assert.equal( inputs.wordpress.version, '7.2-beta1' ); - assert.match( inputs.cacheKey, /^docs-preview-base-v1-[0-9a-f]{64}$/ ); - assert.equal( - inputs.cacheDirectory, - path.join( cacheRoot, inputs.cacheKey ) - ); -} ); diff --git a/.github/scripts/docs-playground-preview/test/final.test.mjs b/.github/scripts/docs-playground-preview/test/final.test.mjs deleted file mode 100644 index c6cead429d0fe..0000000000000 --- a/.github/scripts/docs-playground-preview/test/final.test.mjs +++ /dev/null @@ -1,157 +0,0 @@ -import assert from 'node:assert/strict'; -import { mkdir, mkdtemp, open, readFile, writeFile } from 'node:fs/promises'; -import os from 'node:os'; -import path from 'node:path'; -import { test } from 'node:test'; - -import { createFinalBlueprint, packageFinalSnapshot } from '../lib/final.mjs'; - -function inputs( cacheDirectory, snapshotBytes = 1024 ) { - return { - cacheDirectory, - wordpress: { version: '7.2-beta1' }, - dependencies: { - playground: { - blueprintSchema: - 'https://playground.wordpress.net/blueprint-schema.json', - phpVersion: '8.4', - }, - limits: { snapshotBytes }, - }, - }; -} - -test( 'the final Blueprint restores the base before the complete import', () => { - const blueprint = createFinalBlueprint( inputs( '/cache' ) ); - assert.equal( blueprint.landingPage, '/reference/' ); - assert.equal( blueprint.login, false ); - assert.deepEqual( blueprint.features, { networking: false } ); - assert.deepEqual( - blueprint.steps.map( ( step ) => step.step ), - [ - 'unzip', - 'mkdir', - 'mkdir', - 'writeFile', - 'writeFile', - 'wp-cli', - 'writeFile', - 'wp-cli', - 'rmdir', - 'writeFile', - 'defineWpConfigConsts', - ] - ); - assert.equal( blueprint.steps[ 0 ].extractToPath, '/' ); - assert.equal( blueprint.steps[ 1 ].path, '/tmp/docs-preview-version' ); - assert.equal( - blueprint.steps[ 2 ].path, - '/tmp/docs-preview-version/wp-includes' - ); - assert.equal( - blueprint.steps[ 4 ].path, - '/tmp/docs-preview-version/wp-includes/version.php' - ); - assert.equal( blueprint.steps[ 4 ].data.path, 'safe-version.php' ); - assert.match( blueprint.steps[ 5 ].command, /parser import.+--quick/ ); - assert.equal( - blueprint.steps.at( -3 ).path, - '/wordpress/wp-content/plugins/phpdoc-parser' - ); - assert.equal( - blueprint.steps.at( -2 ).path, - '/wordpress/wp-content/mu-plugins/001-docs-preview-runtime.php' - ); - assert.deepEqual( blueprint.steps.at( -1 ), { - step: 'defineWpConfigConsts', - method: 'rewrite-wp-config', - consts: { - DISABLE_WP_CRON: true, - AUTOMATIC_UPDATER_DISABLED: true, - WP_AUTO_UPDATE_CORE: false, - DISALLOW_FILE_MODS: true, - }, - } ); -} ); - -async function fixture( snapshotBytes = 1024 ) { - const root = await mkdtemp( - path.join( os.tmpdir(), 'docs-preview-final-' ) - ); - const cache = path.join( root, 'cache' ); - await mkdir( cache ); - await writeFile( path.join( cache, 'base.zip' ), 'base' ); - const referenceJson = path.join( root, 'reference.json' ); - await writeFile( referenceJson, '[]' ); - return { - root, - resolved: inputs( cache, snapshotBytes ), - referenceJson, - output: path.join( root, 'output/snapshot.zip' ), - }; -} - -test( 'final packaging returns publisher metadata for a bounded snapshot', async () => { - const current = await fixture(); - let invocation; - const buildSnapshotImplementation = async ( command, options ) => { - invocation = { command, options }; - await writeFile( current.output, 'snapshot' ); - }; - const snapshot = await packageFinalSnapshot( current.resolved, { - workDirectory: path.join( current.root, 'work' ), - output: current.output, - referenceJson: current.referenceJson, - stagedSource: '/source', - playgroundCli: '/tools/wp-playground-cli', - provenance: { - sourceRepository: 'example/wordpress-develop', - sourceSha: 'a'.repeat( 40 ), - generationTimestamp: '2026-08-09T12:34:56.000Z', - runUrl: 'https://github.com/example/wordpress-develop/actions/runs/123', - }, - buildSnapshotImplementation, - } ); - assert.equal( snapshot.filename, 'snapshot.zip' ); - assert.equal( snapshot.bytes, 8 ); - assert.match( snapshot.sha256, /^[0-9a-f]{64}$/ ); - assert.equal( invocation.command, '/tools/wp-playground-cli' ); - assert.deepEqual( invocation.options.mount, [ - { hostPath: '/source', vfsPath: '/tmp/docs-preview-source' }, - ] ); - assert.equal( invocation.options.php, '8.4' ); - assert.equal( invocation.options.wp, '7.2-beta1' ); - const blueprint = JSON.parse( - await readFile( - path.join( current.root, 'work/final-blueprint.json' ), - 'utf8' - ) - ); - assert.equal( blueprint.steps[ 0 ].zipFile.path, 'base.zip' ); -} ); - -test( 'the 100 MiB snapshot boundary always fails closed', async () => { - const current = await fixture( 8 ); - const buildSnapshotImplementation = async () => { - const output = await open( current.output, 'w' ); - await output.truncate( 9 ); - await output.close(); - }; - await assert.rejects( - packageFinalSnapshot( current.resolved, { - workDirectory: path.join( current.root, 'work' ), - output: current.output, - referenceJson: current.referenceJson, - stagedSource: '/source', - playgroundCli: '/tools/wp-playground-cli', - provenance: { - sourceRepository: 'example/wordpress-develop', - sourceSha: 'a'.repeat( 40 ), - generationTimestamp: '2026-08-09T12:34:56.000Z', - runUrl: 'https://github.com/example/wordpress-develop/actions/runs/123', - }, - buildSnapshotImplementation, - } ), - /exceeds 100 MiB/ - ); -} ); diff --git a/.github/scripts/docs-playground-preview/test/github.test.mjs b/.github/scripts/docs-playground-preview/test/github.test.mjs index 0c445548cb38e..46020a32c070b 100644 --- a/.github/scripts/docs-playground-preview/test/github.test.mjs +++ b/.github/scripts/docs-playground-preview/test/github.test.mjs @@ -1,318 +1,225 @@ import assert from 'node:assert/strict'; import { test } from 'node:test'; -import { GitHubApi } from '../lib/github.mjs'; -import { COMMENT_MARKER, RELEASE_TAG } from '../lib/publication.mjs'; +import { GitHubApi, assertDeploymentEnabled } from '../lib/github.mjs'; -function response( value, status = 200 ) { +const repository = 'WordPress/wordpress-develop'; + +test( 'deployment is inert outside the primary and opted-in staging repositories', () => { + assert.doesNotThrow( () => assertDeploymentEnabled( repository ) ); + assert.doesNotThrow( () => + assertDeploymentEnabled( 'sirreal/wordpress-develop', 'true' ) + ); + for ( const [ candidate, staging ] of [ + [ 'sirreal/wordpress-develop', undefined ], + [ 'sirreal/wordpress-develop', 'TRUE' ], + [ 'somebody/wordpress-develop', 'true' ], + ] ) { + assert.throws( + () => assertDeploymentEnabled( candidate, staging ), + /disabled in this repository/ + ); + } +} ); + +/** + * @param {number} status + * @param {unknown} [body] + */ +function response( status, body = {} ) { return { ok: status >= 200 && status < 300, status, - json: async () => value, - text: async () => JSON.stringify( value ), + json: async () => body, + text: async () => JSON.stringify( body ), }; } -function run( overrides = {} ) { - return { - id: 456, - run_attempt: 1, - head_sha: 'a'.repeat( 40 ), - head_branch: 'feature', - head_repository: { - full_name: 'contributor/wordpress-develop', - owner: { login: 'contributor' }, - }, - ...overrides, +/** + * @param {any[]} responses + */ +function recordingFetch( responses ) { + /** @type {any[]} */ + const calls = []; + /** + * @param {string} url + * @param {Record} options + */ + const fetchImplementation = async ( url, options ) => { + calls.push( { url, options } ); + const next = responses.shift(); + if ( ! next ) { + throw new Error( `Unexpected request for ${ url }.` ); + } + return next; }; + return { calls, fetchImplementation }; } -function pullRequest( overrides = {} ) { - return { - number: 123, - base: { ref: 'trunk' }, - head: { - sha: 'a'.repeat( 40 ), - ref: 'feature', - repo: { full_name: 'contributor/wordpress-develop' }, - }, - ...overrides, - }; +/** + * @param {any[]} responses + */ +function apiWith( responses ) { + const { calls, fetchImplementation } = recordingFetch( responses ); + const api = new GitHubApi( repository, 'token', fetchImplementation ); + api.backoff = async () => {}; + return { api, calls }; } -test( 'API requests use the repository token and report failures', async () => { - const calls = []; - const api = new GitHubApi( - 'WordPress/wordpress-develop', - 'token', - async ( url, options ) => { - calls.push( { url, options } ); - return response( { id: 456 } ); - } - ); - assert.equal( ( await api.getRun( 456 ) ).id, 456 ); - assert.match( calls[ 0 ].url, /actions\/runs\/456$/ ); +test( 'a read is retried through a server error', async () => { + const { api, calls } = apiWith( [ + response( 500 ), + response( 200, { id: 5 } ), + ] ); + + assert.deepEqual( await api.getRun( 5 ), { id: 5 } ); + assert.equal( calls.length, 2 ); assert.equal( calls[ 0 ].options.headers.Authorization, 'Bearer token' ); +} ); - const failing = new GitHubApi( - 'WordPress/wordpress-develop', - 'token', - async () => response( { message: 'no' }, 403 ) - ); - await assert.rejects( failing.getRun( 456 ), /HTTP 403/ ); +test( 'a write is not repeated', async () => { + const { api, calls } = apiWith( [ response( 500, { message: 'boom' } ) ] ); + + await assert.rejects( api.createComment( 1, 'body' ), /HTTP 500/ ); + assert.equal( calls.length, 1 ); } ); -test( 'a fork run resolves its PR without workflow pull_requests data', async () => { - const api = new GitHubApi( - 'WordPress/wordpress-develop', - 'token', - async ( url ) => { - assert.match( url, /state=open/ ); - assert.match( url, /head=contributor%3Afeature/ ); - return response( [ pullRequest() ] ); - } - ); - assert.equal( ( await api.findPullRequestForRun( run() ) ).number, 123 ); - const obsolete = new GitHubApi( - 'WordPress/wordpress-develop', - 'token', - async () => response( [] ) - ); - assert.equal( await obsolete.findPullRequestForRun( run() ), null ); +test( 'a missing release reads as no release and other failures carry the status', async () => { + const missing = apiWith( [ response( 404 ) ] ); + assert.equal( await missing.api.getRelease(), null ); - const ambiguous = new GitHubApi( - 'WordPress/wordpress-develop', - 'token', - async () => - response( [ pullRequest(), pullRequest( { number: 124 } ) ] ) - ); - await assert.rejects( - ambiguous.findPullRequestForRun( run() ), - /Expected one pull request/ - ); + const forbidden = apiWith( [ response( 403, { message: 'no' } ) ] ); + await assert.rejects( forbidden.api.getRelease(), ( error ) => { + assert.equal( /** @type {any} */ ( error ).status, 403 ); + return true; + } ); } ); -test( 'latest run lookup binds SHA, branch, repository, and current attempt', async () => { - const calls = []; - const api = new GitHubApi( - 'WordPress/wordpress-develop', - 'token', - async ( url ) => { - calls.push( url ); - if ( url.includes( '/actions/workflows/' ) ) { - return response( { - workflow_runs: [ - run( { id: 789, head_branch: 'other' } ), - run( { id: 456 } ), - ], - } ); - } - if ( url.includes( '/jobs?' ) ) { - return response( { - jobs: [ - { - name: 'Build Code Reference snapshot', - conclusion: 'success', - }, - ], - } ); - } - return response( run( { id: 456, run_attempt: 2 } ) ); - } - ); - const latest = await api.latestPreviewRun( run() ); - assert.equal( latest.run_attempt, 2 ); - assert.match( calls[ 0 ], /head_sha=/ ); - assert.match( calls[ 1 ], /actions\/runs\/456$/ ); - assert.match( calls[ 2 ], /runs\/456\/attempts\/2\/jobs/ ); +test( 'listings follow every page', async () => { + const first = Array.from( { length: 100 }, ( _, index ) => ( { + id: index, + } ) ); + const { api, calls } = apiWith( [ + response( 200, first ), + response( 200, [ { id: 100 } ] ), + ] ); + + assert.equal( ( await api.listReleaseAssets( 9 ) ).length, 101 ); + assert.match( calls[ 0 ].url, /per_page=100&page=1/ ); + assert.match( calls[ 1 ].url, /per_page=100&page=2/ ); } ); -test( 'trunk lookup binds the workflow, branch, repository, and current attempt', async () => { - const calls = []; - const api = new GitHubApi( - 'WordPress/wordpress-develop', - 'token', - async ( url ) => { - calls.push( url ); - if ( url.includes( '/git/ref/heads/trunk' ) ) { - return response( { object: { sha: 'a'.repeat( 40 ) } } ); - } - if ( url.includes( '/actions/workflows/' ) ) { - return response( { - workflow_runs: [ - run( { - id: 789, - event: 'push', - head_branch: 'trunk', - head_repository: { - full_name: 'other/wordpress-develop', - }, - } ), - run( { - id: 456, - event: 'push', - head_branch: 'trunk', - head_repository: { - full_name: 'WordPress/wordpress-develop', - }, - } ), - ], - } ); - } - return response( - run( { - id: 456, - run_attempt: 2, - event: 'push', - head_branch: 'trunk', - head_repository: { - full_name: 'WordPress/wordpress-develop', - }, - } ) - ); - } +test( 'an upload replaces the asset a previous attempt left behind', async () => { + const { api, calls } = apiWith( [ + response( 422, { message: 'already_exists' } ), + response( 200, [ { id: 3, name: 'snapshot.zip' } ] ), + response( 204 ), + response( 200, { id: 4, name: 'snapshot.zip' } ), + ] ); + + assert.deepEqual( + await api.uploadReleaseAsset( + 9, + 'snapshot.zip', + Buffer.from( 'x' ), + 'application/zip' + ), + { id: 4, name: 'snapshot.zip' } ); - assert.equal( await api.getTrunkHeadSha(), 'a'.repeat( 40 ) ); - assert.equal( ( await api.latestTrunkPreviewRun() ).run_attempt, 2 ); - assert.match( calls[ 1 ], /event=push/ ); - assert.match( calls[ 1 ], /branch=trunk/ ); - assert.match( calls[ 2 ], /actions\/runs\/456$/ ); + assert.equal( calls[ 2 ].options.method, 'DELETE' ); } ); -test( 'skipped trigger runs neither publish nor supersede a build', async () => { - const api = new GitHubApi( - 'WordPress/wordpress-develop', - 'token', - async ( url ) => { - if ( url.includes( '/actions/workflows/' ) ) { - return response( { - workflow_runs: [ run( { id: 789 } ), run() ], - } ); - } - if ( url.includes( '/jobs?' ) ) { - return response( { - jobs: [ - { - name: 'Build Code Reference snapshot', - conclusion: url.includes( '/789/' ) - ? 'skipped' - : 'success', - }, - ], - } ); - } - return response( - url.endsWith( '/789' ) ? run( { id: 789 } ) : run() - ); - } +test( 'a published asset is read without credentials', async () => { + const { api, calls } = apiWith( [ response( 200, { sourceSha: 'abc' } ) ] ); + + assert.deepEqual( + await api.readAssetJson( { + browser_download_url: 'https://github.com/download/snapshot.json', + } ), + { sourceSha: 'abc' } ); - assert.equal( ( await api.latestPreviewRun( run() ) ).id, 456 ); - assert.equal( await api.isSkippedPreviewBuild( run( { id: 789 } ) ), true ); + assert.equal( calls[ 0 ].options.headers, undefined ); } ); -test( 'release operations keep immutable names and bytes', async () => { - const calls = []; - const api = new GitHubApi( - 'WordPress/wordpress-develop', - 'token', - async ( url, options ) => { - calls.push( { url, options } ); - if ( options.method === 'DELETE' ) { - return response( null, 204 ); - } - if ( url.includes( '/actions/caches?' ) ) { - return response( { actions_caches: [] } ); - } - return response( { id: 9, name: 'snapshot.zip' } ); - } - ); - await api.createRelease(); - await api.uploadReleaseAsset( - 9, - 'snapshot.zip', - Buffer.from( 'snapshot' ), - 'application/zip' - ); - await api.deleteReleaseAsset( 10 ); - await api.listActionCaches( 'refs/pull/123/merge' ); - await api.deleteActionCache( 11 ); - assert.equal( calls[ 0 ].options.json.tag_name, RELEASE_TAG ); - assert.match( calls[ 1 ].url, /^https:\/\/uploads\.github\.com/ ); - assert.equal( calls[ 1 ].options.body.toString(), 'snapshot' ); - assert.equal( calls[ 2 ].options.method, 'DELETE' ); - assert.match( calls[ 3 ].url, /ref=refs%2Fpull%2F123%2Fmerge/ ); - assert.equal( calls[ 4 ].options.method, 'DELETE' ); +test( 'the pull request behind a fork build run is found by head identity', async () => { + const run = { + head_sha: 'a'.repeat( 40 ), + head_branch: 'feature', + head_repository: { + full_name: 'contributor/wordpress-develop', + owner: { login: 'contributor' }, + }, + }; + const { api, calls } = apiWith( [ + response( 200, [ + { + number: 1, + head: { + sha: 'b'.repeat( 40 ), + repo: { full_name: 'contributor/wordpress-develop' }, + }, + }, + { + number: 2, + head: { + sha: 'a'.repeat( 40 ), + repo: { full_name: 'contributor/wordpress-develop' }, + }, + }, + ] ), + ] ); + + assert.equal( ( await api.findPullRequestForRun( run ) ).number, 2 ); + assert.match( calls[ 0 ].url, /head=contributor%3Afeature/ ); } ); -test( 'Git object operations create and atomically move a pointer ref', async () => { - const calls = []; - const api = new GitHubApi( - 'WordPress/wordpress-develop', - 'token', - async ( url, options ) => { - calls.push( { url, options } ); - return response( { - sha: 'a'.repeat( 40 ), - object: { sha: 'b'.repeat( 40 ) }, - } ); - } - ); - await api.getGitReference( 'heads/docs-preview-code-reference' ); - await api.createGitBlob( '{"blueprint":true}\n' ); - await api.createGitTree( 'code-reference-trunk.json', 'a'.repeat( 40 ) ); - await api.createGitCommit( - 'Update Code Reference preview', - 'b'.repeat( 40 ), - 'c'.repeat( 40 ) - ); - await api.createGitReference( - 'heads/docs-preview-code-reference', - 'd'.repeat( 40 ) - ); - await api.updateGitReference( - 'heads/docs-preview-code-reference', - 'e'.repeat( 40 ) - ); - assert.match( calls[ 0 ].url, /git\/ref\/heads\/docs-preview/ ); - assert.deepEqual( calls[ 1 ].options.json, { - content: '{"blueprint":true}\n', - encoding: 'utf-8', - } ); - assert.equal( calls[ 2 ].options.json.tree[ 0 ].mode, '100644' ); - assert.deepEqual( calls[ 3 ].options.json.parents, [ 'c'.repeat( 40 ) ] ); +test( 'a run without a fork head matches no pull request', async () => { + const { api } = apiWith( [] ); assert.equal( - calls[ 4 ].options.json.ref, - 'refs/heads/docs-preview-code-reference' + await api.findPullRequestForRun( { head_branch: null } ), + null ); - assert.deepEqual( calls[ 5 ].options.json, { - sha: 'e'.repeat( 40 ), - force: true, - } ); } ); -test( 'only the marked bot comment is updated', async () => { - const calls = []; - const api = new GitHubApi( - 'WordPress/wordpress-develop', - 'token', - async ( url, options ) => { - calls.push( { url, options } ); - if ( url.includes( '/comments?per_page' ) ) { - return response( [ - { id: 1, user: { type: 'User' }, body: COMMENT_MARKER }, - { id: 2, user: { type: 'Bot' }, body: COMMENT_MARKER }, - ] ); - } - if ( options.method === 'DELETE' ) { - return response( null, 204 ); - } - return response( { id: 2 } ); - } - ); - assert.equal( ( await api.findPreviewComment( 123 ) ).id, 2 ); - await api.updateComment( 2, 'updated' ); - await api.removeLabel( 123 ); - assert.equal( calls[ 1 ].options.method, 'PATCH' ); - assert.deepEqual( calls[ 1 ].options.json, { body: 'updated' } ); - assert.match( calls[ 2 ].url, /labels\/docs-preview$/ ); +test( 'a build run whose build job skipped never supersedes a real build', async () => { + const run = { + head_sha: 'a'.repeat( 40 ), + head_branch: 'feature', + head_repository: { full_name: 'contributor/wordpress-develop' }, + }; + const { api } = apiWith( [ + response( 200, { + workflow_runs: [ + { id: 20, run_attempt: 1, ...run }, + { id: 10, run_attempt: 1, ...run }, + ], + } ), + response( 200, { + jobs: [ + { + name: 'Build Code Reference snapshot', + conclusion: 'skipped', + }, + ], + } ), + response( 200, { + jobs: [ + { + name: 'Build Code Reference snapshot', + conclusion: 'success', + }, + ], + } ), + ] ); + + assert.equal( ( await api.latestPullRequestBuildRun( run ) ).id, 10 ); +} ); + +test( 'the newest trunk build run is the first the API reports', async () => { + const { api, calls } = apiWith( [ + response( 200, { workflow_runs: [ { id: 30 } ] } ), + ] ); + + assert.equal( ( await api.latestTrunkBuildRun() ).id, 30 ); + assert.match( calls[ 0 ].url, /event=push&branch=trunk&per_page=1/ ); } ); diff --git a/.github/scripts/docs-playground-preview/test/inputs.test.mjs b/.github/scripts/docs-playground-preview/test/inputs.test.mjs new file mode 100644 index 0000000000000..56fa91b2829e3 --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/inputs.test.mjs @@ -0,0 +1,171 @@ +import assert from 'node:assert/strict'; +import { appendFile, mkdir, mkdtemp, writeFile } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { test } from 'node:test'; + +import { resolveBuildInputs, resolveWordPressBeta } from '../lib/inputs.mjs'; + +/** + * @param {Record[]} offers + */ +function versionApi( offers ) { + /** @param {string} url */ + return async ( url ) => { + assert.equal( + url, + 'https://api.wordpress.org/core/version-check/1.7/?channel=beta' + ); + return { ok: true, json: async () => ( { offers } ) }; + }; +} + +test( 'the preview runs the newest beta or RC build', async () => { + const resolved = await resolveWordPressBeta( + versionApi( [ + { response: 'upgrade', version: '7.1.2' }, + { response: 'development', version: '7.2-RC2' }, + ] ) + ); + assert.deepEqual( resolved, { channel: 'beta', version: '7.2-RC2' } ); +} ); + +test( 'the preview follows the stable release between betas, with a notice', async ( t ) => { + const write = t.mock.method( process.stderr, 'write', () => true ); + const resolved = await resolveWordPressBeta( + versionApi( [ + { response: 'upgrade', version: '7.1.2' }, + { response: 'autoupdate', version: '7.0.3' }, + ] ) + ); + assert.deepEqual( resolved, { channel: 'stable', version: '7.1.2' } ); + assert.equal( write.mock.callCount(), 1 ); + assert.match( + String( write.mock.calls[ 0 ].arguments[ 0 ] ), + /^::notice::.*tracking the stable release 7\.1\.2 until the next beta appears\./ + ); +} ); + +test( 'a version API response with no concrete build fails the build', async () => { + for ( const body of [ + {}, + { offers: [] }, + { offers: [ { response: 'upgrade', version: 'trunk' } ] }, + ] ) { + await assert.rejects( + resolveWordPressBeta( async () => ( { + ok: true, + json: async () => body, + } ) ), + /no concrete beta or stable build/ + ); + } +} ); + +/** + * A checkout with one file in each place the build reads from. + */ +async function repositoryTree() { + const root = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-inputs-' ) + ); + for ( const [ relative, content ] of Object.entries( { + '.github/docs-playground-preview/dependencies.json': JSON.stringify( { + cacheSchemaVersion: 1, + playground: { phpVersion: '8.4' }, + } ), + '.github/docs-playground-preview/.nvmrc': '20.20.2\n', + '.github/docs-playground-preview/package-lock.json': '{}\n', + '.github/scripts/docs-playground-preview/lib/base.mjs': '// base\n', + '.github/scripts/docs-playground-preview/lib/snapshot.mjs': + '// snapshot\n', + '.github/scripts/docs-playground-preview/php/configure-base.php': + ' { + const root = await repositoryTree(); + let previous = await cacheKey( root ); + // The lifecycle deletes a closed pull request's caches by this prefix. + assert.match( previous, /^docs-preview-base-v1-[0-9a-f]{64}$/ ); + + for ( const relative of [ + '.github/docs-playground-preview/dependencies.json', + '.github/docs-playground-preview/.nvmrc', + '.github/docs-playground-preview/package-lock.json', + '.github/scripts/docs-playground-preview/lib/base.mjs', + '.github/scripts/docs-playground-preview/lib/snapshot.mjs', + '.github/scripts/docs-playground-preview/php/configure-base.php', + ] ) { + // Trailing whitespace leaves every one of these files valid. + await appendFile( path.join( root, relative ), '\n' ); + const changed = await cacheKey( root ); + assert.notEqual( changed, previous, `${ relative } is not covered` ); + previous = changed; + } + + // The tests never run during a build, so they do not rebuild the base. + await appendFile( + path.join( + root, + '.github/scripts/docs-playground-preview/test/base.test.mjs' + ), + '\n' + ); + assert.equal( await cacheKey( root ), previous ); +} ); + +test( 'a different runner or WordPress version is a different base', async () => { + const root = await repositoryTree(); + const baseline = await cacheKey( root ); + const inputs = { + repositoryRoot: root, + cacheRoot: path.join( root, 'cache' ), + platform: 'linux', + architecture: 'x64', + runnerImage: 'ubuntu-24.04', + fetch: versionApi( [ { version: '7.2-RC2' } ] ), + }; + + assert.notEqual( + ( + await resolveBuildInputs( { + ...inputs, + runnerImage: 'ubuntu-26.04', + } ) + ).cacheKey, + baseline + ); + assert.notEqual( + ( + await resolveBuildInputs( { + ...inputs, + fetch: versionApi( [ { version: '7.2-RC3' } ] ), + } ) + ).cacheKey, + baseline + ); +} ); diff --git a/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs b/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs index ac5c63dc9f2ed..6aa6152e1abc0 100644 --- a/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs +++ b/.github/scripts/docs-playground-preview/test/lifecycle.test.mjs @@ -1,379 +1,176 @@ import assert from 'node:assert/strict'; -import { createHash } from 'node:crypto'; import { test } from 'node:test'; -import { managePullRequest } from '../lifecycle.mjs'; -import { - createPublishedMetadata, - renderPreviewComment, -} from '../lib/publisher.mjs'; import { - COMMENT_MARKER, - PLAYGROUND_ORIGIN, - metadataAssetName, - releaseAssetUrl, - snapshotAssetName, -} from '../lib/publication.mjs'; - -const repository = 'WordPress/wordpress-develop'; -const sourceRepository = 'contributor/wordpress-develop'; -const currentSha = 'a'.repeat( 40 ); -const previewSha = 'c'.repeat( 40 ); -const snapshotBytes = Buffer.from( 'snapshot' ); + FakeGitHub, + pullRequest, + releaseAsset, + repository, + sourceRepository, + sourceSha, +} from './fake-github.mjs'; +import { readyComment } from '../lib/comment.mjs'; +import { managePullRequest } from '../lifecycle.mjs'; -function pullRequest( state = 'open', labels = [], headSha = currentSha ) { +const preview = { + sourceRepository, + sourceSha, + publication: { + publishedAt: '2026-09-04T12:00:00.000Z', + playgroundUrl: 'https://playground.wordpress.net/?blueprint-url=x', + }, +}; + +/** + * @param {string} action + * @param {Record} [overrides] + */ +function event( action, overrides = {} ) { return { - number: 123, - state, - base: { ref: 'trunk' }, - head: { - sha: headSha, - ref: 'feature', - repo: { full_name: sourceRepository }, - }, - labels, + action, + pull_request: pullRequest( { labels: [], ...overrides } ), }; } -function event( - action, - state = action === 'closed' ? 'closed' : 'open', - headSha = currentSha -) { - return { action, pull_request: pullRequest( state, [], headSha ) }; +/** + * @param {Record} api + * @param {Record} payload + */ +function manage( api, payload ) { + return managePullRequest( { repository, api, event: payload } ); } -function previewMetadata() { - const snapshotFilename = snapshotAssetName( { - pullRequestNumber: 123, - sourceSha: previewSha, - workflowRunId: 300, - workflowRunAttempt: 1, +test( 'a push away from the built commit marks the comment stale', async () => { + const metadata = 'code-reference-pr-123-old.json'; + const api = new FakeGitHub( { + comments: [ { id: 7, body: readyComment( preview, 'https://run' ) } ], + assets: [ + releaseAsset( 1, metadata ), + releaseAsset( 2, 'code-reference-pr-123-old.zip' ), + ], + assetJson: new Map( [ [ metadata, preview ] ] ), } ); - return createPublishedMetadata( - { - schemaVersion: 1, - sourceRepository, - pullRequestNumber: 123, - sourceSha: previewSha, - workflowRunId: '300', - workflowRunAttempt: 1, - runUrl: `https://github.com/${ repository }/actions/runs/300`, - resolvedWordPressBeta: { - channel: 'beta', - version: '7.2-beta1', - downloadUrl: - 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', + const result = await manage( + api, + event( 'synchronize', { + head: { + sha: 'b'.repeat( 40 ), + repo: { full_name: sourceRepository }, }, - phpVersion: '8.4', - dependencyManifestDigest: 'b'.repeat( 64 ), - snapshotFilename, - snapshotBytes: snapshotBytes.byteLength, - snapshotSha256: createHash( 'sha256' ) - .update( snapshotBytes ) - .digest( 'hex' ), - buildStatus: 'success', - validationStatus: 'passed', - generationTimestamp: '2026-08-08T12:00:00.000Z', - }, - repository, - '2026-08-08T13:00:00.000Z' + } ) ); -} - -function releaseAsset( id, name ) { - return { - id, - name, - created_at: '2026-08-08T13:00:00.000Z', - browser_download_url: releaseAssetUrl( repository, name ), - }; -} - -class FakeApi { - constructor() { - this.currentPullRequest = pullRequest(); - this.release = { id: 9 }; - this.assets = []; - this.caches = []; - this.metadata = new Map(); - this.assetBytes = new Map(); - this.comment = { id: 20, body: `${ COMMENT_MARKER }\nReady` }; - this.deletedAssets = []; - this.deletedCaches = []; - this.commentBodies = []; - this.failedAsset = null; - this.cacheRef = null; - this.replacementComment = null; - this.commentReads = 0; - } - - async getPullRequest() { - return this.currentPullRequest; - } - - async getRelease() { - return this.release; - } - - async listReleaseAssets() { - return [ ...this.assets ]; - } - - async listActionCaches( ref ) { - this.cacheRef = ref; - return [ ...this.caches ]; - } - - async findPreviewComment() { - this.commentReads++; - if ( this.commentReads > 1 && this.replacementComment ) { - return this.replacementComment; - } - return this.comment; - } - - async updateComment( id, body ) { - assert.equal( id, this.comment.id ); - this.commentBodies.push( body ); - this.comment = { id, body }; - } - async deleteReleaseAsset( id ) { - if ( id === this.failedAsset ) { - throw new Error( 'release deletion failed' ); - } - this.deletedAssets.push( id ); - } - - async deleteActionCache( id ) { - this.deletedCaches.push( id ); - } -} - -function installPreview( api ) { - const metadata = previewMetadata(); - const metadataName = metadataAssetName( metadata.snapshotFilename ); - api.assets.push( - releaseAsset( 1, metadata.snapshotFilename ), - releaseAsset( 2, metadataName ) - ); - api.metadata.set( metadataName, metadata ); - api.assetBytes.set( metadata.snapshotFilename, snapshotBytes ); - return metadata; -} - -function publicFetch( api ) { - return async ( url ) => { - if ( url.includes( 'wordpress-playground-cors-proxy.net' ) ) { - const name = url.split( '/' ).at( -1 ); - return { - status: 200, - headers: { - get: ( header ) => - ( { - 'x-playground-cors-proxy': 'true', - 'access-control-allow-origin': PLAYGROUND_ORIGIN, - } )[ header.toLowerCase() ] || null, - }, - arrayBuffer: async () => api.assetBytes.get( name ), - }; - } - const name = url.split( '/' ).at( -1 ); - return { - status: api.metadata.has( name ) ? 200 : 404, - json: async () => api.metadata.get( name ), - }; - }; -} - -function options( api, lifecycleEvent ) { - return { - repository, - stagingVariable: '', - event: lifecycleEvent, - api, - fetchImplementation: publicFetch( api ), - warning: () => {}, - }; -} - -test( 'a later unlabeled commit marks the healthy preview stale', async () => { - const api = new FakeApi(); - installPreview( api ); - const result = await managePullRequest( - options( api, event( 'synchronize' ) ) - ); assert.equal( result.status, 'stale' ); - assert.match( api.comment.body, /Status:\*\* Stale/ ); - assert.match( api.comment.body, new RegExp( previewSha ) ); - assert.match( api.comment.body, new RegExp( currentSha ) ); - assert.match( api.comment.body, /add the `docs-preview` label again/i ); + assert.match( api.comments[ 0 ].body, /\*\*Status:\*\* Stale/ ); + assert.ok( + api.comments[ 0 ].body.includes( preview.publication.playgroundUrl ) + ); } ); -test( 'a proxy outage cannot remove the last successful stale link', async () => { - const api = new FakeApi(); - const preview = installPreview( api ); - api.comment.body = renderPreviewComment( { - status: 'ready', - preview, - runUrl: preview.runUrl, +test( 'a comment that already describes the current head is left alone', async () => { + const api = new FakeGitHub( { + comments: [ { id: 7, body: readyComment( preview, 'https://run' ) } ], } ); - const lifecycleOptions = options( api, event( 'synchronize' ) ); - lifecycleOptions.fetchImplementation = async () => ( { status: 503 } ); + const result = await manage( api, event( 'synchronize' ) ); - const result = await managePullRequest( lifecycleOptions ); - - assert.equal( result.status, 'stale' ); - assert.match( api.comment.body, /Status:\*\* Stale/ ); - assert.match( api.comment.body, /Latest successful docs preview/ ); - assert.ok( api.comment.body.includes( preview.publication.playgroundUrl ) ); - assert.match( api.comment.body, new RegExp( previewSha ) ); - assert.match( api.comment.body, new RegExp( currentSha ) ); -} ); - -test( 'a labeled synchronize event leaves the publisher in charge', async () => { - const api = new FakeApi(); - api.currentPullRequest = pullRequest( 'open', [ - { name: 'docs-preview' }, - ] ); - const result = await managePullRequest( - options( api, event( 'synchronize' ) ) - ); assert.equal( result.status, 'ignored' ); - assert.equal( api.commentBodies.length, 0 ); -} ); - -test( 'a newer terminal comment supersedes an older stale event', async () => { - const api = new FakeApi(); - installPreview( api ); - api.replacementComment = { - id: api.comment.id, - body: renderPreviewComment( { - status: 'failed', - sourceRepository, - sourceSha: currentSha, - at: '2026-08-09T13:00:00.000Z', - runUrl: `https://github.com/${ repository }/actions/runs/456`, - previous: previewMetadata(), - } ), - }; - await assert.rejects( - managePullRequest( options( api, event( 'synchronize' ) ) ), - /superseded/ - ); - assert.equal( api.commentBodies.length, 0 ); + assert.match( api.comments[ 0 ].body, /\*\*Status:\*\* Ready/ ); } ); -test( 'a delayed stale event ignores the current terminal comment', async () => { - const api = new FakeApi(); - api.comment.body = renderPreviewComment( { - status: 'failed', - sourceRepository, - sourceSha: currentSha, - at: '2026-08-09T13:00:00.000Z', - runUrl: `https://github.com/${ repository }/actions/runs/456`, - previous: previewMetadata(), +test( 'a labelled pull request is left to the build that is already running', async () => { + const api = new FakeGitHub( { + comments: [ { id: 7, body: readyComment( preview, 'https://run' ) } ], } ); - const result = await managePullRequest( - options( api, event( 'synchronize' ) ) + const result = await manage( + api, + event( 'synchronize', { + labels: [ { name: 'docs-preview' } ], + head: { + sha: 'b'.repeat( 40 ), + repo: { full_name: sourceRepository }, + }, + } ) ); + assert.equal( result.status, 'ignored' ); - assert.equal( api.commentBodies.length, 0 ); + assert.match( api.comments[ 0 ].body, /\*\*Status:\*\* Ready/ ); } ); -test( 'returning to a stale preview SHA refreshes obsolete visible state', async () => { - const api = new FakeApi(); - installPreview( api ); - await managePullRequest( options( api, event( 'synchronize' ) ) ); - assert.match( api.comment.body, new RegExp( currentSha ) ); - - api.currentPullRequest = pullRequest( 'open', [], previewSha ); - const result = await managePullRequest( - options( api, event( 'synchronize', 'open', previewSha ) ) +test( 'a stale comment with no surviving preview says so', async () => { + const api = new FakeGitHub( { + comments: [ { id: 7, body: readyComment( preview, 'https://run' ) } ], + } ); + const result = await manage( + api, + event( 'synchronize', { + head: { + sha: 'b'.repeat( 40 ), + repo: { full_name: sourceRepository }, + }, + } ) ); + assert.equal( result.status, 'stale' ); - assert.doesNotMatch( api.comment.body, new RegExp( currentSha ) ); - assert.match( api.comment.body, new RegExp( previewSha ) ); + assert.match( + api.comments[ 0 ].body, + /no healthy docs preview is available/ + ); } ); -test( 'a stale failed attempt still tells the maintainer how to rebuild', async () => { - const api = new FakeApi(); - api.comment.body = renderPreviewComment( { - status: 'failed', - sourceRepository, - sourceSha: previewSha, - at: '2026-08-08T13:00:00.000Z', - runUrl: `https://github.com/${ repository }/actions/runs/300`, - previous: null, +test( 'a push with neither a preview nor a known source reports nothing to do', async () => { + const api = new FakeGitHub( { + comments: [ { id: 7, body: '' } ], } ); - const result = await managePullRequest( - options( api, event( 'synchronize' ) ) + const result = await manage( + api, + event( 'synchronize', { + head: { + sha: 'b'.repeat( 40 ), + repo: { full_name: sourceRepository }, + }, + } ) ); - assert.equal( result.status, 'stale' ); - assert.match( api.comment.body, /no healthy docs preview is available/ ); - assert.match( api.comment.body, new RegExp( previewSha ) ); - assert.match( api.comment.body, new RegExp( currentSha ) ); - assert.match( api.comment.body, /add the `docs-preview` label again/i ); + + assert.equal( result.status, 'unavailable' ); } ); -test( 'closing a PR deletes only its preview assets and scoped docs caches', async () => { - const api = new FakeApi(); - api.currentPullRequest = pullRequest( 'closed' ); - installPreview( api ); - api.assets.push( - releaseAsset( 3, `code-reference-pr-124-${ previewSha }-300-1.zip` ), - releaseAsset( 4, 'code-reference-trunk-snapshot.zip' ) - ); - api.caches.push( - { - id: 10, - ref: 'refs/pull/123/merge', - key: `docs-preview-base-v1-${ 'd'.repeat( 64 ) }`, - }, - { - id: 11, - ref: 'refs/pull/123/merge', - key: 'unrelated-cache', - }, - { - id: 12, - ref: 'refs/heads/trunk', - key: `docs-preview-base-v1-${ 'e'.repeat( 64 ) }`, - } - ); - const result = await managePullRequest( options( api, event( 'closed' ) ) ); +test( 'closing a pull request deletes its assets and caches and expires the comment', async () => { + const api = new FakeGitHub( { + comments: [ { id: 7, body: readyComment( preview, 'https://run' ) } ], + assets: [ + releaseAsset( 1, 'code-reference-pr-123-old.zip' ), + releaseAsset( 2, 'code-reference-pr-123-old.json' ), + releaseAsset( 3, 'code-reference-pr-999-other.zip' ), + releaseAsset( 4, 'code-reference-trunk-x.zip' ), + ], + caches: [ + { id: 11, key: 'docs-preview-base-v1-abc' }, + { id: 12, key: 'unrelated-cache' }, + ], + } ); + const result = await manage( api, event( 'closed' ) ); + assert.equal( result.status, 'expired' ); assert.deepEqual( api.deletedAssets, [ 1, 2 ] ); - assert.deepEqual( api.deletedCaches, [ 10 ] ); - assert.equal( api.cacheRef, 'refs/pull/123/merge' ); - assert.match( api.comment.body, /Status:\*\* Expired/ ); + assert.deepEqual( api.deletedCaches, [ 11 ] ); + assert.match( api.comments[ 0 ].body, /\*\*Status:\*\* Expired/ ); } ); -test( 'asset cleanup failure preserves a comment that still links live assets', async () => { - const api = new FakeApi(); - api.currentPullRequest = pullRequest( 'closed' ); - installPreview( api ); - api.failedAsset = 1; - api.caches.push( { - id: 10, - ref: 'refs/pull/123/merge', - key: `docs-preview-base-v1-${ 'd'.repeat( 64 ) }`, - } ); +test( 'an unsupported action is refused', async () => { await assert.rejects( - managePullRequest( options( api, event( 'closed' ) ) ), - /Pull request cleanup failed/ + manage( new FakeGitHub(), event( 'opened' ) ), + /Unsupported pull request action opened\./ ); - assert.deepEqual( api.deletedAssets, [ 2 ] ); - assert.deepEqual( api.deletedCaches, [ 10 ] ); - assert.equal( api.commentBodies.length, 0 ); } ); -test( 'a reopened PR supersedes close cleanup before any deletion', async () => { - const api = new FakeApi(); - installPreview( api ); - const result = await managePullRequest( options( api, event( 'closed' ) ) ); - assert.equal( result.status, 'superseded' ); - assert.deepEqual( api.deletedAssets, [] ); - assert.deepEqual( api.deletedCaches, [] ); +test( 'a repository without deployment enabled runs no lifecycle', async () => { + await assert.rejects( + managePullRequest( { + repository: 'sirreal/wordpress-develop', + stagingVariable: 'false', + api: new FakeGitHub(), + event: event( 'closed' ), + } ), + /disabled in this repository/ + ); } ); diff --git a/.github/scripts/docs-playground-preview/test/parser.test.mjs b/.github/scripts/docs-playground-preview/test/parser.test.mjs index 8d4f33e09cb86..b38bb98e5fbc0 100644 --- a/.github/scripts/docs-playground-preview/test/parser.test.mjs +++ b/.github/scripts/docs-playground-preview/test/parser.test.mjs @@ -1,11 +1,12 @@ import assert from 'node:assert/strict'; -import { mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises'; +import { mkdir, mkdtemp, readFile, symlink, writeFile } from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import { test } from 'node:test'; -import { generateParserJson, inspectParserRecords } from '../lib/parser.mjs'; +import { inspectParserRecords, stageCorePhp } from '../lib/parser.mjs'; +/** @returns {any[]} */ function records() { return [ { @@ -36,17 +37,71 @@ function records() { ]; } -test( 'parser inspection counts every supported reference type', () => { - const parsed = records(); - const result = inspectParserRecords( parsed, { - classes: 1, - methods: 2, - functions: 1, - hooks: 2, - filters: 2, - } ); +const floors = { + classes: 1, + methods: 2, + functions: 1, + hooks: 2, + filters: 2, +}; + +test( 'only Core PHP outside bundled plugins and themes is staged', async () => { + const root = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-source-' ) + ); + const source = path.join( root, 'src' ); + for ( const relative of [ + 'wp-includes/version.php', + 'wp-admin/admin.php', + 'wp-content/plugins/hello.php', + 'wp-content/themes/twentytwenty/functions.php', + ] ) { + await mkdir( path.dirname( path.join( source, relative ) ), { + recursive: true, + } ); + await writeFile( + path.join( source, relative ), + ` { + const root = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-none-' ) + ); + await assert.rejects( + stageCorePhp( root, path.join( root, 'staged' ) ), + /No WordPress source tree found/ + ); +} ); + +test( 'every reference type is counted', () => { + const result = inspectParserRecords( records(), floors ); assert.deepEqual( result, { - records: 1, counts: { classes: 1, methods: 2, @@ -56,93 +111,18 @@ test( 'parser inspection counts every supported reference type', () => { }, failures: [], } ); - assert.equal( parsed[ 0 ].root, '/tmp/docs-preview-source' ); } ); -test( 'sanity floors report catastrophic undercounts by symbol type', () => { - const result = inspectParserRecords( records(), { - classes: 2, - methods: 3, - functions: 2, - hooks: 3, - filters: 3, - } ); +test( 'a parse that lost most of Core is reported as a failure', () => { + const result = inspectParserRecords( [], floors ); assert.equal( result.failures.length, 5 ); - assert.match( result.failures[ 0 ], /1 classes; expected at least 2/ ); -} ); - -test( 'an empty parser array is an advisory validation failure', () => { - const result = inspectParserRecords( [], { - classes: 1, - methods: 1, - functions: 1, - hooks: 1, - filters: 1, - } ); - assert.deepEqual( result.counts, { - classes: 0, - methods: 0, - functions: 0, - hooks: 0, - filters: 0, - } ); - assert.equal( result.failures.length, 6 ); - assert.equal( result.failures[ 0 ], 'Parser produced no file records.' ); -} ); - -test( 'parser inspection rejects excluded bundled extension source', () => { - const parsed = records(); - parsed[ 0 ].path = 'wp-content/plugins/hello.php'; - assert.throws( - () => inspectParserRecords( parsed, {} ), - /contains excluded source/ - ); + assert.match( result.failures[ 0 ], /0 classes; expected at least 1/ ); } ); test( 'the importer never receives pull-request PHP as its version include', () => { const parsed = records(); - parsed.push( { - path: 'wp-includes/version.php', - functions: [], - classes: [], - hooks: [], - } ); - inspectParserRecords( parsed, {} ); + parsed.push( { path: 'wp-includes/version.php' } ); + inspectParserRecords( parsed, floors ); assert.equal( parsed[ 0 ].root, '/tmp/docs-preview-source' ); assert.equal( parsed[ 1 ].root, '/tmp/docs-preview-version' ); } ); - -test( 'generateParserJson stages PHP, invokes the pinned parser, and normalizes output', async () => { - const temporary = await mkdtemp( - path.join( os.tmpdir(), 'docs-preview-parser-' ) - ); - const source = path.join( temporary, 'wordpress/src' ); - await mkdir( path.join( source, 'wp-includes' ), { recursive: true } ); - await writeFile( path.join( source, 'wp-includes/version.php' ), ' { - invocation = { command, args }; - await writeFile( output, JSON.stringify( records() ) ); - }; - const result = await generateParserJson( { - source: path.dirname( source ), - stagedSource: path.join( temporary, 'staged' ), - parser: '/cache/parser', - output, - minimumSymbols: { - classes: 1, - methods: 2, - functions: 1, - hooks: 2, - filters: 2, - }, - runImplementation, - } ); - assert.equal( result.sourceFiles, 2 ); - assert.equal( invocation.command, 'php' ); - assert.equal( invocation.args.at( -1 ), output ); - const normalized = JSON.parse( await readFile( output, 'utf8' ) ); - assert.equal( normalized[ 0 ].root, '/tmp/docs-preview-source' ); -} ); diff --git a/.github/scripts/docs-playground-preview/test/playground.test.mjs b/.github/scripts/docs-playground-preview/test/playground.test.mjs deleted file mode 100644 index ba6e04aa5c206..0000000000000 --- a/.github/scripts/docs-playground-preview/test/playground.test.mjs +++ /dev/null @@ -1,29 +0,0 @@ -import assert from 'node:assert/strict'; -import { test } from 'node:test'; - -import { - createSnapshotArguments, - PLAYGROUND_BUILD_WORKERS, -} from '../lib/playground.mjs'; - -test( 'snapshot builds serialize Playground filesystem access', () => { - assert.equal( PLAYGROUND_BUILD_WORKERS, 1 ); - assert.deepEqual( - createSnapshotArguments( { - php: '8.4', - wp: '7.2-beta1', - blueprint: '/tmp/blueprint.json', - outfile: '/tmp/snapshot.zip', - workers: 2, - command: 'server', - } ), - { - php: '8.4', - wp: '7.2-beta1', - blueprint: '/tmp/blueprint.json', - outfile: '/tmp/snapshot.zip', - workers: PLAYGROUND_BUILD_WORKERS, - command: 'build-snapshot', - } - ); -} ); diff --git a/.github/scripts/docs-playground-preview/test/preview.test.mjs b/.github/scripts/docs-playground-preview/test/preview.test.mjs new file mode 100644 index 0000000000000..0d64d463a8466 --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/preview.test.mjs @@ -0,0 +1,141 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; + +import { + FakeGitHub, + releaseAsset, + repository, + sourceSha, +} from './fake-github.mjs'; +import { + createPublishedMetadata, + createTrunkBlueprint, + findLatestPublishedPreview, + metadataAssetName, + snapshotAssetName, +} from '../lib/preview.mjs'; + +test( 'asset names carry the run that produced them', () => { + assert.equal( + snapshotAssetName( { + pullRequestNumber: 123, + sourceSha, + workflowRunId: 456, + workflowRunAttempt: 2, + } ), + `code-reference-pr-123-${ sourceSha }-456-2.zip` + ); + assert.equal( + snapshotAssetName( { + pullRequestNumber: null, + sourceSha, + workflowRunId: 456, + workflowRunAttempt: 1, + } ), + `code-reference-trunk-${ sourceSha }-456-1.zip` + ); + assert.equal( + metadataAssetName( `code-reference-pr-123-${ sourceSha }-456-2.zip` ), + `code-reference-pr-123-${ sourceSha }-456-2.json` + ); +} ); + +test( 'a published preview opens the reference anonymously with networking off', () => { + const published = createPublishedMetadata( + { + phpVersion: '8.4', + resolvedWordPressBeta: { version: '7.2-beta1' }, + generationTimestamp: '2026-09-04T11:00:00.000Z', + }, + { + repository, + pullRequestNumber: null, + sourceRepository: repository, + sourceSha, + workflowRunId: 456, + workflowRunAttempt: 1, + runUrl: `https://github.com/${ repository }/actions/runs/456`, + }, + { filename: 'snapshot.zip', bytes: 10, sha256: 'b'.repeat( 64 ) } + ); + + assert.equal( + published.publication.snapshotUrl, + 'https://github.com/WordPress/wordpress-develop/releases/download/code-reference-playground-preview/snapshot.zip' + ); + // The repository README links this one. + assert.equal( + published.publication.stablePlaygroundUrl, + 'https://playground.wordpress.net/?blueprint-url=https%3A%2F%2Fwordpress-playground-cors-proxy.net%2Fhttps%3A%2F%2Fraw.githubusercontent.com%2FWordPress%2Fwordpress-develop%2Fdocs-preview-code-reference%2Fcode-reference-trunk.json' + ); + assert.equal( published.snapshotBytes, 10 ); + assert.ok( + published.publication.playgroundUrl.startsWith( + 'https://playground.wordpress.net/?blueprint-url=data:application/json,' + ) + ); + + const blueprint = createTrunkBlueprint( published ); + assert.equal( blueprint.landingPage, '/reference/' ); + assert.equal( blueprint.login, false ); + assert.deepEqual( blueprint.features, { networking: false } ); + assert.deepEqual( blueprint.preferredVersions, { + php: '8.4', + wp: '7.2-beta1', + } ); + assert.equal( + blueprint.steps[ 0 ].zipFile.url, + `https://wordpress-playground-cors-proxy.net/${ published.publication.snapshotUrl }` + ); + assert.match( blueprint.meta.description, new RegExp( sourceSha ) ); +} ); + +test( 'the newest published preview with both assets is the one reported', async () => { + const api = new FakeGitHub( { + assets: [ + releaseAsset( + 1, + 'code-reference-pr-123-old.json', + '2026-08-01T00:00:00Z' + ), + releaseAsset( + 2, + 'code-reference-pr-123-old.zip', + '2026-08-01T00:00:00Z' + ), + // Newest, but its metadata upload never completed. + releaseAsset( + 3, + 'code-reference-pr-123-new.zip', + '2026-09-01T00:00:00Z' + ), + releaseAsset( + 4, + 'code-reference-pr-999-other.json', + '2026-09-02T00:00:00Z' + ), + releaseAsset( + 5, + 'code-reference-pr-999-other.zip', + '2026-09-02T00:00:00Z' + ), + ], + assetJson: new Map( [ + [ 'code-reference-pr-123-old.json', { sourceSha } ], + ] ), + } ); + + assert.deepEqual( await findLatestPublishedPreview( api, 123 ), { + sourceSha, + } ); +} ); + +test( 'no release means no published preview', async () => { + assert.equal( + await findLatestPublishedPreview( + new FakeGitHub( { release: null } ), + 123 + ), + null + ); +} ); diff --git a/.github/scripts/docs-playground-preview/test/publication.test.mjs b/.github/scripts/docs-playground-preview/test/publication.test.mjs deleted file mode 100644 index 353f8e10f9bc8..0000000000000 --- a/.github/scripts/docs-playground-preview/test/publication.test.mjs +++ /dev/null @@ -1,217 +0,0 @@ -import assert from 'node:assert/strict'; -import { createHash } from 'node:crypto'; -import { test } from 'node:test'; - -import { - PLAYGROUND_ORIGIN, - createLaunchBlueprint, - createReuseHandoff, - metadataAssetName, - playgroundUrl, - releaseAssetUrl, - snapshotAssetName, - validatePublicSnapshot, - validateReusableMetadata, -} from '../lib/publication.mjs'; -import { findReusablePreview } from '../reuse.mjs'; - -const identity = { - repository: 'WordPress/wordpress-develop', - sourceRepository: 'contributor/wordpress-develop', - pullRequestNumber: 123, - sourceSha: 'a'.repeat( 40 ), - workflowRunId: '456', - workflowRunAttempt: 1, - runUrl: 'https://github.com/WordPress/wordpress-develop/actions/runs/456/attempts/1', - maximumBytes: 104857600, -}; - -function published( bytes = Buffer.from( 'snapshot' ) ) { - const snapshotFilename = snapshotAssetName( identity ); - return { - schemaVersion: 1, - sourceRepository: identity.sourceRepository, - pullRequestNumber: identity.pullRequestNumber, - sourceSha: identity.sourceSha, - workflowRunId: identity.workflowRunId, - workflowRunAttempt: identity.workflowRunAttempt, - runUrl: identity.runUrl, - resolvedWordPressBeta: { - channel: 'beta', - version: '7.2-beta1', - downloadUrl: - 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', - }, - phpVersion: '8.4', - dependencyManifestDigest: 'b'.repeat( 64 ), - snapshotFilename, - snapshotBytes: bytes.byteLength, - snapshotSha256: createHash( 'sha256' ).update( bytes ).digest( 'hex' ), - buildStatus: 'success', - validationStatus: 'passed', - validationFailures: [], - generationTimestamp: '2026-08-09T12:34:56.000Z', - }; -} - -function headers( values ) { - return { get: ( name ) => values[ name.toLowerCase() ] || null }; -} - -test( 'immutable asset names bind the PR, SHA, run, and attempt', () => { - const snapshot = snapshotAssetName( identity ); - assert.equal( - snapshot, - `code-reference-pr-123-${ 'a'.repeat( 40 ) }-456-1.zip` - ); - assert.equal( - metadataAssetName( snapshot ), - `code-reference-pr-123-${ 'a'.repeat( 40 ) }-456-1.json` - ); - assert.throws( - () => snapshotAssetName( { ...identity, sourceSha: 'trunk' } ), - /full lowercase commit/ - ); -} ); - -test( 'the launch Blueprint opens the snapshot at the reference index', () => { - const snapshot = snapshotAssetName( identity ); - const publicUrl = releaseAssetUrl( identity.repository, snapshot ); - const runtime = { - blueprintSchema: - 'https://playground.wordpress.net/blueprint-schema.json', - phpVersion: '8.4', - wordpressVersion: '7.2-beta1', - }; - const blueprint = createLaunchBlueprint( publicUrl, runtime ); - assert.equal( blueprint.landingPage, '/reference/' ); - assert.equal( blueprint.login, false ); - assert.equal( blueprint.features.networking, false ); - assert.equal( blueprint.steps[ 0 ].extractToPath, '/' ); - assert.match( - blueprint.steps[ 0 ].zipFile.url, - /^https:\/\/wordpress-playground-cors-proxy\.net\/https:\/\/github\.com\// - ); - assert.match( - decodeURIComponent( playgroundUrl( publicUrl, runtime ) ), - /"landingPage":"\/reference\/"/ - ); -} ); - -test( 'public delivery is checked by status, headers, size, and digest', async () => { - const bytes = Buffer.from( 'snapshot' ); - const expected = published( bytes ); - const result = await validatePublicSnapshot( - releaseAssetUrl( identity.repository, expected.snapshotFilename ), - { - bytes: expected.snapshotBytes, - sha256: expected.snapshotSha256, - maximumBytes: identity.maximumBytes, - }, - async ( url, options ) => { - assert.match( url, /wordpress-playground-cors-proxy/ ); - assert.equal( options.headers.Origin, PLAYGROUND_ORIGIN ); - return { - status: 200, - headers: headers( { - 'x-playground-cors-proxy': 'true', - 'access-control-allow-origin': PLAYGROUND_ORIGIN, - } ), - arrayBuffer: async () => bytes, - }; - } - ); - assert.equal( result.sha256, expected.snapshotSha256 ); - - await assert.rejects( - validatePublicSnapshot( - 'https://github.com/WordPress/wordpress-develop/releases/download/x/y.zip', - { - bytes: bytes.byteLength, - sha256: '0'.repeat( 64 ), - maximumBytes: identity.maximumBytes, - }, - async () => ( { - status: 200, - headers: headers( { - 'x-playground-cors-proxy': 'true', - 'access-control-allow-origin': PLAYGROUND_ORIGIN, - } ), - arrayBuffer: async () => bytes, - } ) - ), - /digest/ - ); -} ); - -test( 'reusable metadata must identify a passed same-PR snapshot', () => { - const metadata = published(); - assert.equal( validateReusableMetadata( metadata, identity ), metadata ); - assert.throws( - () => - validateReusableMetadata( - { ...metadata, validationStatus: 'failed' }, - identity - ), - /valid snapshot/ - ); - assert.throws( - () => - validateReusableMetadata( - { ...metadata, snapshotBytes: 104857601 }, - identity - ), - /identity is invalid/ - ); -} ); - -test( 'same-SHA discovery verifies metadata and the proxied snapshot', async () => { - const bytes = Buffer.from( 'snapshot' ); - const metadata = published( bytes ); - const metadataName = metadataAssetName( metadata.snapshotFilename ); - const assets = [ - { - name: metadataName, - created_at: '2026-08-09T13:00:00Z', - browser_download_url: 'https://github.com/metadata.json', - }, - { - name: metadata.snapshotFilename, - created_at: '2026-08-09T13:00:00Z', - browser_download_url: 'https://github.com/snapshot.zip', - }, - ]; - const fetchImplementation = async ( url ) => { - if ( url.includes( '/releases/tags/' ) ) { - return { ok: true, status: 200, json: async () => ( { id: 9 } ) }; - } - if ( url.includes( '/releases/9/assets' ) ) { - return { ok: true, status: 200, json: async () => assets }; - } - if ( url === 'https://github.com/metadata.json' ) { - return { status: 200, json: async () => metadata }; - } - assert.match( url, /wordpress-playground-cors-proxy/ ); - return { - status: 200, - headers: headers( { - 'x-playground-cors-proxy': 'true', - 'access-control-allow-origin': PLAYGROUND_ORIGIN, - } ), - arrayBuffer: async () => bytes, - }; - }; - const handoff = await findReusablePreview( { - ...identity, - token: 'read-token', - fetchImplementation, - } ); - assert.equal( handoff.handoffType, 'reuse' ); - assert.equal( handoff.workflowRunId, identity.workflowRunId ); - assert.equal( handoff.reusedSnapshot.assetName, metadata.snapshotFilename ); - assert.equal( handoff.reusedSnapshot.metadataAssetName, metadataName ); - assert.deepEqual( - createReuseHandoff( metadata, identity ).reusedSnapshot, - handoff.reusedSnapshot - ); -} ); diff --git a/.github/scripts/docs-playground-preview/test/publish-trunk.test.mjs b/.github/scripts/docs-playground-preview/test/publish-trunk.test.mjs deleted file mode 100644 index 9ebd9c089a33b..0000000000000 --- a/.github/scripts/docs-playground-preview/test/publish-trunk.test.mjs +++ /dev/null @@ -1,545 +0,0 @@ -import assert from 'node:assert/strict'; -import { createHash } from 'node:crypto'; -import { mkdtemp, writeFile } from 'node:fs/promises'; -import os from 'node:os'; -import path from 'node:path'; -import { test } from 'node:test'; - -import { - PLAYGROUND_ORIGIN, - metadataAssetName, - releaseAssetUrl, -} from '../lib/publication.mjs'; -import { - TRUNK_POINTER_REF, - createTrunkBlueprint, - createTrunkPublishedMetadata, - trunkSnapshotAssetName, -} from '../lib/trunk.mjs'; -import { publishTrunk } from '../publish-trunk.mjs'; - -const repository = 'WordPress/wordpress-develop'; -const sha = 'a'.repeat( 40 ); -const snapshotBytes = Buffer.from( 'snapshot' ); - -function run( overrides = {} ) { - return { - id: 456, - run_attempt: 1, - name: 'Code Reference Playground Preview Build', - event: 'push', - status: 'completed', - head_branch: 'trunk', - head_sha: sha, - head_repository: { full_name: repository }, - ...overrides, - }; -} - -function buildMetadata( overrides = {} ) { - const sourceSha = overrides.sourceSha || sha; - const workflowRunId = overrides.workflowRunId || '456'; - const workflowRunAttempt = overrides.workflowRunAttempt || 1; - const bytes = overrides.bytes || snapshotBytes; - return { - schemaVersion: 1, - sourceRepository: repository, - pullRequestNumber: null, - sourceSha, - workflowRunId, - workflowRunAttempt, - runUrl: `https://github.com/${ repository }/actions/runs/${ workflowRunId }`, - resolvedWordPressBeta: { - channel: 'beta', - version: '7.2-beta1', - downloadUrl: - 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', - }, - phpVersion: '8.4', - dependencyManifestDigest: 'b'.repeat( 64 ), - snapshotFilename: trunkSnapshotAssetName( { - sourceSha, - workflowRunId, - workflowRunAttempt, - } ), - snapshotBytes: bytes.byteLength, - snapshotSha256: createHash( 'sha256' ).update( bytes ).digest( 'hex' ), - buildStatus: 'success', - validationStatus: 'passed', - validationFailures: [], - generationTimestamp: '2026-08-09T12:34:56.000Z', - ...overrides, - }; -} - -class FakeApi { - constructor() { - this.currentRun = run(); - this.latestRun = this.currentRun; - this.trunkHeadSha = sha; - this.release = { id: 9 }; - this.assets = []; - this.assetBytes = new Map(); - this.deleted = []; - this.events = []; - this.nextId = 10; - this.deleteErrorId = null; - this.refSha = null; - this.previousRefSha = null; - this.commits = new Map(); - this.pendingBlueprint = null; - this.refReads = 0; - this.refUpdateFailure = null; - this.failRefResolution = false; - this.staleRefAfterMutation = false; - } - - async getRun() { - return this.currentRun; - } - - async latestTrunkPreviewRun() { - return this.latestRun; - } - - async getTrunkHeadSha() { - return this.trunkHeadSha; - } - - async getRelease() { - return this.release; - } - - async createRelease() { - this.release = { id: 9 }; - return this.release; - } - - async listReleaseAssets() { - return this.assets.map( ( asset ) => ( { ...asset } ) ); - } - - async uploadReleaseAsset( releaseId, name, bytes, contentType ) { - assert.equal( releaseId, 9 ); - const asset = { - id: this.nextId++, - name, - content_type: contentType, - browser_download_url: releaseAssetUrl( repository, name ), - }; - this.assets.push( asset ); - this.assetBytes.set( asset.id, Buffer.from( bytes ) ); - this.events.push( `upload:${ name }` ); - return { ...asset }; - } - - async getGitReference( reference ) { - assert.equal( reference, TRUNK_POINTER_REF ); - this.refReads++; - const visibleSha = - this.staleRefAfterMutation && this.refReads > 1 - ? this.previousRefSha - : this.refSha; - this.events.push( `git:read-ref:${ visibleSha }` ); - if ( this.failRefResolution && this.refReads > 1 ) { - throw new Error( 'ref resolution failed' ); - } - return visibleSha ? { object: { sha: visibleSha } } : null; - } - - async createGitBlob( content ) { - this.pendingBlueprint = content; - this.events.push( 'git:blob' ); - return { sha: 'd'.repeat( 40 ) }; - } - - async createGitTree() { - this.events.push( 'git:tree' ); - return { sha: 'e'.repeat( 40 ) }; - } - - async createGitCommit() { - const commitSha = 'f'.repeat( 40 ); - this.commits.set( commitSha, Buffer.from( this.pendingBlueprint ) ); - this.events.push( 'git:commit' ); - return { sha: commitSha }; - } - - async createGitReference( reference, candidateSha ) { - assert.equal( reference, TRUNK_POINTER_REF ); - this.refSha = candidateSha; - this.events.push( `git:create-ref:${ candidateSha }` ); - } - - async updateGitReference( reference, candidateSha ) { - assert.equal( reference, TRUNK_POINTER_REF ); - if ( this.refUpdateFailure === 'before' ) { - throw new Error( 'ref update failed' ); - } - this.refSha = candidateSha; - this.events.push( `git:update-ref:${ candidateSha }` ); - if ( this.refUpdateFailure === 'after' ) { - throw new Error( 'ref update response failed' ); - } - } - - async deleteReleaseAsset( id ) { - if ( id === this.deleteErrorId ) { - throw new Error( 'cleanup failed' ); - } - this.deleted.push( id ); - this.events.push( `delete:${ id }` ); - this.assets = this.assets.filter( ( asset ) => asset.id !== id ); - this.assetBytes.delete( id ); - } -} - -function publicFetch( api, fetchOptions = {} ) { - return async ( url ) => { - const rawStart = url.indexOf( 'https://raw.githubusercontent.com/' ); - if ( rawStart !== -1 ) { - const rawUrl = new URL( url.slice( rawStart ) ); - const parts = rawUrl.pathname.split( '/' ).filter( Boolean ); - const reference = parts[ 2 ]; - let commitSha = reference; - if ( reference === 'docs-preview-code-reference' ) { - commitSha = fetchOptions.staleStablePointer - ? api.previousRefSha - : api.refSha; - } - api.events.push( - `fetch:${ - reference === 'docs-preview-code-reference' - ? 'stable-pointer' - : 'candidate-pointer' - }` - ); - const bytes = api.commits.get( commitSha ); - return { - status: bytes ? 200 : 404, - headers: { - get: ( header ) => - ( { - 'x-playground-cors-proxy': 'true', - 'access-control-allow-origin': PLAYGROUND_ORIGIN, - } )[ header.toLowerCase() ] || null, - }, - arrayBuffer: async () => bytes, - }; - } - const throughProxy = url.includes( - 'wordpress-playground-cors-proxy.net' - ); - const name = new URL( - throughProxy - ? url.slice( url.indexOf( 'https://github.com/' ) ) - : url - ).pathname - .split( '/' ) - .at( -1 ); - const asset = api.assets.find( - ( candidate ) => candidate.name === name - ); - api.events.push( `fetch:${ name }` ); - if ( ! asset ) { - return { status: 404, headers: { get: () => null } }; - } - let bytes = api.assetBytes.get( asset.id ); - if ( fetchOptions.corruptSnapshot && asset.name.endsWith( '.zip' ) ) { - bytes = Buffer.from( 'corrupt' ); - } - return { - status: 200, - headers: { - get: ( header ) => - ( { - 'x-playground-cors-proxy': 'true', - 'access-control-allow-origin': PLAYGROUND_ORIGIN, - } )[ header.toLowerCase() ] || null, - }, - arrayBuffer: async () => bytes, - json: async () => JSON.parse( bytes.toString( 'utf8' ) ), - }; - }; -} - -async function handoffDirectory( metadata, bytes = snapshotBytes ) { - const directory = await mkdtemp( - path.join( os.tmpdir(), 'docs-preview-trunk-publish-' ) - ); - await writeFile( - path.join( directory, 'build.json' ), - `${ JSON.stringify( metadata ) }\n` - ); - if ( metadata.snapshotFilename ) { - await writeFile( - path.join( directory, metadata.snapshotFilename ), - bytes - ); - } - return directory; -} - -function options( api, directory, extra = {} ) { - return { - repository, - stagingVariable: '', - triggerRunId: 456, - triggerRunAttempt: 1, - handoffDirectory: directory, - artifactAvailable: true, - api, - fetchImplementation: publicFetch( api ), - now: () => '2026-08-09T13:00:00.000Z', - warning: () => {}, - ...extra, - }; -} - -function addAsset( api, id, name, bytes ) { - api.assets.push( { - id, - name, - browser_download_url: releaseAssetUrl( repository, name ), - } ); - api.assetBytes.set( id, Buffer.from( bytes ) ); -} - -function installPrevious( api ) { - const oldSha = 'c'.repeat( 40 ); - const oldBytes = Buffer.from( 'old snapshot' ); - const build = buildMetadata( { - sourceSha: oldSha, - workflowRunId: '300', - bytes: oldBytes, - snapshotBytes: oldBytes.byteLength, - snapshotSha256: createHash( 'sha256' ) - .update( oldBytes ) - .digest( 'hex' ), - } ); - const published = createTrunkPublishedMetadata( - build, - repository, - '2026-08-08T13:00:00.000Z' - ); - addAsset( api, 1, build.snapshotFilename, oldBytes ); - addAsset( - api, - 2, - metadataAssetName( build.snapshotFilename ), - `${ JSON.stringify( published ) }\n` - ); - addAsset( api, 4, 'code-reference-pr-123-preview.zip', 'pr snapshot' ); - api.previousRefSha = '9'.repeat( 40 ); - api.refSha = api.previousRefSha; - api.commits.set( - api.refSha, - Buffer.from( - `${ JSON.stringify( createTrunkBlueprint( published ) ) }\n` - ) - ); - return { build, published }; -} - -test( 'the stable pointer moves only after every candidate is public', async () => { - const api = new FakeApi(); - installPrevious( api ); - const current = buildMetadata(); - const directory = await handoffDirectory( current ); - const result = await publishTrunk( options( api, directory ) ); - assert.equal( result.status, 'ready' ); - assert.equal( api.refSha, 'f'.repeat( 40 ) ); - const blueprint = JSON.parse( - api.commits.get( api.refSha ).toString( 'utf8' ) - ); - assert.match( blueprint.meta.description, new RegExp( sha ) ); - assert.deepEqual( api.deleted.sort(), [ 1, 2 ] ); - assert.ok( api.assets.some( ( asset ) => asset.id === 4 ) ); - const candidateFetch = api.events.lastIndexOf( 'fetch:candidate-pointer' ); - const pointerMove = api.events.lastIndexOf( - `git:update-ref:${ 'f'.repeat( 40 ) }` - ); - const pointerRead = api.events.lastIndexOf( - `git:read-ref:${ 'f'.repeat( 40 ) }` - ); - const stableFetch = api.events.lastIndexOf( 'fetch:stable-pointer' ); - const firstDelete = api.events.findIndex( ( event ) => - event.startsWith( 'delete:' ) - ); - assert.ok( - candidateFetch > -1 && - candidateFetch < pointerMove && - pointerMove < pointerRead && - pointerRead < stableFetch && - stableFetch < firstDelete - ); -} ); - -test( 'stale stable delivery retains both snapshot generations', async () => { - const api = new FakeApi(); - installPrevious( api ); - const directory = await handoffDirectory( buildMetadata() ); - await assert.rejects( - publishTrunk( - options( api, directory, { - fetchImplementation: publicFetch( api, { - staleStablePointer: true, - } ), - } ) - ), - /does not identify the candidate/ - ); - assert.equal( api.refSha, 'f'.repeat( 40 ) ); - assert.ok( api.assets.some( ( asset ) => asset.id === 1 ) ); - assert.ok( api.assets.some( ( asset ) => asset.id === 10 ) ); - assert.deepEqual( api.deleted, [] ); -} ); - -test( 'public candidate failure leaves the previous pointer and snapshot intact', async () => { - const api = new FakeApi(); - installPrevious( api ); - const directory = await handoffDirectory( buildMetadata() ); - await assert.rejects( - publishTrunk( - options( api, directory, { - fetchImplementation: publicFetch( api, { - corruptSnapshot: true, - } ), - } ) - ), - /Public snapshot/ - ); - assert.equal( api.refSha, api.previousRefSha ); - assert.ok( api.assets.some( ( asset ) => asset.id === 1 ) ); - assert.deepEqual( api.deleted.sort(), [ 10, 11 ] ); -} ); - -test( 'a failed ref update retains the previous and candidate assets', async () => { - const api = new FakeApi(); - installPrevious( api ); - api.refUpdateFailure = 'before'; - const directory = await handoffDirectory( buildMetadata() ); - await assert.rejects( - publishTrunk( options( api, directory ) ), - /ref update failed/ - ); - assert.equal( api.refSha, api.previousRefSha ); - assert.ok( api.assets.some( ( asset ) => asset.id === 1 ) ); - assert.ok( api.assets.some( ( asset ) => asset.id === 10 ) ); - assert.deepEqual( api.deleted, [] ); -} ); - -test( 'an ambiguous ref response resolves the candidate and completes', async () => { - const api = new FakeApi(); - installPrevious( api ); - api.refUpdateFailure = 'after'; - const directory = await handoffDirectory( buildMetadata() ); - assert.equal( - ( await publishTrunk( options( api, directory ) ) ).status, - 'ready' - ); - assert.equal( api.refSha, 'f'.repeat( 40 ) ); - assert.deepEqual( api.deleted.sort(), [ 1, 2 ] ); -} ); - -test( 'unresolved ref state retains both valid generations', async () => { - const api = new FakeApi(); - installPrevious( api ); - api.refUpdateFailure = 'after'; - api.failRefResolution = true; - const directory = await handoffDirectory( buildMetadata() ); - await assert.rejects( - publishTrunk( options( api, directory ) ), - /ref resolution failed/ - ); - assert.equal( api.refSha, 'f'.repeat( 40 ) ); - assert.ok( api.assets.some( ( asset ) => asset.id === 1 ) ); - assert.ok( api.assets.some( ( asset ) => asset.id === 10 ) ); - assert.deepEqual( api.deleted, [] ); -} ); - -test( 'a stale read after ref creation retains the candidate assets', async () => { - const api = new FakeApi(); - api.staleRefAfterMutation = true; - const directory = await handoffDirectory( buildMetadata() ); - await assert.rejects( - publishTrunk( options( api, directory ) ), - /pointer mutation has unknown state/ - ); - assert.equal( api.refSha, 'f'.repeat( 40 ) ); - assert.equal( api.assets.length, 2 ); - assert.deepEqual( api.deleted, [] ); -} ); - -test( 'an update error and stale read retain both asset generations', async () => { - const api = new FakeApi(); - installPrevious( api ); - api.refUpdateFailure = 'after'; - api.staleRefAfterMutation = true; - const directory = await handoffDirectory( buildMetadata() ); - await assert.rejects( - publishTrunk( options( api, directory ) ), - /ref update response failed/ - ); - assert.equal( api.refSha, 'f'.repeat( 40 ) ); - assert.ok( api.assets.some( ( asset ) => asset.id === 1 ) ); - assert.ok( api.assets.some( ( asset ) => asset.id === 10 ) ); - assert.deepEqual( api.deleted, [] ); -} ); - -test( 'cleanup failure keeps the new validated pointer and both snapshots', async () => { - const api = new FakeApi(); - installPrevious( api ); - api.deleteErrorId = 1; - const directory = await handoffDirectory( buildMetadata() ); - await assert.rejects( - publishTrunk( options( api, directory ) ), - /cleanup failed/ - ); - assert.equal( api.refSha, 'f'.repeat( 40 ) ); - assert.ok( api.assets.some( ( asset ) => asset.id === 1 ) ); - assert.ok( api.assets.some( ( asset ) => asset.id === 10 ) ); -} ); - -test( 'invalid, missing, and superseded handoffs never move the pointer', async () => { - const invalidApi = new FakeApi(); - installPrevious( invalidApi ); - const invalid = buildMetadata( { validationStatus: 'failed' } ); - assert.equal( - ( - await publishTrunk( - options( invalidApi, await handoffDirectory( invalid ) ) - ) - ).status, - 'invalid' - ); - assert.equal( invalidApi.assets.length, 3 ); - - const missingApi = new FakeApi(); - installPrevious( missingApi ); - await assert.rejects( - publishTrunk( - options( missingApi, await handoffDirectory( buildMetadata() ), { - artifactAvailable: false, - } ) - ), - /handoff is unavailable/ - ); - assert.equal( missingApi.assets.length, 3 ); - - const supersededApi = new FakeApi(); - installPrevious( supersededApi ); - supersededApi.latestRun = run( { id: 789 } ); - assert.equal( - ( - await publishTrunk( - options( - supersededApi, - await handoffDirectory( buildMetadata() ) - ) - ) - ).status, - 'superseded' - ); - assert.equal( supersededApi.assets.length, 3 ); -} ); diff --git a/.github/scripts/docs-playground-preview/test/publish.test.mjs b/.github/scripts/docs-playground-preview/test/publish.test.mjs index 123ed316d3fa2..017ab9fea652c 100644 --- a/.github/scripts/docs-playground-preview/test/publish.test.mjs +++ b/.github/scripts/docs-playground-preview/test/publish.test.mjs @@ -1,561 +1,503 @@ import assert from 'node:assert/strict'; -import { createHash } from 'node:crypto'; import { mkdtemp, writeFile } from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import { test } from 'node:test'; import { - createPublishedMetadata, - renderPreviewComment, -} from '../lib/publisher.mjs'; -import { - PLAYGROUND_ORIGIN, - createReuseHandoff, - metadataAssetName, - releaseAssetUrl, - snapshotAssetName, -} from '../lib/publication.mjs'; -import { - enforceValidationResult, - formatFailure, - publishPullRequest, -} from '../publish.mjs'; - -const repository = 'WordPress/wordpress-develop'; -const sourceRepository = 'contributor/wordpress-develop'; -const sha = 'a'.repeat( 40 ); -const snapshotBytes = Buffer.from( 'snapshot' ); - -function run() { - return { - id: 456, - run_attempt: 1, - name: 'Code Reference Playground Preview Build', - event: 'pull_request', - status: 'completed', - head_sha: sha, - head_branch: 'feature', - head_repository: { - full_name: sourceRepository, - owner: { login: 'contributor' }, - }, - }; -} - -function pullRequest() { - return { - number: 123, - state: 'open', - base: { ref: 'trunk' }, - head: { - sha, - ref: 'feature', - repo: { full_name: sourceRepository }, - }, - labels: [ { name: 'docs-preview' } ], - }; -} + FakeGitHub, + buildRun, + pullRequest, + releaseAsset, + repository, + sourceRepository, + sourceSha, +} from './fake-github.mjs'; +import { HANDOFF_SNAPSHOT, snapshotAssetName } from '../lib/preview.mjs'; +import { enforceValidation, publishPreview } from '../publish.mjs'; + +const PLAYGROUND_ORIGIN = 'https://playground.wordpress.net'; +const pullRequestNumber = 123; +const runId = 456; +const prSnapshot = snapshotAssetName( { + pullRequestNumber, + sourceSha, + workflowRunId: runId, + workflowRunAttempt: 1, +} ); +const trunkSnapshot = snapshotAssetName( { + pullRequestNumber: null, + sourceSha, + workflowRunId: runId, + workflowRunAttempt: 1, +} ); -function buildMetadata( overrides = {} ) { - const identity = { - pullRequestNumber: 123, - sourceSha: sha, - workflowRunId: 456, - workflowRunAttempt: 1, - }; +/** + * What the build writes and the publisher reads. The build also records its own + * idea of the source and of the snapshot; the publisher ignores both, so only + * the tests that prove it name those fields. + * + * @param {Record} [overrides] + */ +function handoff( overrides = {} ) { return { - schemaVersion: 1, - sourceRepository, - pullRequestNumber: 123, - sourceSha: sha, - workflowRunId: '456', - workflowRunAttempt: 1, - runUrl: `${ - releaseAssetUrl( repository, 'x' ).split( '/releases/' )[ 0 ] - }/actions/runs/456`, - resolvedWordPressBeta: { - channel: 'beta', - version: '7.2-beta1', - downloadUrl: - 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', - }, + resolvedWordPressBeta: { version: '7.2-beta1' }, phpVersion: '8.4', dependencyManifestDigest: 'b'.repeat( 64 ), - snapshotFilename: snapshotAssetName( identity ), - snapshotBytes: snapshotBytes.byteLength, - snapshotSha256: createHash( 'sha256' ) - .update( snapshotBytes ) - .digest( 'hex' ), + generationTimestamp: '2026-09-04T11:00:00.000Z', buildStatus: 'success', validationStatus: 'passed', - validationFailures: [], - generationTimestamp: '2026-08-09T12:34:56.000Z', ...overrides, }; } -function releaseAsset( id, name, createdAt = '2026-08-08T13:00:00.000Z' ) { - return { - id, - name, - created_at: createdAt, - browser_download_url: releaseAssetUrl( repository, name ), - }; -} - -class FakeApi { - constructor() { - this.currentRun = run(); - this.currentPullRequest = pullRequest(); - this.latestRun = this.currentRun; - this.release = { id: 9 }; - this.assets = []; - this.metadata = new Map(); - this.assetBytes = new Map(); - this.uploads = []; - this.deleted = []; - this.comments = []; - this.labelRemovals = 0; - this.runReads = 0; - this.deleteError = null; - this.uploadError = null; - } - - async getRun() { - this.runReads++; - return this.currentRun; - } - - async findPullRequestForRun() { - return this.currentPullRequest; - } - - async getPullRequest() { - return this.currentPullRequest; - } - - async latestPreviewRun() { - return this.latestRun; - } - - async isSkippedPreviewBuild() { - return false; - } - - async getRelease() { - return this.release; - } - - async createRelease() { - this.release = { id: 9 }; - return this.release; - } - - async listReleaseAssets() { - return [ ...this.assets ]; - } - - async uploadReleaseAsset( releaseId, name, bytes, contentType ) { - assert.equal( releaseId, 9 ); - if ( this.uploadError ) { - throw this.uploadError; - } - const asset = { - id: this.assets.length + 10, - name, - created_at: '2026-08-09T13:00:00.000Z', - browser_download_url: releaseAssetUrl( repository, name ), - }; - this.assets.push( asset ); - this.uploads.push( { name, contentType } ); - if ( contentType === 'application/json' ) { - this.metadata.set( name, JSON.parse( bytes.toString() ) ); - } else { - this.assetBytes.set( name, Buffer.from( bytes ) ); - } - return asset; - } - - async deleteReleaseAsset( id ) { - if ( this.deleteError ) { - throw this.deleteError; - } - this.deleted.push( id ); - } - - async findPreviewComment() { - return this.comments[ 0 ] || null; - } - - async createComment( number, body ) { - this.comments = [ { id: 1, number, body } ]; - } - - async updateComment( id, body ) { - this.comments = [ { id, body } ]; - } - - async removeLabel() { - this.labelRemovals++; - } -} - -function publicFetch( api, corrupt = new Set() ) { - return async ( url ) => { - if ( url.includes( 'wordpress-playground-cors-proxy.net' ) ) { - const name = url.split( '/' ).at( -1 ); - const bytes = corrupt.has( name ) - ? Buffer.from( 'corrupt!' ) - : api.assetBytes.get( name ); - return { - status: 200, - headers: { - get: ( header ) => - ( { - 'x-playground-cors-proxy': 'true', - 'access-control-allow-origin': PLAYGROUND_ORIGIN, - } )[ header.toLowerCase() ] || null, - }, - arrayBuffer: async () => bytes, - }; - } - const name = url.split( '/' ).at( -1 ); - return { - status: api.metadata.has( name ) ? 200 : 404, - json: async () => api.metadata.get( name ), - }; - }; -} - -async function handoffDirectory( metadata, includeSnapshot = true ) { +/** + * @param {Record} metadata + * @param {boolean} [withSnapshot] + * @param {number} [snapshotSize] + */ +async function handoffDirectory( metadata, withSnapshot, snapshotSize = 8 ) { const directory = await mkdtemp( path.join( os.tmpdir(), 'docs-preview-publish-' ) ); await writeFile( path.join( directory, 'build.json' ), - `${ JSON.stringify( metadata ) }\n` + JSON.stringify( metadata ) ); - if ( includeSnapshot ) { + if ( withSnapshot ) { await writeFile( - path.join( directory, metadata.snapshotFilename ), - snapshotBytes + path.join( directory, HANDOFF_SNAPSHOT ), + Buffer.alloc( snapshotSize, 1 ) ); } return directory; } -function options( api, directory, extra = {} ) { - return { +/** + * @param {Record} api + * @param {Record} [options] + */ +function publish( api, options = {} ) { + return publishPreview( { repository, - stagingVariable: '', - triggerRunId: 456, + triggerRunId: runId, triggerRunAttempt: 1, - handoffDirectory: directory, artifactAvailable: true, api, - fetchImplementation: publicFetch( api ), - now: () => '2026-08-09T13:00:00.000Z', - warning: () => {}, - ...extra, - }; + ...options, + } ); } -function installPublishedPreview( - api, - build, - content = snapshotBytes, - publishedAt = '2026-08-08T13:00:00.000Z' -) { - const published = createPublishedMetadata( build, repository, publishedAt ); - const metadataName = metadataAssetName( build.snapshotFilename ); - api.assets.push( - releaseAsset( 1, build.snapshotFilename, publishedAt ), - releaseAsset( 2, metadataName, publishedAt ) - ); - api.metadata.set( metadataName, published ); - api.assetBytes.set( build.snapshotFilename, content ); - return published; -} +test( 'a pull request preview publishes both assets and announces the link', async () => { + const api = new FakeGitHub( { + assets: [ + releaseAsset( + 1, + `code-reference-pr-${ pullRequestNumber }-old.zip` + ), + releaseAsset( 2, 'code-reference-pr-999-other.zip' ), + ], + } ); + const result = await publish( api, { + handoffDirectory: await handoffDirectory( handoff(), true ), + } ); -test( 'a candidate is public before the comment moves and old assets delete', async () => { - const api = new FakeApi(); - const metadata = buildMetadata(); - const directory = await handoffDirectory( metadata ); - const result = await publishPullRequest( options( api, directory ) ); assert.equal( result.status, 'ready' ); assert.deepEqual( - api.uploads.map( ( upload ) => upload.contentType ), - [ 'application/zip', 'application/json' ] + api.uploads.map( ( upload ) => upload.name ), + [ prSnapshot, prSnapshot.replace( /\.zip$/, '.json' ) ] + ); + assert.match( api.comments[ 0 ].body, /\*\*Status:\*\* Ready/ ); + assert.ok( + api.comments[ 0 ].body.includes( + `${ PLAYGROUND_ORIGIN }/?blueprint-url=data:application/json,` + ) ); - assert.match( api.comments[ 0 ].body, /Status:\*\* Ready/ ); - assert.match( api.comments[ 0 ].body, /blueprint-url=/ ); assert.equal( api.labelRemovals, 1 ); - assert.ok( api.runReads >= 5 ); + // The superseded asset for this pull request goes; another pull request's + // asset stays. + assert.deepEqual( api.deletedAssets, [ 1 ] ); } ); -test( 'an advisory validation failure updates the comment but publishes nothing', async () => { - const api = new FakeApi(); - const metadata = buildMetadata( { validationStatus: 'failed' } ); - const directory = await handoffDirectory( metadata, false ); - const result = await publishPullRequest( options( api, directory ) ); - assert.equal( result.status, 'invalid' ); - assert.equal( api.uploads.length, 0 ); - assert.match( api.comments[ 0 ].body, /Latest attempt failed/ ); - assert.equal( api.labelRemovals, 1 ); -} ); +test( 'published metadata names the commit of the workflow run, not the handoff', async () => { + const api = new FakeGitHub(); + await publish( api, { + handoffDirectory: await handoffDirectory( + handoff( { + sourceSha: 'f'.repeat( 40 ), + sourceRepository: 'attacker/wordpress-develop', + pullRequestNumber: 999, + } ), + true + ), + } ); -test( 'the trusted publisher enforces invalid fork handoffs', () => { - const invalid = { status: 'invalid' }; - assert.throws( - () => enforceValidationResult( invalid, 'true' ), - /DOCS_PREVIEW_ENFORCE/ + const published = api.assetJson.get( + prSnapshot.replace( /\.zip$/, '.json' ) ); - assert.equal( enforceValidationResult( invalid, 'false' ), invalid ); - assert.equal( enforceValidationResult( invalid, 'TRUE' ), invalid ); - assert.equal( - enforceValidationResult( { status: 'ready' }, 'true' ).status, - 'ready' + assert.equal( published.sourceSha, sourceSha ); + assert.equal( published.sourceRepository, sourceRepository ); + assert.equal( published.pullRequestNumber, pullRequestNumber ); + assert.equal( published.snapshotBytes, 8 ); + assert.ok( + ! Number.isNaN( Date.parse( published.publication.publishedAt ) ) ); } ); -test( 'publisher failures retain every nested cause in the Actions log', () => { - const failure = new AggregateError( - [ - new Error( 'candidate upload failed' ), - new AggregateError( - [ - new Error( 'comment failed' ), - new Error( 'label removal failed' ), - ], - 'failure reporting failed' +test( 'a snapshot above the size limit is not published', async () => { + const api = new FakeGitHub(); + await assert.rejects( + publish( api, { + handoffDirectory: await handoffDirectory( + handoff(), + true, + 104857601 ), + } ), + /the limit is 104857600/ + ); + assert.deepEqual( api.uploads, [] ); + assert.match( api.comments[ 0 ].body, /Latest attempt failed/ ); +} ); + +test( 'a failed build reports the failure and keeps the last working link', async () => { + const previous = 'code-reference-pr-123-old.json'; + const api = new FakeGitHub( { + assets: [ + releaseAsset( 1, previous ), + releaseAsset( 2, 'code-reference-pr-123-old.zip' ), ], - 'publication failed' + assetJson: new Map( [ + [ + previous, + { + sourceRepository, + sourceSha: 'e'.repeat( 40 ), + publication: { + playgroundUrl: `${ PLAYGROUND_ORIGIN }/?blueprint-url=previous`, + }, + }, + ], + ] ), + } ); + await assert.rejects( + publish( api, { + handoffDirectory: await handoffDirectory( + handoff( { + buildStatus: 'failed', + buildError: 'The parser crashed.', + } ) + ), + } ), + /The parser crashed\./ ); - const formatted = formatFailure( failure ); - assert.match( formatted, /candidate upload failed/ ); - assert.match( formatted, /comment failed/ ); - assert.match( formatted, /label removal failed/ ); + assert.match( + api.comments[ 0 ].body, + /\*\*Status:\*\* Latest attempt failed/ + ); + assert.match( api.comments[ 0 ].body, /Latest successful docs preview/ ); + assert.equal( api.labelRemovals, 1 ); } ); -test( 'a missing handoff always fails after its terminal comment', async () => { - const api = new FakeApi(); - const directory = await mkdtemp( - path.join( os.tmpdir(), 'docs-preview-missing-' ) +test( 'a failed validation reports invalid without publishing', async () => { + const api = new FakeGitHub(); + const result = await publish( api, { + handoffDirectory: await handoffDirectory( + handoff( { validationStatus: 'failed' } ), + true + ), + } ); + + assert.equal( result.status, 'invalid' ); + assert.deepEqual( api.uploads, [] ); + assert.match( api.comments[ 0 ].body, /Latest attempt failed/ ); +} ); + +test( 'enforcement turns an invalid candidate into a failure', () => { + assert.deepEqual( enforceValidation( { status: 'invalid' }, 'false' ), { + status: 'invalid', + } ); + assert.throws( + () => enforceValidation( { status: 'invalid' }, 'true' ), + /DOCS_PREVIEW_ENFORCE/ ); +} ); + +test( 'a missing handoff artifact reports the failure', async () => { + const api = new FakeGitHub(); await assert.rejects( - publishPullRequest( - options( api, directory, { artifactAvailable: false } ) - ), - /artifact is unavailable/ + publish( api, { + artifactAvailable: false, + handoffDirectory: await handoffDirectory( handoff(), true ), + } ), + /handoff artifact is unavailable/ ); - assert.match( api.comments[ 0 ].body, /Latest attempt failed/ ); - assert.equal( api.labelRemovals, 1 ); + assert.deepEqual( api.uploads, [] ); } ); -test( 'a superseded run makes no mutation', async () => { - const api = new FakeApi(); - api.latestRun = { ...api.currentRun, id: 789 }; - const directory = await handoffDirectory( buildMetadata() ); - const result = await publishPullRequest( options( api, directory ) ); +test( 'a superseded build publishes nothing', async () => { + const api = new FakeGitHub( { latestRun: buildRun( { id: 999 } ) } ); + const result = await publish( api, { + handoffDirectory: await handoffDirectory( handoff(), true ), + } ); + assert.equal( result.status, 'superseded' ); - assert.equal( api.uploads.length, 0 ); - assert.equal( api.comments.length, 0 ); - assert.equal( api.labelRemovals, 0 ); + assert.deepEqual( api.uploads, [] ); + assert.deepEqual( api.comments, [] ); } ); -test( 'a skipped trigger run makes no mutation', async () => { - const api = new FakeApi(); - api.isSkippedPreviewBuild = async () => true; - const directory = await handoffDirectory( buildMetadata() ); - const result = await publishPullRequest( options( api, directory ) ); - assert.equal( result.status, 'ignored' ); - assert.equal( api.uploads.length, 0 ); - assert.equal( api.comments.length, 0 ); - assert.equal( api.labelRemovals, 0 ); +test( 'a newer attempt of the same run publishes nothing', async () => { + const api = new FakeGitHub( { run: buildRun( { run_attempt: 2 } ) } ); + const result = await publish( api, { + handoffDirectory: await handoffDirectory( handoff(), true ), + } ); + + assert.equal( result.status, 'superseded' ); } ); -test( 'public delivery failure keeps the previous healthy preview', async () => { - const api = new FakeApi(); - const oldBytes = Buffer.from( 'old snapshot' ); - const oldBuild = buildMetadata( { - sourceSha: 'c'.repeat( 40 ), - workflowRunId: '300', - snapshotFilename: snapshotAssetName( { - pullRequestNumber: 123, - sourceSha: 'c'.repeat( 40 ), - workflowRunId: 300, - workflowRunAttempt: 1, - } ), - snapshotBytes: oldBytes.byteLength, - snapshotSha256: createHash( 'sha256' ) - .update( oldBytes ) - .digest( 'hex' ), +test( 'a removed docs-preview label stops the first attempt', async () => { + const api = new FakeGitHub( { + pullRequest: pullRequest( { labels: [] } ), + } ); + const result = await publish( api, { + handoffDirectory: await handoffDirectory( handoff(), true ), + } ); + + assert.equal( result.status, 'superseded' ); + assert.deepEqual( api.uploads, [] ); +} ); + +test( 'a closed pull request publishes nothing', async () => { + const api = new FakeGitHub( { pullRequest: null } ); + const result = await publish( api, { + handoffDirectory: await handoffDirectory( handoff(), true ), } ); - installPublishedPreview( api, oldBuild, oldBytes ); - const current = buildMetadata(); - const directory = await handoffDirectory( current ); + assert.equal( result.status, 'superseded' ); +} ); + +test( 'a reuse handoff announces the published assets without uploading', async () => { + const reused = `code-reference-pr-${ pullRequestNumber }-${ sourceSha }-1-1.zip`; + const reusedMetadata = reused.replace( /\.zip$/, '.json' ); + const api = new FakeGitHub( { + assets: [ + releaseAsset( 1, reused ), + releaseAsset( 2, reusedMetadata ), + ], + assetJson: new Map( [ + [ + reusedMetadata, + { + sourceRepository, + sourceSha, + publication: { + publishedAt: '2026-09-03T10:00:00.000Z', + playgroundUrl: `${ PLAYGROUND_ORIGIN }/?blueprint-url=reused`, + }, + }, + ], + ] ), + } ); + const result = await publish( api, { + handoffDirectory: await handoffDirectory( { handoffType: 'reuse' } ), + } ); + + assert.equal( result.status, 'reused' ); + assert.deepEqual( api.uploads, [] ); + assert.deepEqual( api.deletedAssets, [] ); + assert.match( api.comments[ 0 ].body, /\*\*Status:\*\* Ready/ ); + assert.equal( api.labelRemovals, 1 ); +} ); + +test( 'a reuse handoff for a commit with no published preview is refused', async () => { + const api = new FakeGitHub( { + assets: [ + releaseAsset( 1, `code-reference-pr-999-${ sourceSha }-1-1.zip` ), + releaseAsset( 2, `code-reference-pr-999-${ sourceSha }-1-1.json` ), + ], + } ); await assert.rejects( - publishPullRequest( - options( api, directory, { - fetchImplementation: publicFetch( - api, - new Set( [ current.snapshotFilename ] ) - ), - } ) - ), - /digest/ - ); - assert.deepEqual( - api.deleted.sort( ( left, right ) => left - right ), - [ 12, 13 ] + publish( api, { + handoffDirectory: await handoffDirectory( { + handoffType: 'reuse', + } ), + } ), + /no longer published/ ); - assert.match( api.comments[ 0 ].body, /Latest successful docs preview/ ); - assert.match( api.comments[ 0 ].body, new RegExp( 'c'.repeat( 40 ) ) ); + assert.deepEqual( api.deletedAssets, [] ); } ); -test( 'a proxy outage cannot remove the previous comment link', async () => { - const api = new FakeApi(); - const oldBytes = Buffer.from( 'old snapshot' ); - const oldBuild = buildMetadata( { - sourceSha: 'c'.repeat( 40 ), - workflowRunId: '300', - runUrl: `https://github.com/${ repository }/actions/runs/300`, - snapshotFilename: snapshotAssetName( { - pullRequestNumber: 123, - sourceSha: 'c'.repeat( 40 ), - workflowRunId: 300, - workflowRunAttempt: 1, - } ), - snapshotBytes: oldBytes.byteLength, - snapshotSha256: createHash( 'sha256' ) - .update( oldBytes ) - .digest( 'hex' ), +test( 'trunk publishes the snapshot and points the stable Blueprint at it', async () => { + const api = new FakeGitHub( { + pullRequest: null, + gitReference: { object: { sha: '9'.repeat( 40 ) } }, + } ); + const result = await publish( api, { + trunk: true, + handoffDirectory: await handoffDirectory( handoff(), true ), } ); - const oldPreview = installPublishedPreview( api, oldBuild, oldBytes ); - api.comments = [ - { - id: 1, - body: renderPreviewComment( { - status: 'ready', - preview: oldPreview, - runUrl: oldBuild.runUrl, - } ), - }, - ]; - const current = buildMetadata(); - const directory = await handoffDirectory( current ); - await assert.rejects( - publishPullRequest( - options( api, directory, { - fetchImplementation: publicFetch( - api, - new Set( [ - oldBuild.snapshotFilename, - current.snapshotFilename, - ] ) - ), - } ) - ), - /digest/ + assert.equal( result.status, 'ready' ); + assert.deepEqual( + api.uploads.map( ( upload ) => upload.name ), + [ trunkSnapshot, trunkSnapshot.replace( /\.zip$/, '.json' ) ] ); - assert.match( api.comments[ 0 ].body, /Latest attempt failed/ ); - assert.match( api.comments[ 0 ].body, /Latest successful docs preview/ ); - assert.match( api.comments[ 0 ].body, new RegExp( 'c'.repeat( 40 ) ) ); + assert.equal( api.trees[ 0 ].path, 'code-reference-trunk.json' ); + const blueprint = JSON.parse( api.trees[ 0 ].content ); assert.ok( - api.comments[ 0 ].body.includes( oldPreview.publication.playgroundUrl ) + blueprint.steps[ 0 ].zipFile.url.endsWith( `/${ trunkSnapshot }` ) ); + assert.equal( api.commits[ 0 ].parentSha, '9'.repeat( 40 ) ); + assert.equal( api.gitReference.existed, true ); + // Trunk publishes no comment and touches no label. + assert.deepEqual( api.comments, [] ); + assert.equal( api.labelRemovals, 0 ); } ); -test( 'a retry cannot delete a same-name preview it did not upload', async () => { - const api = new FakeApi(); - const current = buildMetadata(); - installPublishedPreview( api, current ); - api.uploadError = new Error( 'already_exists' ); - const directory = await handoffDirectory( current ); - await assert.rejects( - publishPullRequest( options( api, directory ) ), - /already_exists/ - ); - assert.deepEqual( api.deleted, [] ); - assert.match( api.comments[ 0 ].body, /Latest successful docs preview/ ); +test( 'trunk creates the pointer branch when it does not exist', async () => { + const api = new FakeGitHub( { pullRequest: null, gitReference: null } ); + await publish( api, { + trunk: true, + handoffDirectory: await handoffDirectory( handoff(), true ), + } ); + + assert.equal( api.commits[ 0 ].parentSha, null ); + assert.equal( api.gitReference.existed, false ); } ); -test( 'a healthy same-SHA preview is reused without uploading it again', async () => { - const api = new FakeApi(); - const oldBuild = buildMetadata( { - workflowRunId: '300', - workflowRunAttempt: 1, - runUrl: `https://github.com/${ repository }/actions/runs/300`, - snapshotFilename: snapshotAssetName( { - pullRequestNumber: 123, - sourceSha: sha, - workflowRunId: 300, - workflowRunAttempt: 1, - } ), +test( 'trunk keeps the two newest generations so warm Blueprint copies resolve', async () => { + const older = `code-reference-trunk-${ 'b'.repeat( 40 ) }-100-1`; + const previous = `code-reference-trunk-${ 'c'.repeat( 40 ) }-200-1`; + const api = new FakeGitHub( { + pullRequest: null, + assets: [ + releaseAsset( 1, `${ older }.zip` ), + releaseAsset( 2, `${ older }.json` ), + releaseAsset( 3, `${ previous }.zip` ), + releaseAsset( 4, `${ previous }.json` ), + ], } ); - const oldPublished = installPublishedPreview( api, oldBuild ); - api.assets.push( - releaseAsset( - 3, - `code-reference-pr-123-${ 'c'.repeat( 40 ) }-200-1.zip`, - '2026-08-07T13:00:00.000Z' - ) - ); - const handoff = createReuseHandoff( oldPublished, { - sourceRepository, - pullRequestNumber: 123, - sourceSha: sha, - workflowRunId: 456, - workflowRunAttempt: 1, - runUrl: `https://github.com/${ repository }/actions/runs/456`, - maximumBytes: 104857600, + await publish( api, { + trunk: true, + handoffDirectory: await handoffDirectory( handoff(), true ), } ); - const directory = await handoffDirectory( handoff, false ); - const result = await publishPullRequest( options( api, directory ) ); - assert.equal( result.status, 'reused' ); - assert.equal( api.uploads.length, 0 ); - assert.deepEqual( api.deleted, [ 3 ] ); - assert.match( api.comments[ 0 ].body, /Status:\*\* Ready/ ); - assert.equal( api.labelRemovals, 1 ); + + assert.deepEqual( api.deletedAssets.sort(), [ 1, 2 ] ); } ); -test( 'cleanup failure cannot replace an already-ready comment', async () => { - const api = new FakeApi(); - api.assets.push( - releaseAsset( - 1, - `code-reference-pr-123-${ 'c'.repeat( 40 ) }-300-1.zip` - ) - ); - api.deleteError = new Error( 'cleanup failed' ); - const directory = await handoffDirectory( buildMetadata() ); +test( 'the first publication creates the shared release', async () => { + const api = new FakeGitHub( { release: null } ); + const result = await publish( api, { + handoffDirectory: await handoffDirectory( handoff(), true ), + } ); + + assert.equal( result.status, 'ready' ); + assert.deepEqual( api.release, { id: 9 } ); + assert.equal( api.uploads.length, 2 ); +} ); + +test( 'a repository without deployment enabled refuses to publish', async () => { await assert.rejects( - publishPullRequest( options( api, directory ) ), - /cleanup failed/ + publish( new FakeGitHub(), { + repository: 'sirreal/wordpress-develop', + stagingVariable: 'false', + handoffDirectory: await handoffDirectory( handoff(), true ), + } ), + /disabled in this repository/ ); - assert.match( api.comments[ 0 ].body, /Status:\*\* Ready/ ); - assert.doesNotMatch( api.comments[ 0 ].body, /Latest attempt failed/ ); - assert.equal( api.labelRemovals, 1 ); } ); -test( 'GitHub read failures remain visible publication failures', async () => { - const api = new FakeApi(); - api.getPullRequest = async () => { - throw new Error( 'GitHub is unavailable' ); - }; - const directory = await handoffDirectory( buildMetadata() ); +test( 'a handoff whose PHP version would break the comment link is refused', async () => { + const api = new FakeGitHub(); await assert.rejects( - publishPullRequest( options( api, directory ) ), - ( error ) => { - assert.ok( error instanceof AggregateError ); - assert.match( error.errors[ 0 ].message, /GitHub is unavailable/ ); - return true; - } + publish( api, { + handoffDirectory: await handoffDirectory( + handoff( { phpVersion: '8.4)](https://evil.example/' } ), + true + ), + } ), + /no valid phpVersion/ ); - assert.equal( api.comments.length, 0 ); + + assert.deepEqual( api.uploads, [] ); + assert.match( api.comments[ 0 ].body, /Latest attempt failed/ ); +} ); + +test( 'a handoff with no valid runtime or provenance is refused', async () => { + /** @type {[string, unknown][]} */ + const broken = [ + [ 'resolvedWordPressBeta', { version: 'trunk' } ], + [ 'dependencyManifestDigest', 'not-a-digest' ], + [ 'generationTimestamp', 'yesterday' ], + ]; + for ( const [ field, value ] of broken ) { + const api = new FakeGitHub(); + await assert.rejects( + publish( api, { + handoffDirectory: await handoffDirectory( + handoff( { [ field ]: value } ), + true + ), + } ), + new RegExp( `no valid ${ field }` ) + ); + assert.deepEqual( api.uploads, [] ); + } +} ); + +test( 'a comment deleted while it is being updated is replaced', async () => { + const api = new FakeGitHub( { + comments: [ { id: 7, body: 'the comment someone deletes' } ], + } ); + api.updateComment = async () => { + api.comments = []; + throw Object.assign( new Error( 'Not Found' ), { status: 404 } ); + }; + const result = await publish( api, { + handoffDirectory: await handoffDirectory( handoff(), true ), + } ); + + assert.equal( result.status, 'ready' ); + assert.equal( api.uploads.length, 2 ); + assert.match( api.comments[ 0 ].body, /\*\*Status:\*\* Ready/ ); +} ); + +test( 'a push during the publication keeps the label, so its build can publish', async () => { + const api = new FakeGitHub(); + api.getPullRequest = async () => + pullRequest( { + head: { + sha: 'b'.repeat( 40 ), + repo: { full_name: sourceRepository }, + }, + } ); + const result = await publish( api, { + handoffDirectory: await handoffDirectory( handoff(), true ), + } ); + + assert.equal( result.status, 'ready' ); + assert.match( api.comments[ 0 ].body, /\*\*Status:\*\* Ready/ ); + assert.equal( api.labelRemovals, 0 ); +} ); + +test( 'a preview stays announced when the tidy-up after it fails', async ( t ) => { + t.mock.method( process.stderr, 'write', () => true ); + const api = new FakeGitHub( { + assets: [ + releaseAsset( + 1, + `code-reference-pr-${ pullRequestNumber }-old.zip` + ), + ], + } ); + api.deleteReleaseAsset = async () => { + throw new Error( 'The release asset is gone.' ); + }; + const result = await publish( api, { + handoffDirectory: await handoffDirectory( handoff(), true ), + } ); + + assert.equal( result.status, 'ready' ); + assert.match( api.comments[ 0 ].body, /\*\*Status:\*\* Ready/ ); } ); diff --git a/.github/scripts/docs-playground-preview/test/published.test.mjs b/.github/scripts/docs-playground-preview/test/published.test.mjs deleted file mode 100644 index 2f094e9fcfdd1..0000000000000 --- a/.github/scripts/docs-playground-preview/test/published.test.mjs +++ /dev/null @@ -1,220 +0,0 @@ -import assert from 'node:assert/strict'; -import { createHash } from 'node:crypto'; -import { test } from 'node:test'; - -import { - findPreviousPreview, - loadPublishedPreview, -} from '../lib/published.mjs'; -import { - PLAYGROUND_ORIGIN, - corsProxyUrl, - metadataAssetName, - playgroundUrl, - releaseAssetUrl, - snapshotAssetName, -} from '../lib/publication.mjs'; - -const repository = 'WordPress/wordpress-develop'; -const sourceRepository = 'contributor/wordpress-develop'; -const pullRequestNumber = 123; -const sourceSha = 'a'.repeat( 40 ); -const bytes = Buffer.from( 'snapshot' ); - -function published() { - const snapshotFilename = snapshotAssetName( { - pullRequestNumber, - sourceSha, - workflowRunId: 456, - workflowRunAttempt: 1, - } ); - const snapshotUrl = releaseAssetUrl( repository, snapshotFilename ); - return { - schemaVersion: 1, - sourceRepository, - pullRequestNumber, - sourceSha, - workflowRunId: '456', - workflowRunAttempt: 1, - runUrl: `https://github.com/${ repository }/actions/runs/456`, - resolvedWordPressBeta: { - channel: 'beta', - version: '7.2-beta1', - downloadUrl: - 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', - }, - phpVersion: '8.4', - dependencyManifestDigest: 'b'.repeat( 64 ), - snapshotFilename, - snapshotBytes: bytes.byteLength, - snapshotSha256: createHash( 'sha256' ).update( bytes ).digest( 'hex' ), - buildStatus: 'success', - validationStatus: 'passed', - generationTimestamp: '2026-08-09T12:34:56.000Z', - publication: { - snapshotUrl, - snapshotProxyUrl: corsProxyUrl( snapshotUrl ), - playgroundUrl: playgroundUrl( snapshotUrl, { - blueprintSchema: - 'https://playground.wordpress.net/blueprint-schema.json', - phpVersion: '8.4', - wordpressVersion: '7.2-beta1', - } ), - publishedAt: '2026-08-09T12:35:00.000Z', - }, - }; -} - -function assets( metadata ) { - return { - metadataAsset: { - name: metadataAssetName( metadata.snapshotFilename ), - browser_download_url: `https://github.com/metadata/${ metadata.snapshotFilename }.json`, - created_at: '2026-08-09T12:35:00Z', - }, - snapshotAsset: { - name: metadata.snapshotFilename, - browser_download_url: releaseAssetUrl( - repository, - metadata.snapshotFilename - ), - created_at: '2026-08-09T12:34:59Z', - }, - }; -} - -function headers() { - return { - get: ( name ) => - ( { - 'x-playground-cors-proxy': 'true', - 'access-control-allow-origin': PLAYGROUND_ORIGIN, - } )[ name.toLowerCase() ] || null, - }; -} - -function publicFetch( metadata, metadataAsset ) { - return async ( url ) => { - if ( url === metadataAsset.browser_download_url ) { - return { status: 200, json: async () => metadata }; - } - assert.equal( url, metadata.publication.snapshotProxyUrl ); - return { - status: 200, - headers: headers(), - arrayBuffer: async () => bytes, - }; - }; -} - -test( 'a published preview is accepted only through its public delivery path', async () => { - const metadata = published(); - const { metadataAsset, snapshotAsset } = assets( metadata ); - assert.equal( - await loadPublishedPreview( - repository, - pullRequestNumber, - metadataAsset, - snapshotAsset, - { - sourceRepository, - sourceSha, - fetchImplementation: publicFetch( metadata, metadataAsset ), - } - ), - metadata - ); -} ); - -test( 'a published preview rejects invalid pointer identity and proxy bytes', async () => { - const metadata = published(); - const { metadataAsset, snapshotAsset } = assets( metadata ); - await assert.rejects( - loadPublishedPreview( - repository, - pullRequestNumber, - metadataAsset, - snapshotAsset, - { - sourceRepository, - sourceSha, - fetchImplementation: publicFetch( - { - ...metadata, - publication: { - ...metadata.publication, - publishedAt: '2026-08-09T12:35:00+04:00', - }, - }, - metadataAsset - ), - } - ), - /pointer metadata/ - ); - - await assert.rejects( - loadPublishedPreview( - repository, - pullRequestNumber, - metadataAsset, - snapshotAsset, - { - sourceRepository, - sourceSha, - fetchImplementation: async ( url ) => { - if ( url === metadataAsset.browser_download_url ) { - return { status: 200, json: async () => metadata }; - } - return { - status: 200, - headers: headers(), - arrayBuffer: async () => Buffer.from( 'changed' ), - }; - }, - } - ), - /bytes/ - ); -} ); - -test( 'previous-preview discovery skips broken and excluded assets', async () => { - const metadata = published(); - const { metadataAsset, snapshotAsset } = assets( metadata ); - const broken = { - name: `code-reference-pr-${ pullRequestNumber }-${ 'c'.repeat( - 40 - ) }-999-1.json`, - browser_download_url: 'https://github.com/metadata/broken.json', - created_at: '2026-08-09T12:36:00Z', - }; - const warnings = []; - const session = { - repository, - context: { pullRequestNumber }, - warning: ( message ) => warnings.push( message ), - fetchImplementation: async ( url ) => { - if ( url === broken.browser_download_url ) { - return { status: 404 }; - } - return publicFetch( metadata, metadataAsset )( url ); - }, - api: { - getRelease: async () => ( { id: 1 } ), - listReleaseAssets: async () => [ - broken, - metadataAsset, - snapshotAsset, - ], - }, - }; - assert.equal( await findPreviousPreview( session ), metadata ); - assert.equal( warnings.length, 1 ); - assert.equal( - await findPreviousPreview( - session, - new Set( [ metadataAsset.name, snapshotAsset.name ] ) - ), - null - ); -} ); diff --git a/.github/scripts/docs-playground-preview/test/publisher.test.mjs b/.github/scripts/docs-playground-preview/test/publisher.test.mjs deleted file mode 100644 index a90dceeeeb5f2..0000000000000 --- a/.github/scripts/docs-playground-preview/test/publisher.test.mjs +++ /dev/null @@ -1,320 +0,0 @@ -import assert from 'node:assert/strict'; -import { createHash } from 'node:crypto'; -import { mkdtemp, writeFile } from 'node:fs/promises'; -import os from 'node:os'; -import path from 'node:path'; -import { test } from 'node:test'; - -import { - assertLatestAuthorized, - createPublishedMetadata, - inspectHandoff, - readPreviewCommentSuccess, - readPreviewCommentSource, - renderPreviewComment, - validateCandidateFile, - validatePublicationContext, -} from '../lib/publisher.mjs'; -import { - COMMENT_MARKER, - metadataAssetName, - snapshotAssetName, -} from '../lib/publication.mjs'; - -const sha = 'a'.repeat( 40 ); -const bytes = Buffer.from( 'snapshot' ); - -function context() { - return { - repository: 'WordPress/wordpress-develop', - stagingVariable: '', - triggerRunId: 456, - triggerRunAttempt: 1, - latestRun: { id: 456, run_attempt: 1 }, - run: { - id: 456, - run_attempt: 1, - name: 'Code Reference Playground Preview Build', - event: 'pull_request', - status: 'completed', - head_sha: sha, - head_repository: { full_name: 'contributor/wordpress-develop' }, - pull_requests: [], - }, - pullRequest: { - number: 123, - state: 'open', - base: { ref: 'trunk' }, - head: { - sha, - repo: { full_name: 'contributor/wordpress-develop' }, - }, - labels: [ { name: 'docs-preview' } ], - }, - }; -} - -function metadata( overrides = {} ) { - const identity = { - pullRequestNumber: 123, - sourceSha: sha, - workflowRunId: 456, - workflowRunAttempt: 1, - }; - return { - schemaVersion: 1, - sourceRepository: 'contributor/wordpress-develop', - pullRequestNumber: 123, - sourceSha: sha, - workflowRunId: '456', - workflowRunAttempt: 1, - runUrl: 'https://github.com/WordPress/wordpress-develop/actions/runs/456', - resolvedWordPressBeta: { - channel: 'beta', - version: '7.2-beta1', - downloadUrl: - 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', - }, - phpVersion: '8.4', - dependencyManifestDigest: 'b'.repeat( 64 ), - snapshotFilename: snapshotAssetName( identity ), - snapshotBytes: bytes.byteLength, - snapshotSha256: createHash( 'sha256' ).update( bytes ).digest( 'hex' ), - buildStatus: 'success', - validationStatus: 'passed', - validationFailures: [], - generationTimestamp: '2026-08-09T12:34:56.000Z', - ...overrides, - }; -} - -test( 'the publisher binds the trigger to an enabled terminal PR run', () => { - const checked = validatePublicationContext( context() ); - assert.equal( checked.sourceSha, sha ); - assert.equal( checked.pullRequestNumber, 123 ); - assert.equal( - checked.runUrl, - 'https://github.com/WordPress/wordpress-develop/actions/runs/456' - ); - assert.throws( - () => - validatePublicationContext( { - ...context(), - repository: 'attacker/wordpress-develop', - } ), - /publication is disabled/ - ); - assert.throws( - () => - validatePublicationContext( { - ...context(), - triggerRunAttempt: 2, - } ), - /no longer current/ - ); - assert.throws( - () => - validatePublicationContext( { - ...context(), - run: { ...context().run, head_sha: 'c'.repeat( 40 ) }, - } ), - /does not match the pull request head/ - ); -} ); - -test( 'latest-wins requires the label except for an explicit rerun', () => { - assert.equal( assertLatestAuthorized( context() ).sourceSha, sha ); - const unlabeled = context(); - unlabeled.pullRequest.labels = []; - assert.throws( () => assertLatestAuthorized( unlabeled ), /authorized/ ); - const rerun = context(); - rerun.triggerRunAttempt = 2; - rerun.run.run_attempt = 2; - rerun.latestRun.run_attempt = 2; - rerun.pullRequest.labels = []; - assert.equal( assertLatestAuthorized( rerun ).workflowRunAttempt, 2 ); - assert.throws( - () => - assertLatestAuthorized( { - ...context(), - latestRun: { id: 789, run_attempt: 1 }, - } ), - /superseded/ - ); -} ); - -test( 'a fresh candidate must pass every publisher identity field', () => { - assert.equal( inspectHandoff( metadata(), context() ).kind, 'candidate' ); - for ( const [ name, value, error ] of [ - [ 'schemaVersion', 2, /schema/ ], - [ 'sourceSha', 'c'.repeat( 40 ), /workflow run/ ], - [ 'workflowRunAttempt', 2, /workflow run/ ], - [ 'phpVersion', '8.5', /runtime identity/ ], - [ 'dependencyManifestDigest', 'broken', /dependency identity/ ], - [ 'handoffType', 'unknown', /Handoff type/ ], - [ 'snapshotFilename', 'snapshot.zip', /filename/ ], - [ 'snapshotBytes', 104857601, /100 MiB/ ], - [ 'snapshotSha256', 'broken', /digest/ ], - ] ) { - assert.throws( - () => inspectHandoff( metadata( { [ name ]: value } ), context() ), - error - ); - } -} ); - -test( 'failed and advisory handoffs are terminal but not candidates', () => { - assert.equal( - inspectHandoff( - metadata( { - buildStatus: 'failed', - validationStatus: 'not-run', - } ), - context() - ).kind, - 'failed' - ); - assert.equal( - inspectHandoff( metadata( { validationStatus: 'failed' } ), context() ) - .kind, - 'invalid' - ); -} ); - -test( 'reuse identifies the exact existing snapshot and metadata assets', () => { - const current = metadata(); - const reused = { - ...current, - handoffType: 'reuse', - reusedSnapshot: { - assetName: current.snapshotFilename, - metadataAssetName: metadataAssetName( current.snapshotFilename ), - }, - }; - assert.equal( inspectHandoff( reused, context() ).kind, 'reuse' ); - assert.throws( - () => - inspectHandoff( - { - ...reused, - reusedSnapshot: { - ...reused.reusedSnapshot, - assetName: 'other.zip', - }, - }, - context() - ), - /Reusable asset identity/ - ); -} ); - -test( 'candidate bytes and digest are checked without opening the snapshot', async () => { - const temporary = await mkdtemp( - path.join( os.tmpdir(), 'docs-preview-publisher-' ) - ); - const current = metadata(); - const filename = path.join( temporary, current.snapshotFilename ); - await writeFile( filename, bytes ); - assert.deepEqual( await validateCandidateFile( current, filename ), { - bytes: bytes.byteLength, - sha256: current.snapshotSha256, - } ); - await writeFile( filename, 'changed!' ); - await assert.rejects( - validateCandidateFile( current, filename ), - /digest/ - ); -} ); - -test( 'published metadata contains the immutable public launch identity', () => { - const published = createPublishedMetadata( - metadata(), - 'WordPress/wordpress-develop', - '2026-08-09T13:00:00.000Z' - ); - assert.equal( - published.publication.publishedAt, - '2026-08-09T13:00:00.000Z' - ); - assert.match( published.publication.snapshotUrl, /releases\/download/ ); - assert.match( - published.publication.snapshotProxyUrl, - /wordpress-playground-cors-proxy/ - ); - assert.match( published.publication.playgroundUrl, /blueprint-url=/ ); -} ); - -test( 'the sticky comment has ready, failed, stale, and expired states', () => { - const preview = createPublishedMetadata( - metadata(), - 'WordPress/wordpress-develop', - '2026-08-09T13:00:00.000Z' - ); - const ready = renderPreviewComment( { - status: 'ready', - preview, - runUrl: preview.runUrl, - } ); - assert.match( ready, new RegExp( COMMENT_MARKER ) ); - assert.match( ready, /Status:\*\* Ready/ ); - assert.match( ready, new RegExp( sha ) ); - assert.deepEqual( readPreviewCommentSource( ready ), { - repository: preview.sourceRepository, - sha, - } ); - assert.deepEqual( readPreviewCommentSuccess( ready ), { - sourceRepository: preview.sourceRepository, - sourceSha: sha, - publication: { playgroundUrl: preview.publication.playgroundUrl }, - } ); - assert.equal( ready.match( /Open Code Reference preview/g ).length, 1 ); - - const failed = renderPreviewComment( { - status: 'failed', - sourceRepository: preview.sourceRepository, - sourceSha: 'c'.repeat( 40 ), - at: '2026-08-09T14:00:00.000Z', - runUrl: preview.runUrl, - previous: preview, - } ); - assert.match( failed, /Latest attempt failed/ ); - assert.equal( failed.match( /Latest successful docs preview/g ).length, 1 ); - assert.deepEqual( readPreviewCommentSuccess( failed ), { - sourceRepository: preview.sourceRepository, - sourceSha: sha, - publication: { playgroundUrl: preview.publication.playgroundUrl }, - } ); - - const stale = renderPreviewComment( { - status: 'stale', - preview, - currentRepository: preview.sourceRepository, - currentSha: 'c'.repeat( 40 ), - } ); - assert.match( stale, /Status:\*\* Stale/ ); - assert.match( stale, /add the `docs-preview` label again/i ); - assert.match( stale, new RegExp( sha ) ); - assert.match( stale, new RegExp( 'c'.repeat( 40 ) ) ); - assert.deepEqual( readPreviewCommentSource( stale ), { - repository: preview.sourceRepository, - sha, - } ); - const unavailable = renderPreviewComment( { - status: 'stale-unavailable', - previousRepository: preview.sourceRepository, - previousSha: sha, - currentRepository: preview.sourceRepository, - currentSha: 'c'.repeat( 40 ), - } ); - assert.match( unavailable, /no healthy docs preview is available/ ); - assert.match( unavailable, /add the `docs-preview` label again/i ); - assert.deepEqual( readPreviewCommentSource( unavailable ), { - repository: preview.sourceRepository, - sha, - } ); - - assert.match( - renderPreviewComment( { status: 'expired' } ), - /preview expired and is no longer live/ - ); -} ); diff --git a/.github/scripts/docs-playground-preview/test/reuse.test.mjs b/.github/scripts/docs-playground-preview/test/reuse.test.mjs new file mode 100644 index 0000000000000..32689d364ccf5 --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/reuse.test.mjs @@ -0,0 +1,133 @@ +import assert from 'node:assert/strict'; +import { mkdtemp, readFile } from 'node:fs/promises'; +import { existsSync } from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { test } from 'node:test'; + +import { FakeGitHub, releaseAsset, sourceSha } from './fake-github.mjs'; +import { reusePublishedSnapshot } from '../reuse.mjs'; + +const current = `code-reference-pr-123-${ sourceSha }-500-1`; +const older = `code-reference-pr-123-${ sourceSha }-400-1`; +const pullRequest = { number: 123, head: { sha: sourceSha } }; + +/** + * The reuse check writes into a directory of its own and appends to the file + * the runner exposes as GITHUB_OUTPUT. + * + * @param {import('node:test').TestContext} t + */ +async function workspace( t ) { + const directory = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-reuse-' ) + ); + const previous = process.env.GITHUB_OUTPUT; + process.env.GITHUB_OUTPUT = path.join( directory, 'github-output' ); + t.after( () => { + if ( previous === undefined ) { + delete process.env.GITHUB_OUTPUT; + } else { + process.env.GITHUB_OUTPUT = previous; + } + } ); + return { + handoff: path.join( directory, 'handoff' ), + output: process.env.GITHUB_OUTPUT, + }; +} + +test( 'reuse hands the newest complete snapshot over and skips the build', async ( t ) => { + const api = new FakeGitHub( { + assets: [ + releaseAsset( 1, `${ older }.zip`, '2026-08-01T00:00:00Z' ), + releaseAsset( 2, `${ older }.json`, '2026-08-01T00:00:00Z' ), + releaseAsset( 3, `${ current }.zip`, '2026-09-01T00:00:00Z' ), + releaseAsset( 4, `${ current }.json`, '2026-09-01T00:00:00Z' ), + ], + } ); + const { handoff, output } = await workspace( t ); + + assert.equal( + await reusePublishedSnapshot( api, pullRequest, handoff ), + `${ current }.zip` + ); + assert.deepEqual( + JSON.parse( + await readFile( path.join( handoff, 'build.json' ), 'utf8' ) + ), + { handoffType: 'reuse' } + ); + assert.equal( await readFile( output, 'utf8' ), 'reused=true\n' ); +} ); + +test( 'the build job skips its four build steps on that output', async () => { + const workflow = await readFile( + fileURLToPath( + new URL( + '../../../workflows/docs-playground-preview-build.yml', + import.meta.url + ) + ), + 'utf8' + ); + + // The name of the output is a contract between the reuse check and the + // steps that a reused snapshot skips: install PHP, resolve the inputs, + // restore the cache and build. + assert.equal( + workflow.match( /steps\.reuse\.outputs\.reused != 'true'/g )?.length, + 4 + ); +} ); + +test( 'a snapshot without its metadata is not reused', async ( t ) => { + const api = new FakeGitHub( { + assets: [ releaseAsset( 1, `${ current }.zip` ) ], + } ); + const { handoff, output } = await workspace( t ); + + assert.equal( + await reusePublishedSnapshot( api, pullRequest, handoff ), + null + ); + assert.equal( existsSync( handoff ), false ); + assert.equal( existsSync( output ), false ); +} ); + +test( 'another pull request or another commit is not reused', async ( t ) => { + const api = new FakeGitHub( { + assets: [ + releaseAsset( 1, `code-reference-pr-999-${ sourceSha }-1-1.zip` ), + releaseAsset( 2, `code-reference-pr-999-${ sourceSha }-1-1.json` ), + releaseAsset( + 3, + `code-reference-pr-123-${ 'b'.repeat( 40 ) }-1-1.zip` + ), + releaseAsset( + 4, + `code-reference-pr-123-${ 'b'.repeat( 40 ) }-1-1.json` + ), + ], + } ); + const { handoff } = await workspace( t ); + + assert.equal( + await reusePublishedSnapshot( api, pullRequest, handoff ), + null + ); +} ); + +test( 'no release means nothing to reuse', async ( t ) => { + const { handoff } = await workspace( t ); + + assert.equal( + await reusePublishedSnapshot( + new FakeGitHub( { release: null } ), + pullRequest, + handoff + ), + null + ); +} ); diff --git a/.github/scripts/docs-playground-preview/test/runtime.test.mjs b/.github/scripts/docs-playground-preview/test/runtime.test.mjs deleted file mode 100644 index 9cecb70a94378..0000000000000 --- a/.github/scripts/docs-playground-preview/test/runtime.test.mjs +++ /dev/null @@ -1,51 +0,0 @@ -import assert from 'node:assert/strict'; -import { test } from 'node:test'; - -import { renderRuntimePlugin, validateProvenance } from '../lib/runtime.mjs'; - -const provenance = { - sourceRepository: 'example/wordpress-develop', - sourceSha: 'a'.repeat( 40 ), - generationTimestamp: '2026-08-09T12:34:56.000Z', - runUrl: 'https://github.com/example/wordpress-develop/actions/runs/123', -}; - -test( 'runtime provenance requires exact public build identity', () => { - assert.equal( validateProvenance( provenance ), provenance ); - assert.throws( - () => validateProvenance( { ...provenance, sourceSha: 'trunk' } ), - /commit is invalid/ - ); - assert.throws( - () => - validateProvenance( { - ...provenance, - runUrl: 'https://example.com/', - } ), - /run URL is invalid/ - ); -} ); - -test( 'runtime plugin exposes the banner, health, network, and policy behavior', () => { - const plugin = renderRuntimePlugin( provenance ); - assert.match( plugin, /wporg-code-reference-preview-provenance/ ); - assert.match( plugin, /wp_body_open/ ); - assert.match( plugin, /wp_footer/ ); - assert.match( plugin, /docs-preview\/v1/ ); - assert.match( plugin, /docs_preview_network_disabled/ ); - for ( const name of [ - 'DISABLE_WP_CRON', - 'AUTOMATIC_UPDATER_DISABLED', - 'WP_AUTO_UPDATE_CORE', - 'DISALLOW_FILE_MODS', - ] ) { - assert.match( plugin, new RegExp( name ) ); - } - assert.match( plugin, new RegExp( provenance.sourceSha ) ); - assert.match( plugin, new RegExp( provenance.runUrl ) ); -} ); - -test( 'local runtime provenance may omit an Actions run', () => { - const plugin = renderRuntimePlugin( { ...provenance, runUrl: null } ); - assert.match( plugin, /'runUrl'\s+=> null/ ); -} ); diff --git a/.github/scripts/docs-playground-preview/test/snapshot.test.mjs b/.github/scripts/docs-playground-preview/test/snapshot.test.mjs new file mode 100644 index 0000000000000..587300658a82c --- /dev/null +++ b/.github/scripts/docs-playground-preview/test/snapshot.test.mjs @@ -0,0 +1,102 @@ +import assert from 'node:assert/strict'; +import { mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { test } from 'node:test'; + +import { packageSnapshot } from '../lib/snapshot.mjs'; + +const SNAPSHOT_BYTES_LIMIT = 104857600; + +const inputs = { + dependencies: { + playground: { + phpVersion: '8.4', + }, + }, + wordpress: { channel: 'beta', version: '7.2-beta1' }, +}; + +/** + * Stands in for the pinned Playground CLI, which packageSnapshot loads from + * beside the executable it is given. It writes a snapshot of the size asked + * for, as a sparse file so that the size costs nothing. + * + * @param {number} bytes + */ +async function playgroundCli( bytes ) { + const root = await mkdtemp( + path.join( os.tmpdir(), 'docs-preview-snapshot-' ) + ); + const module = path.join( root, 'node_modules/@wp-playground/cli' ); + await mkdir( module, { recursive: true } ); + await writeFile( + path.join( module, 'index.js' ), + `import { open } from 'node:fs/promises'; +export async function runCLI( options ) { + const handle = await open( options.outfile, 'w' ); + await handle.truncate( ${ bytes } ); + await handle.close(); +} +` + ); + await mkdir( path.join( root, 'cache' ), { recursive: true } ); + await writeFile( path.join( root, 'cache/base.zip' ), 'base' ); + await writeFile( path.join( root, 'reference.json' ), '[]' ); + return { + inputs: { ...inputs, cacheDirectory: path.join( root, 'cache' ) }, + options: { + workDirectory: path.join( root, 'work' ), + output: path.join( root, 'snapshot.zip' ), + referenceJson: path.join( root, 'reference.json' ), + stagedSource: path.join( root, 'source' ), + playgroundCli: path.join( + root, + 'node_modules/.bin/wp-playground-cli' + ), + provenance: { + sourceRepository: 'WordPress/wordpress-develop', + sourceSha: 'a'.repeat( 40 ), + generationTimestamp: '2026-09-04T11:00:00.000Z', + runUrl: null, + }, + }, + }; +} + +test( 'a snapshot over the limit is not handed to the publisher', async () => { + const { inputs: resolved, options } = await playgroundCli( + SNAPSHOT_BYTES_LIMIT + 1 + ); + + await assert.rejects( + packageSnapshot( resolved, options ), + /The snapshot is 104857601 bytes; the limit is 104857600\./ + ); +} ); + +test( 'the packaged snapshot imports the reference into the cached base', async () => { + const { inputs: resolved, options } = await playgroundCli( 1024 ); + await packageSnapshot( resolved, options ); + + const blueprint = JSON.parse( + await readFile( + path.join( options.workDirectory, 'final-blueprint.json' ), + 'utf8' + ) + ); + assert.equal( blueprint.login, false ); + assert.deepEqual( blueprint.features, { networking: false } ); + assert.deepEqual( blueprint.preferredVersions, { + php: '8.4', + wp: '7.2-beta1', + } ); + // The provenance the preview banner reads names the commit that was built. + assert.match( + await readFile( + path.join( options.workDirectory, 'provenance.php' ), + 'utf8' + ), + new RegExp( `'sha'\\s+=> '${ 'a'.repeat( 40 ) }'` ) + ); +} ); diff --git a/.github/scripts/docs-playground-preview/test/trunk.test.mjs b/.github/scripts/docs-playground-preview/test/trunk.test.mjs deleted file mode 100644 index b4ca1ad9f27bb..0000000000000 --- a/.github/scripts/docs-playground-preview/test/trunk.test.mjs +++ /dev/null @@ -1,279 +0,0 @@ -import assert from 'node:assert/strict'; -import { createHash } from 'node:crypto'; -import { readFile } from 'node:fs/promises'; -import { test } from 'node:test'; - -import { PLAYGROUND_ORIGIN, corsProxyUrl } from '../lib/publication.mjs'; -import { - TRUNK_POINTER_ASSET, - TRUNK_POINTER_REF, - assertLatestTrunkAuthorized, - createTrunkBlueprint, - createTrunkPublishedMetadata, - inspectTrunkHandoff, - trunkPlaygroundUrl, - trunkBlueprintCommitUrl, - trunkSnapshotAssetName, - trunkStableBlueprintUrl, - validatePublicBlueprint, - validatePublishedTrunkMetadata, - validateTrunkPublicationContext, -} from '../lib/trunk.mjs'; - -const repository = 'WordPress/wordpress-develop'; -const sha = 'a'.repeat( 40 ); -const snapshot = Buffer.from( 'snapshot' ); - -function context( overrides = {} ) { - const run = { - id: 456, - run_attempt: 2, - name: 'Code Reference Playground Preview Build', - event: 'push', - status: 'completed', - head_branch: 'trunk', - head_sha: sha, - head_repository: { full_name: repository }, - }; - return { - repository, - stagingVariable: '', - triggerRunId: 456, - triggerRunAttempt: 2, - run, - latestRun: run, - trunkHeadSha: sha, - ...overrides, - }; -} - -function metadata( overrides = {} ) { - const identity = { - sourceSha: sha, - workflowRunId: 456, - workflowRunAttempt: 2, - }; - return { - schemaVersion: 1, - sourceRepository: repository, - pullRequestNumber: null, - sourceSha: sha, - workflowRunId: '456', - workflowRunAttempt: 2, - runUrl: `https://github.com/${ repository }/actions/runs/456`, - resolvedWordPressBeta: { - channel: 'beta', - version: '7.2-beta1', - downloadUrl: - 'https://downloads.wordpress.org/release/wordpress-7.2-beta1.zip', - }, - phpVersion: '8.4', - dependencyManifestDigest: 'b'.repeat( 64 ), - snapshotFilename: trunkSnapshotAssetName( identity ), - snapshotBytes: snapshot.byteLength, - snapshotSha256: createHash( 'sha256' ) - .update( snapshot ) - .digest( 'hex' ), - buildStatus: 'success', - validationStatus: 'passed', - validationFailures: [], - generationTimestamp: '2026-08-09T12:34:56.000Z', - ...overrides, - }; -} - -function headers( overrides = {} ) { - const values = { - 'x-playground-cors-proxy': 'true', - 'access-control-allow-origin': PLAYGROUND_ORIGIN, - ...overrides, - }; - return { get: ( name ) => values[ name.toLowerCase() ] || null }; -} - -test( 'trunk assets bind the exact SHA, run, and attempt', () => { - const identity = { - sourceSha: sha, - workflowRunId: 456, - workflowRunAttempt: 2, - }; - assert.equal( - trunkSnapshotAssetName( identity ), - `code-reference-trunk-${ sha }-456-2.zip` - ); - assert.equal( - trunkBlueprintCommitUrl( repository, sha ), - `https://raw.githubusercontent.com/${ repository }/${ sha }/${ TRUNK_POINTER_ASSET }` - ); - assert.equal( TRUNK_POINTER_REF, 'heads/docs-preview-code-reference' ); - assert.throws( - () => trunkSnapshotAssetName( { ...identity, sourceSha: 'short' } ), - /full lowercase commit/ - ); -} ); - -test( 'the stable trunk entry point uses the fixed public Blueprint asset', () => { - const blueprintUrl = `https://raw.githubusercontent.com/${ repository }/docs-preview-code-reference/${ TRUNK_POINTER_ASSET }`; - assert.equal( trunkStableBlueprintUrl( repository ), blueprintUrl ); - const launch = new URL( trunkPlaygroundUrl( repository ) ); - assert.equal( launch.origin, PLAYGROUND_ORIGIN ); - assert.equal( - launch.searchParams.get( 'blueprint-url' ), - corsProxyUrl( blueprintUrl ) - ); -} ); - -test( 'the repository README links the stable trunk entry point', async () => { - const readme = await readFile( - new URL( '../../../../README.md', import.meta.url ), - 'utf8' - ); - assert.match( readme, /latest successful Core Code Reference preview/ ); - assert.ok( readme.includes( trunkPlaygroundUrl( repository ) ) ); -} ); - -test( 'trunk publication binds the exact terminal push and latest head', () => { - const checked = validateTrunkPublicationContext( context() ); - assert.equal( checked.sourceSha, sha ); - assert.equal( checked.sourceRepository, repository ); - assert.equal( checked.workflowRunAttempt, 2 ); - assert.equal( assertLatestTrunkAuthorized( context() ).workflowRunId, 456 ); - assert.throws( - () => - validateTrunkPublicationContext( - context( { repository: 'attacker/wordpress-develop' } ) - ), - /publication is disabled/ - ); - assert.throws( - () => - validateTrunkPublicationContext( { - ...context(), - run: { ...context().run, event: 'pull_request' }, - } ), - /source workflow identity/ - ); - assert.throws( - () => - assertLatestTrunkAuthorized( { - ...context(), - trunkHeadSha: 'c'.repeat( 40 ), - } ), - /superseded/ - ); -} ); - -test( 'the trunk handoff accepts only a passed exact-run candidate', () => { - assert.equal( - inspectTrunkHandoff( metadata(), context() ).kind, - 'candidate' - ); - assert.equal( - inspectTrunkHandoff( - metadata( { validationStatus: 'failed' } ), - context() - ).kind, - 'invalid' - ); - assert.equal( - inspectTrunkHandoff( - metadata( { - buildStatus: 'failed', - validationStatus: 'not-run', - } ), - context() - ).kind, - 'failed' - ); - for ( const [ name, value, error ] of [ - [ 'pullRequestNumber', 123, /trunk run/ ], - [ 'sourceSha', 'c'.repeat( 40 ), /trunk run/ ], - [ 'workflowRunAttempt', 1, /trunk run/ ], - [ 'phpVersion', '8.5', /runtime identity/ ], - [ 'dependencyManifestDigest', 'broken', /dependency identity/ ], - [ 'snapshotFilename', 'snapshot.zip', /filename/ ], - [ 'snapshotBytes', 104857601, /snapshot identity/ ], - ] ) { - assert.throws( - () => - inspectTrunkHandoff( - metadata( { [ name ]: value } ), - context() - ), - error - ); - } -} ); - -test( 'published trunk metadata produces an exact immutable Blueprint', () => { - const published = createTrunkPublishedMetadata( - metadata(), - repository, - '2026-08-09T13:00:00.000Z' - ); - assert.equal( - validatePublishedTrunkMetadata( published, repository, sha ), - published - ); - const blueprint = createTrunkBlueprint( published ); - assert.equal( blueprint.landingPage, '/reference/' ); - assert.equal( blueprint.login, false ); - assert.equal( blueprint.features.networking, false ); - assert.equal( - blueprint.steps[ 0 ].zipFile.url, - published.publication.snapshotProxyUrl - ); - assert.match( blueprint.meta.description, new RegExp( sha ) ); - assert.match( blueprint.meta.description, /actions\/runs\/456/ ); - const broken = structuredClone( published ); - broken.publication.stableBlueprintUrl = 'https://example.test/broken'; - assert.throws( - () => validatePublishedTrunkMetadata( broken, repository, sha ), - /pointer metadata/ - ); -} ); - -test( 'the public Blueprint must arrive intact through the Playground proxy', async () => { - const expected = createTrunkBlueprint( - createTrunkPublishedMetadata( - metadata(), - repository, - '2026-08-09T13:00:00.000Z' - ) - ); - const publicUrl = trunkStableBlueprintUrl( repository ); - const fetchImplementation = async ( url, options ) => { - assert.equal( url, corsProxyUrl( publicUrl ) ); - assert.equal( options.headers.Origin, PLAYGROUND_ORIGIN ); - return { - status: 200, - headers: headers(), - arrayBuffer: async () => - Buffer.from( `${ JSON.stringify( expected ) }\n` ), - }; - }; - assert.deepEqual( - await validatePublicBlueprint( - publicUrl, - expected, - fetchImplementation - ), - expected - ); - await assert.rejects( - validatePublicBlueprint( publicUrl, expected, async () => ( { - status: 200, - headers: headers(), - arrayBuffer: async () => Buffer.from( '{}' ), - } ) ), - /does not identify/ - ); - await assert.rejects( - validatePublicBlueprint( publicUrl, expected, async () => ( { - status: 200, - headers: headers( { 'x-playground-cors-proxy': 'false' } ), - arrayBuffer: async () => Buffer.from( '{}' ), - } ) ), - /did not come through/ - ); -} ); diff --git a/.github/scripts/docs-playground-preview/test/validate.test.mjs b/.github/scripts/docs-playground-preview/test/validate.test.mjs index df3836fd09c3c..6c754a455195d 100644 --- a/.github/scripts/docs-playground-preview/test/validate.test.mjs +++ b/.github/scripts/docs-playground-preview/test/validate.test.mjs @@ -1,12 +1,8 @@ import assert from 'node:assert/strict'; +import { createServer } from 'node:http'; import { test } from 'node:test'; -import { - createValidationBlueprint, - createValidationServerArguments, - inspectSnapshotBehavior, - validateSnapshot, -} from '../lib/validate.mjs'; +import { inspectSnapshotBehavior } from '../lib/validate.mjs'; const provenance = { sourceRepository: 'example/wordpress-develop', @@ -15,232 +11,164 @@ const provenance = { runUrl: 'https://github.com/example/wordpress-develop/actions/runs/123', }; -function inputs() { - return { - wordpress: { version: '7.2-beta1' }, - dependencies: { - playground: { - blueprintSchema: - 'https://playground.wordpress.net/blueprint-schema.json', - phpVersion: '8.4', - }, - validation: { - routes: { - index: { - path: '/reference/', - expectedText: 'Reference index', - }, - class: { - path: '/reference/classes/example/', - expectedText: 'Example_Class', - }, - method: { - path: '/reference/classes/example/run/', - expectedText: 'Example_Class::run', - }, - function: { - path: '/reference/functions/example/', - expectedText: 'example_function', - }, - hook: { - path: '/reference/hooks/example_action/', - expectedText: 'example_action', - }, - filter: { - path: '/reference/hooks/example_filter/', - expectedText: 'example_filter', - }, - }, - search: { - path: '/?s=example', - expectedText: 'example_result', - expectedPath: '/reference/functions/example/', +const inputs = { + wordpress: { version: '7.2-beta1' }, + dependencies: { + playground: { phpVersion: '8.4' }, + validation: { + routes: { + index: { path: '/reference/', expectedText: 'Code Reference' }, + function: { + path: '/reference/functions/example/', + expectedText: 'example_function', }, }, + search: { + path: '/?s=example&post_type=wp-parser-function', + expectedText: 'example_function', + expectedPath: '/reference/functions/example/', + }, }, - }; -} + }, +}; -function response( body, status = 200, url = 'http://127.0.0.1:9400/' ) { +const health = { + import: { stage: 'complete-import' }, + outboundNetworkDisabled: true, + constants: { + DISABLE_WP_CRON: true, + AUTOMATIC_UPDATER_DISABLED: true, + WP_AUTO_UPDATE_CORE: true, + DISALLOW_FILE_MODS: true, + }, +}; + +/** + * @param {Record} [overrides] + */ +function page( overrides = {} ) { + const banner = ``; return { - status, - url, - text: async () => - typeof body === 'string' ? body : JSON.stringify( body ), + status: 200, + body: `${ banner } example_function Code Reference example`, + ...overrides, }; } -function healthyFetch( url ) { - if ( url.endsWith( '/wp-json/docs-preview/v1/health' ) ) { - return response( - { - provenance, - import: { stage: 'complete-import' }, - outboundNetworkDisabled: true, - constants: { - DISABLE_WP_CRON: true, - AUTOMATIC_UPDATER_DISABLED: true, - WP_AUTO_UPDATE_CORE: true, - DISALLOW_FILE_MODS: true, - }, - }, - 200, - url - ); - } - if ( url.includes( '?s=example' ) ) { - return response( - ` example_result example_function`, - 200, - url - ); - } - const target = Object.values( - inputs().dependencies.validation.routes - ).find( ( route ) => url.endsWith( route.path ) ); - return response( - ` ${ target.expectedText }`, - 200, - url +/** + * Serves the pages the validator requests, so the checks run against a real + * HTTP client and real redirects. + * + * @param {(pathname: string) => Record} respond + * @param {(baseUrl: string) => Promise} inspect + */ +async function withServer( respond, inspect ) { + const server = createServer( ( request, response ) => { + const { pathname } = new URL( request.url || '/', 'http://127.0.0.1' ); + const page = respond( pathname ); + if ( page.location ) { + response.writeHead( 302, { location: page.location } ); + response.end(); + return; + } + response.writeHead( page.status, { + 'content-type': page.json + ? 'application/json' + : 'text/html; charset=utf-8', + } ); + response.end( page.json ? JSON.stringify( page.json ) : page.body ); + } ); + await new Promise( ( resolve ) => + server.listen( 0, '127.0.0.1', () => resolve( undefined ) ) ); + const address = server.address(); + const port = typeof address === 'object' && address ? address.port : 0; + try { + return await inspect( `http://127.0.0.1:${ port }` ); + } finally { + server.close(); + } } -test( 'validation Blueprint boots the exact snapshot without login or networking', () => { - const blueprint = createValidationBlueprint( inputs() ); - assert.equal( blueprint.login, false ); - assert.deepEqual( blueprint.features, { networking: false } ); - assert.equal( blueprint.preferredVersions.php, '8.4' ); - assert.equal( blueprint.preferredVersions.wp, '7.2-beta1' ); - assert.deepEqual( blueprint.steps, [ - { - step: 'unzip', - zipFile: { resource: 'bundled', path: 'snapshot.zip' }, - extractToPath: '/', - }, - ] ); -} ); - -test( 'validation server uses six Playground workers', () => { - const args = createValidationServerArguments( - inputs(), - '/tmp/validation-blueprint.json', - 'http://127.0.0.1:9400', - 9400 +/** + * @param {(pathname: string) => Record} respond + */ +function validate( respond ) { + return withServer( respond, ( baseUrl ) => + inspectSnapshotBehavior( inputs, baseUrl, provenance ) ); - assert.equal( args[ args.indexOf( '--workers' ) + 1 ], '6' ); - assert.deepEqual( args.slice( 0, 5 ), [ - 'server', - '--php', - '8.4', - '--wp', - '7.2-beta1', - ] ); -} ); +} -test( 'behavioral validation covers health, routes, search, and banner', async () => { - const result = await inspectSnapshotBehavior( inputs(), { - baseUrl: 'http://127.0.0.1:9400', - fetchImplementation: healthyFetch, - provenance, - requireRunUrl: true, - } ); +/** + * @param {string} pathname + */ +function healthySite( pathname ) { + return pathname === '/wp-json/docs-preview/v1/health' + ? { status: 200, json: health } + : page(); +} + +test( 'a complete preview reports no failures', async () => { + const result = await validate( healthySite ); assert.deepEqual( result.failures, [] ); - assert.deepEqual( result.checks, { - health: 200, - index: 200, - class: 200, - method: 200, - function: 200, - hook: 200, - filter: 200, - search: 200, - } ); } ); -test( 'behavioral defects are advisory validation failures', async () => { - const result = await inspectSnapshotBehavior( inputs(), { - baseUrl: 'http://127.0.0.1:9400', - fetchImplementation: async ( url ) => { - if ( url.endsWith( '/health' ) ) { - return response( - { - provenance: { - ...provenance, - sourceSha: 'b'.repeat( 40 ), - }, +test( 'an incomplete import and a relaxed runtime policy are reported', async () => { + const result = await validate( ( pathname ) => + pathname === '/wp-json/docs-preview/v1/health' + ? { + status: 200, + json: { import: null, outboundNetworkDisabled: false, - constants: {}, + constants: { + ...health.constants, + DISABLE_WP_CRON: false, + }, }, - 200, - url - ); - } - return response( 'broken', 404, url ); - }, - provenance: { ...provenance, runUrl: null }, - requireRunUrl: true, - } ); - assert.ok( - result.failures.some( ( failure ) => failure.includes( 'sourceSha' ) ) - ); - assert.ok( - result.failures.some( ( failure ) => - failure.includes( 'terminal stage' ) - ) - ); - assert.ok( - result.failures.some( ( failure ) => - failure.includes( 'outbound networking' ) - ) - ); - assert.ok( - result.failures.some( ( failure ) => - failure.includes( 'DISABLE_WP_CRON' ) - ) - ); - assert.ok( - result.failures.some( ( failure ) => failure.includes( 'class route' ) ) + } + : page() ); - assert.ok( - result.failures.some( ( failure ) => failure.includes( 'search' ) ) - ); - assert.ok( - result.failures.some( ( failure ) => - failure.includes( 'Actions run URL' ) - ) + assert.deepEqual( result.failures, [ + 'The Code Reference import did not complete.', + 'WordPress outbound networking is not disabled.', + 'Runtime policy constant DISABLE_WP_CRON is not enforced.', + ] ); +} ); + +test( 'a missing symbol page is reported', async () => { + const result = await validate( ( pathname ) => + pathname === '/reference/functions/example/' + ? page( { status: 404, body: 'Not found' } ) + : healthySite( pathname ) ); + assert.deepEqual( result.failures, [ + 'function route returned HTTP 404: Not found', + 'function route is missing example_function.', + `function page banner is missing id="wporg-code-reference-preview-provenance", ${ provenance.sourceRepository }, ${ provenance.sourceSha }, 2026-08-09 12:34:56 UTC, ${ provenance.runUrl }.`, + ] ); } ); -test( 'symbol routes may not redirect to a generic successful page', async () => { - const baseUrl = 'http://127.0.0.1:9400'; - const result = await inspectSnapshotBehavior( inputs(), { - baseUrl, - fetchImplementation: async ( url ) => { - const current = await healthyFetch( url ); - if ( url.includes( '/reference/classes/example/' ) ) { - return { ...current, url: `${ baseUrl }/reference/` }; - } - return current; - }, - provenance, - requireRunUrl: true, - } ); - assert.ok( - result.failures.includes( - 'class route redirected away from its stable path.' - ) +test( 'a symbol page that redirects to the index is reported', async () => { + const result = await validate( ( pathname ) => + pathname === '/reference/functions/example/' + ? { location: '/reference/' } + : healthySite( pathname ) ); + assert.deepEqual( result.failures, [ + 'function route redirected away from its stable path.', + ] ); } ); -test( 'a Playground boot failure is returned as validation state', async () => { - const result = await validateSnapshot( inputs(), { - serveImplementation: async () => { - throw new Error( 'did not boot' ); - }, - } ); - assert.deepEqual( result, { - failures: [ 'Playground boot failed: did not boot' ], - checks: {}, - } ); +test( 'search results that never reach the symbol are reported', async () => { + const result = await validate( ( pathname ) => + pathname === '/' + ? page( { body: 'No results found.' } ) + : healthySite( pathname ) + ); + assert.deepEqual( result.failures, [ + 'search route is missing example_function.', + 'search route is missing /reference/functions/example/.', + `search page banner is missing id="wporg-code-reference-preview-provenance", ${ provenance.sourceRepository }, ${ provenance.sourceSha }, 2026-08-09 12:34:56 UTC, ${ provenance.runUrl }.`, + ] ); } ); diff --git a/.github/workflows/docs-playground-preview-build.yml b/.github/workflows/docs-playground-preview-build.yml index ed311e720e7e8..6549073f5f026 100644 --- a/.github/workflows/docs-playground-preview-build.yml +++ b/.github/workflows/docs-playground-preview-build.yml @@ -35,15 +35,9 @@ jobs: permissions: contents: read env: - DEPLOYMENT_REPOSITORY: ${{ github.repository }} SOURCE_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }} SOURCE_SHA: ${{ github.event.pull_request.head.sha }} - PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} - WORKFLOW_RUN_ID: ${{ github.run_id }} - WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }} - RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} HANDOFF_DIRECTORY: ${{ github.workspace }}/handoff - SNAPSHOT_NAME: code-reference-pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }}-${{ github.run_id }}-${{ github.run_attempt }}.zip steps: - name: Check out the exact pull-request head uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -56,24 +50,15 @@ jobs: - name: Set up the pinned Node version uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: '20.20.2' + node-version-file: '.github/docs-playground-preview/.nvmrc' + # A re-run is a request for a fresh build, so it never reuses a snapshot. - name: Reuse a healthy public snapshot for the same SHA id: reuse if: ${{ github.run_attempt == 1 }} env: GITHUB_TOKEN: ${{ github.token }} - run: >- - node .github/scripts/docs-playground-preview/reuse.mjs - --repository "$DEPLOYMENT_REPOSITORY" - --source-repository "$SOURCE_REPOSITORY" - --pull-request-number "$PULL_REQUEST_NUMBER" - --source-sha "$SOURCE_SHA" - --workflow-run-id "$WORKFLOW_RUN_ID" - --workflow-run-attempt "$WORKFLOW_RUN_ATTEMPT" - --run-url "$RUN_URL" - --maximum-bytes 104857600 - --output "$HANDOFF_DIRECTORY/build.json" + run: node .github/scripts/docs-playground-preview/reuse.mjs "$HANDOFF_DIRECTORY" - name: Set up the pinned PHP and Composer versions if: ${{ steps.reuse.outputs.reused != 'true' }} @@ -89,18 +74,10 @@ jobs: run: >- npm --prefix .github/docs-playground-preview run build -- --resolve-only - --source "$GITHUB_WORKSPACE" - --source-repository "$SOURCE_REPOSITORY" - --source-sha "$SOURCE_SHA" - --pull-request "$PULL_REQUEST_NUMBER" - --run-id "$WORKFLOW_RUN_ID" - --run-attempt "$WORKFLOW_RUN_ATTEMPT" - --run-url "$RUN_URL" --runner-image ubuntu-24.04 --cache-root "$RUNNER_TEMP/docs-preview-cache" - --work-root "$RUNNER_TEMP/docs-preview-work" --inputs-output "$RUNNER_TEMP/docs-preview-resolved-inputs.json" - --metadata "$HANDOFF_DIRECTORY/build.json" + --handoff "$HANDOFF_DIRECTORY" - name: Restore the exact invariant site base if: ${{ steps.reuse.outputs.reused != 'true' }} @@ -117,22 +94,17 @@ jobs: DOCS_PREVIEW_ENFORCE: ${{ vars.DOCS_PREVIEW_ENFORCE }} run: >- npm --prefix .github/docs-playground-preview run build -- - --source "$GITHUB_WORKSPACE" --source-repository "$SOURCE_REPOSITORY" --source-sha "$SOURCE_SHA" - --pull-request "$PULL_REQUEST_NUMBER" - --run-id "$WORKFLOW_RUN_ID" - --run-attempt "$WORKFLOW_RUN_ATTEMPT" - --run-url "$RUN_URL" + --run-url "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" --runner-image ubuntu-24.04 --cache-root "$RUNNER_TEMP/docs-preview-cache" --work-root "$RUNNER_TEMP/docs-preview-work" --resolved-inputs "$RUNNER_TEMP/docs-preview-resolved-inputs.json" - --output "$HANDOFF_DIRECTORY/$SNAPSHOT_NAME" - --metadata "$HANDOFF_DIRECTORY/build.json" + --handoff "$HANDOFF_DIRECTORY" - name: Upload the terminal publisher handoff - if: ${{ always() && ! cancelled() }} + if: ${{ ! cancelled() }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: docs-preview-handoff-${{ github.run_id }}-${{ github.run_attempt }} @@ -155,13 +127,7 @@ jobs: permissions: contents: read env: - SOURCE_REPOSITORY: ${{ github.repository }} - SOURCE_SHA: ${{ github.sha }} - WORKFLOW_RUN_ID: ${{ github.run_id }} - WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }} - RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} HANDOFF_DIRECTORY: ${{ github.workspace }}/handoff - SNAPSHOT_NAME: code-reference-trunk-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}.zip steps: - name: Check out the exact trunk commit uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -173,7 +139,7 @@ jobs: - name: Set up the pinned Node version uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: '20.20.2' + node-version-file: '.github/docs-playground-preview/.nvmrc' - name: Set up the pinned PHP and Composer versions uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 @@ -187,17 +153,10 @@ jobs: run: >- npm --prefix .github/docs-playground-preview run build -- --resolve-only - --source "$GITHUB_WORKSPACE" - --source-repository "$SOURCE_REPOSITORY" - --source-sha "$SOURCE_SHA" - --run-id "$WORKFLOW_RUN_ID" - --run-attempt "$WORKFLOW_RUN_ATTEMPT" - --run-url "$RUN_URL" --runner-image ubuntu-24.04 --cache-root "$RUNNER_TEMP/docs-preview-cache" - --work-root "$RUNNER_TEMP/docs-preview-work" --inputs-output "$RUNNER_TEMP/docs-preview-resolved-inputs.json" - --metadata "$HANDOFF_DIRECTORY/build.json" + --handoff "$HANDOFF_DIRECTORY" - name: Restore the exact invariant site base uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 @@ -212,21 +171,17 @@ jobs: DOCS_PREVIEW_ENFORCE: ${{ vars.DOCS_PREVIEW_ENFORCE }} run: >- npm --prefix .github/docs-playground-preview run build -- - --source "$GITHUB_WORKSPACE" - --source-repository "$SOURCE_REPOSITORY" - --source-sha "$SOURCE_SHA" - --run-id "$WORKFLOW_RUN_ID" - --run-attempt "$WORKFLOW_RUN_ATTEMPT" - --run-url "$RUN_URL" + --source-repository "$GITHUB_REPOSITORY" + --source-sha "$GITHUB_SHA" + --run-url "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" --runner-image ubuntu-24.04 --cache-root "$RUNNER_TEMP/docs-preview-cache" --work-root "$RUNNER_TEMP/docs-preview-work" --resolved-inputs "$RUNNER_TEMP/docs-preview-resolved-inputs.json" - --output "$HANDOFF_DIRECTORY/$SNAPSHOT_NAME" - --metadata "$HANDOFF_DIRECTORY/build.json" + --handoff "$HANDOFF_DIRECTORY" - name: Upload the terminal trunk publisher handoff - if: ${{ always() && ! cancelled() }} + if: ${{ ! cancelled() }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: docs-preview-trunk-handoff-${{ github.run_id }}-${{ github.run_attempt }} diff --git a/.github/workflows/docs-playground-preview-lifecycle.yml b/.github/workflows/docs-playground-preview-lifecycle.yml index fb7f7f0cbdc3f..136b671c2d737 100644 --- a/.github/workflows/docs-playground-preview-lifecycle.yml +++ b/.github/workflows/docs-playground-preview-lifecycle.yml @@ -1,96 +1,95 @@ name: Code Reference Playground Preview Lifecycle on: # zizmor: ignore[dangerous-triggers] Both jobs check out and run only trusted default-branch code. - pull_request_target: - branches: - - trunk - types: - - synchronize - - closed + pull_request_target: + branches: + - trunk + types: + - synchronize + - closed permissions: {} jobs: - stale: - name: Mark Code Reference preview stale - if: >- - github.event.action == 'synchronize' && - ( github.repository == 'WordPress/wordpress-develop' || - ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) - runs-on: ubuntu-24.04 - timeout-minutes: 10 - concurrency: - group: docs-preview-stale-pr-${{ github.event.pull_request.number }} - cancel-in-progress: true - permissions: - actions: read - contents: read - pull-requests: write - steps: - - name: Check out trusted default-branch lifecycle code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.event.repository.default_branch }} - persist-credentials: false - show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + stale: + name: Mark Code Reference preview stale + if: >- + github.event.action == 'synchronize' && + ( github.repository == 'WordPress/wordpress-develop' || + ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) + runs-on: ubuntu-24.04 + timeout-minutes: 10 + concurrency: + group: docs-preview-stale-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + permissions: + contents: read + pull-requests: write + steps: + - name: Check out trusted default-branch lifecycle code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.repository.default_branch }} + persist-credentials: false + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - - name: Set up the pinned Node version - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: '20.20.2' + - name: Set up the pinned Node version + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version-file: '.github/docs-playground-preview/.nvmrc' - - name: Mark the published preview stale - env: - DOCS_PREVIEW_STAGING: ${{ vars.DOCS_PREVIEW_STAGING }} - GITHUB_TOKEN: ${{ github.token }} - run: node .github/scripts/docs-playground-preview/lifecycle.mjs + - name: Mark the published preview stale + env: + DOCS_PREVIEW_STAGING: ${{ vars.DOCS_PREVIEW_STAGING }} + GITHUB_TOKEN: ${{ github.token }} + run: node .github/scripts/docs-playground-preview/lifecycle.mjs - quiesce-build: - name: Stop the obsolete Code Reference build - if: >- - github.event.action == 'closed' && - ( github.repository == 'WordPress/wordpress-develop' || - ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) - runs-on: ubuntu-24.04 - timeout-minutes: 5 - concurrency: - group: docs-preview-build-pr-${{ github.event.pull_request.number }} - cancel-in-progress: true - steps: - - name: Wait for build cancellation - run: ':' + quiesce-build: + name: Stop the obsolete Code Reference build + if: >- + github.event.action == 'closed' && + ( github.repository == 'WordPress/wordpress-develop' || + ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) + runs-on: ubuntu-24.04 + timeout-minutes: 5 + concurrency: + group: docs-preview-build-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + steps: + - name: Wait for build cancellation + run: ':' - cleanup: - name: Expire Code Reference preview - needs: quiesce-build - if: >- - github.event.action == 'closed' && - ( github.repository == 'WordPress/wordpress-develop' || - ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) - runs-on: ubuntu-24.04 - timeout-minutes: 15 - concurrency: - group: docs-preview-publish-${{ github.event.pull_request.head.repo.full_name }}-${{ github.event.pull_request.head.ref }} - queue: max - permissions: - actions: write - contents: write - pull-requests: write - steps: - - name: Check out trusted default-branch lifecycle code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.event.repository.default_branch }} - persist-credentials: false - show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + cleanup: + name: Expire Code Reference preview + needs: quiesce-build + if: >- + github.event.action == 'closed' && + ( github.repository == 'WordPress/wordpress-develop' || + ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) + runs-on: ubuntu-24.04 + timeout-minutes: 15 + concurrency: + group: docs-preview-publish-${{ github.event.pull_request.head.repo.full_name }}-${{ github.event.pull_request.head.ref }} + queue: max + permissions: + actions: write + contents: write + pull-requests: write + steps: + - name: Check out trusted default-branch lifecycle code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.repository.default_branch }} + persist-credentials: false + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - - name: Set up the pinned Node version - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: '20.20.2' + - name: Set up the pinned Node version + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version-file: '.github/docs-playground-preview/.nvmrc' - - name: Delete the pull request preview and caches - env: - DOCS_PREVIEW_STAGING: ${{ vars.DOCS_PREVIEW_STAGING }} - GITHUB_TOKEN: ${{ github.token }} - run: node .github/scripts/docs-playground-preview/lifecycle.mjs + - name: Delete the pull request preview and caches + env: + DOCS_PREVIEW_STAGING: ${{ vars.DOCS_PREVIEW_STAGING }} + GITHUB_TOKEN: ${{ github.token }} + run: node .github/scripts/docs-playground-preview/lifecycle.mjs diff --git a/.github/workflows/docs-playground-preview-publish.yml b/.github/workflows/docs-playground-preview-publish.yml index 49c4029a2c9da..1d278133e714a 100644 --- a/.github/workflows/docs-playground-preview-publish.yml +++ b/.github/workflows/docs-playground-preview-publish.yml @@ -1,109 +1,116 @@ name: Code Reference Playground Preview Publish on: # zizmor: ignore[dangerous-triggers] Only default-branch code validates and publishes the untrusted handoff. - workflow_run: - workflows: - - 'Code Reference Playground Preview Build' - types: - - completed + workflow_run: + workflows: + - 'Code Reference Playground Preview Build' + types: + - completed # A fork head branch identifies one trunk pull request even when GitHub omits the # pull_requests array. Runs queue instead of interrupting a publication halfway. concurrency: - group: docs-preview-publish-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} - queue: max + group: docs-preview-publish-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} + queue: max permissions: {} jobs: - publish: - name: Publish Code Reference snapshot - if: >- - github.event.workflow_run.event == 'pull_request' && - ( github.repository == 'WordPress/wordpress-develop' || - ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) - runs-on: ubuntu-24.04 - timeout-minutes: 30 - permissions: - actions: read - contents: write - pull-requests: write - steps: - - name: Check out trusted default-branch publisher code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.event.repository.default_branch }} - persist-credentials: false - show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + publish: + name: Publish Code Reference snapshot + # Every push to an unlabeled pull request completes a build run whose + # jobs all skipped. Failed builds still publish their report, so only + # the skipped conclusion is filtered out here. + if: >- + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion != 'skipped' && + ( github.repository == 'WordPress/wordpress-develop' || + ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + actions: read + contents: write + pull-requests: write + steps: + - name: Check out trusted default-branch publisher code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.repository.default_branch }} + persist-credentials: false + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - - name: Set up the pinned Node version - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: '20.20.2' + - name: Set up the pinned Node version + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version-file: '.github/docs-playground-preview/.nvmrc' - - name: Download the exact untrusted handoff - id: download - continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: docs-preview-handoff-${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }} - path: handoff - github-token: ${{ github.token }} - run-id: ${{ github.event.workflow_run.id }} - digest-mismatch: error + - name: Download the exact untrusted handoff + id: download + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: docs-preview-handoff-${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }} + path: handoff + github-token: ${{ github.token }} + run-id: ${{ github.event.workflow_run.id }} + digest-mismatch: error - - name: Validate and publish the handoff - # Fork pull_request jobs cannot read the base repository's Actions - # variables. The trusted publisher therefore applies enforcement - # after reporting the invalid candidate and preserving the live link. - # Administrator: enable enforcement in Settings > Secrets and variables > - # Actions > Variables by creating or updating DOCS_PREVIEW_ENFORCE to true. - env: - DOCS_PREVIEW_ENFORCE: ${{ vars.DOCS_PREVIEW_ENFORCE }} - DOCS_PREVIEW_STAGING: ${{ vars.DOCS_PREVIEW_STAGING }} - GITHUB_TOKEN: ${{ github.token }} - HANDOFF_DOWNLOAD_RESULT: ${{ steps.download.outcome }} - run: node .github/scripts/docs-playground-preview/publish.mjs handoff + - name: Validate and publish the handoff + # Fork pull_request jobs cannot read the base repository's Actions + # variables. The trusted publisher therefore applies enforcement + # after reporting the invalid candidate and preserving the live link. + # Administrator: enable enforcement in Settings > Secrets and variables > + # Actions > Variables by creating or updating DOCS_PREVIEW_ENFORCE to true. + env: + DOCS_PREVIEW_ENFORCE: ${{ vars.DOCS_PREVIEW_ENFORCE }} + DOCS_PREVIEW_STAGING: ${{ vars.DOCS_PREVIEW_STAGING }} + GITHUB_TOKEN: ${{ github.token }} + HANDOFF_DOWNLOAD_RESULT: ${{ steps.download.outcome }} + run: node .github/scripts/docs-playground-preview/publish.mjs pull-request handoff - publish-trunk: - name: Publish trunk Code Reference snapshot - if: >- - github.event.workflow_run.event == 'push' && - github.event.workflow_run.head_branch == 'trunk' && - ( github.repository == 'WordPress/wordpress-develop' || - ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) - runs-on: ubuntu-24.04 - timeout-minutes: 30 - permissions: - actions: read - contents: write - steps: - - name: Check out trusted default-branch publisher code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.event.repository.default_branch }} - persist-credentials: false - show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + publish-trunk: + name: Publish trunk Code Reference snapshot + if: >- + github.event.workflow_run.event == 'push' && + github.event.workflow_run.head_branch == 'trunk' && + ( github.repository == 'WordPress/wordpress-develop' || + ( github.repository == 'sirreal/wordpress-develop' && vars.DOCS_PREVIEW_STAGING == 'true' ) ) + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + actions: read + contents: write + steps: + - name: Check out trusted default-branch publisher code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.repository.default_branch }} + persist-credentials: false + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - - name: Set up the pinned Node version - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: '20.20.2' + - name: Set up the pinned Node version + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version-file: '.github/docs-playground-preview/.nvmrc' - - name: Download the exact trunk handoff - id: download - continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: docs-preview-trunk-handoff-${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }} - path: handoff - github-token: ${{ github.token }} - run-id: ${{ github.event.workflow_run.id }} - digest-mismatch: error + - name: Download the exact trunk handoff + id: download + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: docs-preview-trunk-handoff-${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }} + path: handoff + github-token: ${{ github.token }} + run-id: ${{ github.event.workflow_run.id }} + digest-mismatch: error - - name: Validate and publish the trunk handoff - env: - DOCS_PREVIEW_STAGING: ${{ vars.DOCS_PREVIEW_STAGING }} - GITHUB_TOKEN: ${{ github.token }} - HANDOFF_DOWNLOAD_RESULT: ${{ steps.download.outcome }} - run: node .github/scripts/docs-playground-preview/publish-trunk.mjs handoff + - name: Validate and publish the trunk handoff + # Administrator: enable enforcement in Settings > Secrets and variables > + # Actions > Variables by creating or updating DOCS_PREVIEW_ENFORCE to true. + env: + DOCS_PREVIEW_ENFORCE: ${{ vars.DOCS_PREVIEW_ENFORCE }} + DOCS_PREVIEW_STAGING: ${{ vars.DOCS_PREVIEW_STAGING }} + GITHUB_TOKEN: ${{ github.token }} + HANDOFF_DOWNLOAD_RESULT: ${{ steps.download.outcome }} + run: node .github/scripts/docs-playground-preview/publish.mjs trunk handoff diff --git a/.github/workflows/docs-playground-preview-tests.yml b/.github/workflows/docs-playground-preview-tests.yml new file mode 100644 index 0000000000000..69f30fc6755ca --- /dev/null +++ b/.github/workflows/docs-playground-preview-tests.yml @@ -0,0 +1,87 @@ +name: Code Reference Playground Preview Tests + +on: + # The Code Reference Playground preview tooling was introduced in WordPress 7.2. + # It is self-contained, so only its own files can break its tests. Pushes are + # filtered as well as pull requests because type checking installs the whole + # pinned tool tree. + push: + branches: + - trunk + - '[7-9].[0-9]' + paths: + # The preview implementation and its test suite. + - '.github/scripts/docs-playground-preview/**' + # The pinned npm, tool and TypeScript versions. + - '.github/docs-playground-preview/**' + # Confirm any changes to relevant workflow files. + - '.github/workflows/docs-playground-preview-tests.yml' + pull_request: + branches: + - trunk + - '[7-9].[0-9]' + paths: + - '.github/scripts/docs-playground-preview/**' + - '.github/docs-playground-preview/**' + - '.github/workflows/docs-playground-preview-tests.yml' + workflow_dispatch: + +# Cancels all previous workflow runs for pull requests that have not completed. +concurrency: + # The concurrency group contains the workflow name and the branch name for pull requests + # or the commit hash for any other events. + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true + +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + +jobs: + # Runs the Code Reference Playground preview script tests and type checking. + test: + name: Test the Code Reference preview tooling + runs-on: ubuntu-24.04 + permissions: + contents: read + timeout-minutes: 10 + if: | + github.repository == 'WordPress/wordpress-develop' || ( + github.event_name == 'pull_request' && ( + ! github.event.repository.private || + ! github.event.pull_request.draft || + contains( github.event.pull_request.labels.*.name, 'Draft Workflow Runs' ) + ) + ) + + defaults: + run: + working-directory: .github/docs-playground-preview + + steps: + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + persist-credentials: false + + - name: Set up Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version-file: '.github/docs-playground-preview/.nvmrc' + cache: npm + cache-dependency-path: '.github/docs-playground-preview/package-lock.json' + + # The tests import nothing from `node_modules`, and type checking needs only the + # pinned TypeScript compiler plus the Node type definitions resolved from the + # dependency tree, so no install script output is used by either command. + - name: Install npm dependencies + run: npm ci --ignore-scripts + + - name: Run the preview script tests + run: npm test + + # Runs even when the tests fail so that a single run reports both problems. + - name: Run type checking for the preview scripts + if: ${{ ! cancelled() }} + run: npm run typecheck From 2694113c7bae4b3b77b564cd15cb3eef78eb3222 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Mon, 7 Sep 2026 14:54:13 +0400 Subject: [PATCH 50/51] Staging: probe sentence in the wp_parse_args docblock --- src/wp-includes/functions.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 54b3eb30cdfe9..57c3a2cf161f4 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -5001,6 +5001,9 @@ function smilies_init() { * This function is used throughout WordPress to allow for both string or array * to be merged into another array. * + * STAGING PROBE: this sentence exists only to confirm the Code Reference + * preview renders the pull request's own docblock, not trunk's. + * * @since 2.2.0 * @since 2.3.0 `$args` can now also be an object. * From f0ad54004db0c6a9e944182256f15472a352af24 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 8 Sep 2026 14:12:54 +0400 Subject: [PATCH 51/51] Staging: touch a test file to exercise the tests workflow --- .github/scripts/docs-playground-preview/test/parser.test.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/scripts/docs-playground-preview/test/parser.test.mjs b/.github/scripts/docs-playground-preview/test/parser.test.mjs index b38bb98e5fbc0..e6319d62046aa 100644 --- a/.github/scripts/docs-playground-preview/test/parser.test.mjs +++ b/.github/scripts/docs-playground-preview/test/parser.test.mjs @@ -1,3 +1,4 @@ +// Staging: touched to exercise the tests workflow and the stale-comment path. import assert from 'node:assert/strict'; import { mkdir, mkdtemp, readFile, symlink, writeFile } from 'node:fs/promises'; import os from 'node:os';