From b3228d993b7ef0d8b76316e237144e3dd9f1c2ff Mon Sep 17 00:00:00 2001 From: Christian Theilemann Date: Wed, 12 Oct 2022 11:53:28 -0700 Subject: [PATCH] fix and improve docker image release job (#4989) remove dependency on often breaking "wait-on-check-action" github action and simply handle waiting for the image in the release script itself --- .../workflows/copy-images-to-dockerhub.yaml | 21 +- docker/README.md | 9 +- docker/release-images.mjs | 138 +++++++ docker/release-images.sh | 65 ---- package.json | 8 + pnpm-lock.yaml | 345 ++++++++++++++++++ 6 files changed, 505 insertions(+), 81 deletions(-) create mode 100755 docker/release-images.mjs delete mode 100755 docker/release-images.sh create mode 100644 package.json create mode 100644 pnpm-lock.yaml diff --git a/.github/workflows/copy-images-to-dockerhub.yaml b/.github/workflows/copy-images-to-dockerhub.yaml index 99e8204cc8795..7ae02d5b62aa7 100644 --- a/.github/workflows/copy-images-to-dockerhub.yaml +++ b/.github/workflows/copy-images-to-dockerhub.yaml @@ -1,3 +1,4 @@ +name: Release Images on: workflow_call: inputs: @@ -18,20 +19,7 @@ permissions: id-token: write #required for GCP Workload Identity federation jobs: - wait-for-images-to-have-been-built: - runs-on: ubuntu-latest - steps: - - name: Wait for images to have been built - timeout-minutes: 30 - uses: lewagon/wait-on-check-action@0179dfc359f90a703c41240506f998ee1603f9ea # pin@v1.0.0 - with: - ref: ${{ github.ref }} - check-name: "rust-images / rust-all" # only copy the release images to dockerhub - repo-token: ${{ secrets.GITHUB_TOKEN }} - wait-interval: 30 # wait 30 seconds between making polling API calls, default is 10 but we ran in the past into rate-limiting issues with too frequent polling - copy-images: - needs: wait-for-images-to-have-been-built runs-on: ubuntu-latest steps: - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3 @@ -54,10 +42,15 @@ jobs: username: ${{ secrets.ENV_DOCKERHUB_USERNAME }} password: ${{ secrets.ENV_DOCKERHUB_PASSWORD }} + - uses: pnpm/action-setup@537643d491d20c2712d11533497cb47b2d0eb9d5 # pin https://github.com/pnpm/action-setup/releases/tag/v2.2.3 + with: + version: 7.13.4 + - name: Release Images env: + FORCE_COLOR: 3 # Force color output as per https://github.com/google/zx#using-github-actions GIT_SHA: ${{ github.sha }} GCP_DOCKER_ARTIFACT_REPO: ${{ secrets.GCP_DOCKER_ARTIFACT_REPO }} AWS_ACCOUNT_ID: ${{ secrets.AWS_ECR_ACCOUNT_NUM }} IMAGE_TAG_PREFIX: ${{ inputs.image_tag_prefix }} - run: ./docker/release-images.sh + run: ./docker/release-images.mjs --wait-for-image-seconds=3600 diff --git a/docker/README.md b/docker/README.md index 5beb4b36d6dd1..86567f0265d25 100644 --- a/docker/README.md +++ b/docker/README.md @@ -16,5 +16,10 @@ For using the images, look in the `compose` directory. The `builder` target is the one that builds the rust binaries and is the most expensive. Its output is used by all the other targets that follow. The `builder` itself takes in a few build arguments. Most are build metadata, such as `GIT_SHA` and `GIT_BRANCH`, but others change the build entirely, such as cargo flags `PROFILE` and `FEATURES`. Arguments like these necessitate a different cache to prevent clobbering. The general strategy is to use image tags and cache keys that use these variables. An example image tag might be: -* `performance_failpoints_` -- `performance` profile with `failpoints` feature -* `` -- default `release` profile with no additional features + +- `performance_failpoints_` -- `performance` profile with `failpoints` feature +- `` -- default `release` profile with no additional features + +## Release Images + +Image releasing is done automatically using corresponding github workflow jobs or manually using the `docker/release-images.mjs` script. diff --git a/docker/release-images.mjs b/docker/release-images.mjs new file mode 100755 index 0000000000000..8bb183196c1f6 --- /dev/null +++ b/docker/release-images.mjs @@ -0,0 +1,138 @@ +#!/usr/bin/env -S node + +// This script releases the main aptos docker images to docker hub. +// It does so by copying the images from aptos GCP artifact registry to docker hub. +// It also copies the release tags to GCP Artifact Registry and AWS ECR. +// +// Usually it's run in CI, but you can also run it locally in emergency situations, assuming you have the right credentials. +// Before you run this locally, check one more time whether you can trigger a CI build instead which is usually easier and safer. +// You can do so via the Github UI or CLI: +// E.g: gh workflow run copy-images-to-dockerhub.yaml --ref -F image_tag_prefix=release_testing +// +// If that doesn't work for you, you can run this script locally: +// +// Prerequisites when running locally: +// 1. Tools: +// - docker +// - gcloud +// - aws cli +// - node (node.js) +// - crane - https://github.com/google/go-containerregistry/tree/main/cmd/crane#installation +// - pnpm - https://pnpm.io/installation +// 2. docker login - with authorization to push to the `aptoslabs` org +// 3. gcloud auth configure-docker us-west1-docker.pkg.dev +// 4. gcloud auth login --update-adc +// 5. aws-mfa +// +// Once you have all prerequisites fulfilled, you can run this script via: +// GIT_SHA=${{ github.sha }} GCP_DOCKER_ARTIFACT_REPO="${{ secrets.GCP_DOCKER_ARTIFACT_REPO }}" AWS_ACCOUNT_ID="${{ secrets.AWS_ECR_ACCOUNT_NUM }}" IMAGE_TAG_PREFIX="${{ inputs.image_tag_prefix }}" ./docker/release_images.sh --wait-for-image-seconds=1800 + +const IMAGES_TO_RELEASE = ["validator", "forge", "tools", "faucet", "node-checker"]; + +import { execSync } from "node:child_process"; +import { dirname } from "node:path"; +import { chdir } from "node:process"; +import { promisify } from "node:util"; +const sleep = promisify(setTimeout); + +chdir(dirname(process.argv[1]) + "/.."); // change workdir to the root of the repo +// install repo pnpm dependencies +execSync("pnpm install --frozen-lockfile", { stdio: "inherit" }); +await import("zx/globals"); + +const REQUIRED_ARGS = ["GIT_SHA", "GCP_DOCKER_ARTIFACT_REPO", "AWS_ACCOUNT_ID", "IMAGE_TAG_PREFIX"]; +const OPTIONAL_ARGS = ["WAIT_FOR_IMAGE_SECONDS"]; + +const parsedArgs = {}; + +for (const arg of REQUIRED_ARGS) { + const argValue = argv[arg.toLowerCase().replaceAll("_", "-")] ?? process.env[arg]; + if (!argValue) { + console.error(chalk.red(`ERROR: Missing required argument or environment variable: ${arg}`)); + process.exit(1); + } + parsedArgs[arg] = argValue; +} + +for (const arg of OPTIONAL_ARGS) { + const argValue = argv[arg.toLowerCase().replaceAll("_", "-")] ?? process.env[arg]; + parsedArgs[arg] = argValue; +} + +let crane; + +if (process.env.CI === "true") { + console.log("installing crane automatically in CI"); + await $`curl -sL https://github.com/google/go-containerregistry/releases/download/v0.11.0/go-containerregistry_Linux_x86_64.tar.gz > crane.tar.gz`; + await $`tar -xf crane.tar.gz`; + const sha = (await $`shasum -a 256 ./crane | awk '{ print $1 }'`).toString().trim(); + if (sha !== "2af448965b5feb6c315f4c8e79b18bd15f8c916ead0396be3962baf2f0c815bf") { + console.error(chalk.red(`ERROR: sha256 mismatch for crane- got: ${sha}`)); + process.exit(1); + } + crane = "./crane"; +} else { + if ((await $`command -v cranes`.exitCode) !== 0) { + console.log( + chalk.red( + "ERROR: could not find crane binary in PATH - follow https://github.com/google/go-containerregistry/tree/main/cmd/crane#installation to install", + ), + ); + process.exit(1); + } + crane = "crane"; +} + +const TARGET_REGISTRIES = [ + parsedArgs.GCP_DOCKER_ARTIFACT_REPO, + "docker.io/aptoslabs", + `${parsedArgs.AWS_ACCOUNT_ID}.dkr.ecr.us-west-2.amazonaws.com/aptos`, +]; + +// default 10 seconds +parsedArgs.WAIT_FOR_IMAGE_SECONDS = parseInt(parsedArgs.WAIT_FOR_IMAGE_SECONDS ?? 10, 10); + +for (const image of IMAGES_TO_RELEASE) { + for (const targetRegistry of TARGET_REGISTRIES) { + const imageSource = `${parsedArgs.GCP_DOCKER_ARTIFACT_REPO}/${image}:${parsedArgs.GIT_SHA}`; + const imageTarget = `${targetRegistry}/${image}:${parsedArgs.IMAGE_TAG_PREFIX}`; + console.info(chalk.green(`INFO: copying ${imageSource} to ${imageTarget}`)); + await waitForImageToBecomeAvailable(imageSource, parsedArgs.WAIT_FOR_IMAGE_SECONDS); + await $`${crane} copy ${imageSource} ${imageTarget}`; + await $`${crane} copy ${imageSource} ${imageTarget + "_" + parsedArgs.GIT_SHA}`; + } +} + +async function waitForImageToBecomeAvailable(imageToWaitFor, waitForImageSeconds) { + const WAIT_TIME_IN_BETWEEN_ATTEMPTS = 10000; // 10 seconds in ms + const startTimeMs = Date.now(); + function timeElapsedSeconds() { + return (Date.now() - startTimeMs) / 1000; + } + while (timeElapsedSeconds() < waitForImageSeconds) { + try { + await $`${crane} manifest ${imageToWaitFor}`; + console.info(chalk.green(`INFO: image ${imageToWaitFor} is available`)); + return; + } catch (e) { + if (e.exitCode === 1 && e.stderr.includes("MANIFEST_UNKNOWN")) { + console.log( + chalk.yellow( + // prettier-ignore + `WARN: Image ${imageToWaitFor} not available yet - waiting ${ WAIT_TIME_IN_BETWEEN_ATTEMPTS / 1000 } seconds to try again. Time elapsed: ${timeElapsedSeconds().toFixed( 0, )} seconds. Max wait time: ${waitForImageSeconds} seconds`, + ), + ); + await sleep(WAIT_TIME_IN_BETWEEN_ATTEMPTS); + } else { + console.error(chalk.red(e.stderr ?? e)); + process.exit(1); + } + } + } + console.error( + chalk.red( + `ERROR: timed out after ${waitForImageSeconds} seconds waiting for image to become available: ${imageToWaitFor}`, + ), + ); + process.exit(1); +} diff --git a/docker/release-images.sh b/docker/release-images.sh deleted file mode 100755 index 289c694a9bd7e..0000000000000 --- a/docker/release-images.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env bash - -# This script releases our aptos main images to docker hub. -# It does so by copying the images from the aptos GCP artifact registry to docker hub. -# It also copies the release tags to GCP Artifact Registry and AWS ECR. -# Usually it's run in CI, but you can also run it locally in emergency situations, assuming you have the right credentials -# Prerequisites when running locally: -# 1. `docker login` with authorization to push to the `aptoslabs` org -# 2. gcloud auth login --update-adc -# 3. aws-mfa -# Run with: -# GIT_SHA=${{ github.sha }} GCP_DOCKER_ARTIFACT_REPO="${{ secrets.GCP_DOCKER_ARTIFACT_REPO }}" AWS_ACCOUNT_ID="${{ secrets.AWS_ECR_ACCOUNT_NUM }}" IMAGE_TAG_PREFIX="${{ inputs.image_tag_prefix }}" ./docker/release_images.sh - -REQUIRED_VARS=( - GIT_SHA - GCP_DOCKER_ARTIFACT_REPO - AWS_ACCOUNT_ID - IMAGE_TAG_PREFIX -) - -for VAR in "${REQUIRED_VARS[@]}"; do - if [ -z "${!VAR}" ]; then - echo "missing required env var: $VAR" - exit 1 - fi -done - -if [ "$CI" == "true" ]; then - echo "installing crane automatically in CI" - curl -sL https://github.com/google/go-containerregistry/releases/download/v0.11.0/go-containerregistry_Linux_x86_64.tar.gz > crane.tar.gz - tar -xf crane.tar.gz - sha=$(shasum -a 256 ./crane | awk '{ print $1 }') - [ "$sha" != "2af448965b5feb6c315f4c8e79b18bd15f8c916ead0396be3962baf2f0c815bf" ] && echo "shasum mismatch - got: $sha" && exit 1 - crane="./crane" -else - if ! [ -x "$(command -v crane)" ]; then - echo "could not find crane binary in PATH - follow https://github.com/google/go-containerregistry/tree/main/cmd/crane#installation to install" - exit 1 - fi - crane=$(which crane) -fi - -set -ex - -IMAGES=( - validator - forge - tools - faucet - indexer - node-checker -) - -TARGET_REGISTRIES=( - "$GCP_DOCKER_ARTIFACT_REPO" - "docker.io/aptoslabs" - "$AWS_ACCOUNT_NUM.dkr.ecr.us-west-2.amazonaws.com/aptos" -) - -for IMAGE in "${IMAGES[@]}"; do - for TARGET_REGISTRY in "${TARGET_REGISTRIES[@]}"; do - $crane copy "$GCP_DOCKER_ARTIFACT_REPO/$IMAGE:$GIT_SHA" "$TARGET_REGISTRY/$IMAGE:$IMAGE_TAG_PREFIX" - $crane copy "$GCP_DOCKER_ARTIFACT_REPO/$IMAGE:$GIT_SHA" "$TARGET_REGISTRY/$IMAGE:${IMAGE_TAG_PREFIX}_${GIT_SHA}" - done -done diff --git a/package.json b/package.json new file mode 100644 index 0000000000000..dc0073bd10c3e --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "description": "This is a pure dependency package.json which installs dependencies for js utility scripts used in the repo", + "private": true, + "type": "module", + "devDependencies": { + "zx": "^7.1.1" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000000000..fb163d63d1ffd --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,345 @@ +lockfileVersion: 5.4 + +specifiers: + zx: ^7.1.1 + +devDependencies: + zx: 7.1.1 + +packages: + + /@nodelib/fs.scandir/2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat/2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk/1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.13.0 + dev: true + + /@types/fs-extra/9.0.13: + resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} + dependencies: + '@types/node': 18.8.4 + dev: true + + /@types/minimist/1.2.2: + resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} + dev: true + + /@types/node/18.8.4: + resolution: {integrity: sha512-WdlVphvfR/GJCLEMbNA8lJ0lhFNBj4SW3O+O5/cEGw9oYrv0al9zTwuQsq+myDUXgNx2jgBynoVgZ2MMJ6pbow==} + dev: true + + /@types/ps-tree/1.1.2: + resolution: {integrity: sha512-ZREFYlpUmPQJ0esjxoG1fMvB2HNaD3z+mjqdSosZvd3RalncI9NEur73P8ZJz4YQdL64CmV1w0RuqoRUlhQRBw==} + dev: true + + /@types/which/2.0.1: + resolution: {integrity: sha512-Jjakcv8Roqtio6w1gr0D7y6twbhx6gGgFGF5BLwajPpnOIOxFkakFhCq+LmyyeAz7BX6ULrjBOxdKaCDy+4+dQ==} + dev: true + + /braces/3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /chalk/5.1.0: + resolution: {integrity: sha512-56zD4khRTBoIyzUYAFgDDaPhUMN/fC/rySe6aZGqbj/VWiU2eI3l6ZLOtYGFZAV5v02mwPjtpzlrOveJiz5eZQ==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: true + + /data-uri-to-buffer/4.0.0: + resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==} + engines: {node: '>= 12'} + dev: true + + /dir-glob/3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true + + /duplexer/0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + dev: true + + /event-stream/3.3.4: + resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} + dependencies: + duplexer: 0.1.2 + from: 0.1.7 + map-stream: 0.1.0 + pause-stream: 0.0.11 + split: 0.3.3 + stream-combiner: 0.0.4 + through: 2.3.8 + dev: true + + /fast-glob/3.2.12: + resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fastq/1.13.0: + resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} + dependencies: + reusify: 1.0.4 + dev: true + + /fetch-blob/3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.2.1 + dev: true + + /fill-range/7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /formdata-polyfill/4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + dependencies: + fetch-blob: 3.2.0 + dev: true + + /from/0.1.7: + resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} + dev: true + + /fs-extra/10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + dependencies: + graceful-fs: 4.2.10 + jsonfile: 6.1.0 + universalify: 2.0.0 + dev: true + + /glob-parent/5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /globby/13.1.2: + resolution: {integrity: sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + dir-glob: 3.0.1 + fast-glob: 3.2.12 + ignore: 5.2.0 + merge2: 1.4.1 + slash: 4.0.0 + dev: true + + /graceful-fs/4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + dev: true + + /ignore/5.2.0: + resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} + engines: {node: '>= 4'} + dev: true + + /is-extglob/2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-glob/4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-number/7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /isexe/2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + + /jsonfile/6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + dependencies: + universalify: 2.0.0 + optionalDependencies: + graceful-fs: 4.2.10 + dev: true + + /map-stream/0.1.0: + resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} + dev: true + + /merge2/1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /micromatch/4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + + /minimist/1.2.7: + resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} + dev: true + + /node-domexception/1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + dev: true + + /node-fetch/3.2.10: + resolution: {integrity: sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + data-uri-to-buffer: 4.0.0 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + dev: true + + /path-type/4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true + + /pause-stream/0.0.11: + resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} + dependencies: + through: 2.3.8 + dev: true + + /picomatch/2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /ps-tree/1.2.0: + resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} + engines: {node: '>= 0.10'} + hasBin: true + dependencies: + event-stream: 3.3.4 + dev: true + + /queue-microtask/1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /reusify/1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + + /run-parallel/1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /slash/4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} + dev: true + + /split/0.3.3: + resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} + dependencies: + through: 2.3.8 + dev: true + + /stream-combiner/0.0.4: + resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} + dependencies: + duplexer: 0.1.2 + dev: true + + /through/2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + dev: true + + /to-regex-range/5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /universalify/2.0.0: + resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} + engines: {node: '>= 10.0.0'} + dev: true + + /web-streams-polyfill/3.2.1: + resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} + engines: {node: '>= 8'} + dev: true + + /which/2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /yaml/2.1.3: + resolution: {integrity: sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==} + engines: {node: '>= 14'} + dev: true + + /zx/7.1.1: + resolution: {integrity: sha512-5YlTO2AJ+Ku2YuZKSSSqnUKuagcM/f/j4LmHs15O84Ch80Z9gzR09ZK3gR7GV+rc8IFpz2H/XNFtFVmj31yrZA==} + engines: {node: '>= 16.0.0'} + hasBin: true + dependencies: + '@types/fs-extra': 9.0.13 + '@types/minimist': 1.2.2 + '@types/node': 18.8.4 + '@types/ps-tree': 1.1.2 + '@types/which': 2.0.1 + chalk: 5.1.0 + fs-extra: 10.1.0 + globby: 13.1.2 + minimist: 1.2.7 + node-fetch: 3.2.10 + ps-tree: 1.2.0 + which: 2.0.2 + yaml: 2.1.3 + dev: true