Explorar o código

ci: Refactor scripts (#60)

* [wip]: Add new scripts

* [wip]: Use matric strategy

* [wip]: Add setup action

* [wip]: Tweak output

* [wip]: Debug output

* [wip]: Fix members parsing

* [wip]: Fix matrix strategy

* [wip]: All process steps

* [wip]: Add toolchains

* [wip]: Add CI env variables

* [wip]: Remove nothrow

* [wip]: Filter changes

* [wip]: Add audit step

* [wip]: Update names

* [wip]: Add semver checks

* [wip]: Add publish workflow

* [wip]: Debug semver check

* [wip]: Refactor publish workflow

* [wip]: Tweaks

* [wip]: Fix set version

* [wip]: Install cargo-edit

* [wip]: Refactor

* [wip]: Fix ci variables

* [wip]: Fix import

* [wip]: Install solana

* [wip]: Fix commands

* Fix formatting

* Remove detect changes step

* Relax clippy check

* Review comments

* Add crate/version output

* Simplify range
Fernando Otero hai 9 meses
pai
achega
f2a765b8d3

+ 101 - 0
.github/actions/setup/action.yml

@@ -0,0 +1,101 @@
+name: Setup environment
+
+inputs:
+  cargo-cache-key:
+    description: The key to cache cargo dependencies. Skips cargo caching if not provided.
+    required: false
+  toolchain:
+    description: Rust toolchain to install. Comma-separated string of [`build`, `format`, `lint`, `test`].
+    required: false
+  components:
+    description: Cargo components to install. Comma-separated string of [`audit`, `hack``, `release`, `semver-checks].
+    required: false
+  solana:
+    description: Install Solana if `true`. Defaults to `false`.
+    required: false
+
+runs:
+  using: 'composite'
+  steps:
+    - name: Setup pnpm
+      uses: pnpm/action-setup@v3
+
+    - name: Setup Node.js
+      uses: actions/setup-node@v4
+      with:
+        node-version: 20
+        cache: 'pnpm'
+
+    - name: Install Dependencies
+      run: pnpm install --frozen-lockfile
+      shell: bash
+
+    - name: Set Environment Variables
+      shell: bash
+      run: pnpm tsx ./scripts/setup/ci.mts
+
+    - name: Install Rust 'build' Toolchain
+      if: ${{ contains(inputs.toolchain, 'build') }}
+      uses: dtolnay/rust-toolchain@master
+      with:
+        toolchain: ${{ env.TOOLCHAIN_BUILD }}
+
+    - name: Install Rust 'format' Toolchain
+      if: ${{ contains(inputs.toolchain, 'format') }}
+      uses: dtolnay/rust-toolchain@master
+      with:
+        toolchain: ${{ env.TOOLCHAIN_FORMAT }}
+        components: rustfmt
+
+    - name: Install Rust 'lint' Toolchain
+      if: ${{ contains(inputs.toolchain, 'lint') }}
+      uses: dtolnay/rust-toolchain@master
+      with:
+        toolchain: ${{ env.TOOLCHAIN_LINT }}
+        components: clippy
+
+    - name: Install Rust 'test' Toolchain
+      if: ${{ contains(inputs.toolchain, 'test') }}
+      uses: dtolnay/rust-toolchain@master
+      with:
+        toolchain: ${{ env.TOOLCHAIN_TEST }}
+
+    - name: Install Solana
+      if: ${{ inputs.solana == 'true' }}
+      uses: solana-program/actions/install-solana@v1
+      with:
+        version: ${{ env.SOLANA_VERSION }}
+        cache: true
+
+    - name: Install 'cargo-audit'
+      if: ${{ contains(inputs.components, 'audit') }}
+      shell: bash
+      run: cargo install cargo-audit
+
+    - name: Install 'cargo-hack'
+      if: ${{ contains(inputs.components, 'hack') }}
+      shell: bash
+      run: cargo install cargo-hack
+
+    - name: Install 'cargo-release'
+      if: ${{ contains(inputs.components, 'release') }}
+      shell: bash
+      run: cargo install cargo-release
+
+    - name: Install 'cargo-semver-checks'
+      if: ${{ contains(inputs.components, 'semver-checks') }}
+      shell: bash
+      run: cargo install cargo-semver-checks
+
+    - name: Cache Cargo Dependencies
+      if: ${{ inputs.cargo-cache-key && !inputs.cargo-cache-fallback-key }}
+      uses: actions/cache@v4
+      with:
+        path: |
+          ~/.cargo/bin/
+          ~/.cargo/registry/index/
+          ~/.cargo/registry/cache/
+          ~/.cargo/git/db/
+          target/
+        key: ${{ runner.os }}-${{ inputs.cargo-cache-key }}-${{ hashFiles('**/Cargo.lock') }}
+        restore-keys: ${{ runner.os }}-${{ inputs.cargo-cache-key }}

+ 59 - 47
.github/workflows/main.yml

@@ -6,65 +6,77 @@ on:
   pull_request:
 
 env:
-  RUST_VERSION: 1.78.0
-  SOLANA_VERSION: 1.18.20
-  CARGO_CACHE: |
-    ~/.cargo/bin/
-    ~/.cargo/registry/index/
-    ~/.cargo/registry/cache/
-    ~/.cargo/git/db/
-    target/
+  CACHE: true
 
 jobs:
-  lint:
-    name: Lint
+  audit:
+    name: Audit Dependencies
     runs-on: ubuntu-latest
     steps:
       - name: Git checkout
         uses: actions/checkout@v4
-      - name: Install components
-        uses: dtolnay/rust-toolchain@master
+
+      - name: Setup Environment
+        uses: ./.github/actions/setup
         with:
-          components: clippy, rustfmt
-          toolchain: ${{ env.RUST_VERSION }}
-      - name: Formatting
-        run: cargo fmt --all --check
-      - name: Clippy
-        run: cargo clippy --all-targets --all-features --no-deps
-
-  build:
-    name: Build
-    needs: lint
+          cargo-cache-key: cargo-audit
+          components: audit
+
+      - name: cargo-audit
+        run: pnpm audit
+
+  filter:
+    name: Filter Workspace
     runs-on: ubuntu-latest
+    needs: audit
+    outputs:
+      members: ${{ steps.filter.outputs.members }}
     steps:
-      - name: Git checkout
+      - name: Git Checkout
         uses: actions/checkout@v4
-      - name: Install Solana
-        uses: nifty-oss/actions/install-solana@v1
-        with:
-          version: ${{ env.SOLANA_VERSION }}
-          cache: true
-      - name: Cache cargo dependencies
-        uses: actions/cache@v4
+
+      - name: Setup Environment
+        uses: ./.github/actions/setup
         with:
-          path: ${{ env.CARGO_CACHE }}
-          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
-          restore-keys: ${{ runner.os }}-cargo
-      - name: Build
-        run: cargo build --all-targets --all-features
-
-  test:
-    name: Test
-    needs: lint
+          cargo-cache-key: cargo-filter-workspace
+
+      - name: Filter
+        id: filter
+        run: pnpm tsx ./scripts/setup/members.mts
+
+  process:
+    name: Crate
+    needs: filter
     runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        member: ${{ fromJson(needs.filter.outputs.members) }}
     steps:
-      - name: Git checkout
+      - name: Git Checkout
         uses: actions/checkout@v4
-      - name: Cache cargo dependencies
-        uses: actions/cache@v4
+
+      - name: Setup Environment
+        uses: ./.github/actions/setup
         with:
-          path: ${{ env.CARGO_CACHE }}
-          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
-          restore-keys: ${{ runner.os }}-cargo
-      - name: Build
-        run: cargo test --all-features
+          cargo-cache-key: cargo-${{ matrix.member }}
+          toolchain: build, format, lint, test
+          components: hack
+          solana: true
+
+      - name: fmt
+        run: pnpm format ${{ matrix.member }}
+
+      - name: clippy
+        run: pnpm clippy ${{ matrix.member }}
+
+      - name: cargo-doc
+        run: pnpm doc ${{ matrix.member }}
+
+      - name: cargo-hack
+        run: pnpm hack ${{ matrix.member }}
+
+      - name: build-sbf
+        run: pnpm build-sbf ${{ matrix.member }}
+
+      - name: test
+        run: pnpm test ${{ matrix.member }}

+ 60 - 19
.github/workflows/publish.yml

@@ -6,53 +6,94 @@ on:
       crate:
         description: Crate
         required: true
-        default: pinocchio
+        default: sdk/pinocchio
         type: choice
         options:
-          - pinocchio
-          - pubkey
+          - programs/associated-token-account
+          - programs/system
+          - programs/token
+          - sdk/log/crate
+          - sdk/log/macro
+          - sdk/pinocchio
+          - sdk/pubkey
+      level:
+        description: Level
+        required: true
+        default: patch
+        type: choice
+        options:
+          - patch
+          - minor
+          - major
       dry_run:
         description: Dry run
         required: true
         default: true
         type: boolean
+      create_release:
+        description: Create a GitHub release
+        required: true
+        type: boolean
+        default: true
 
 env:
-  RUST_VERSION: 1.78.0
-  SOLANA_VERSION: 1.18.20
-  CARGO_CACHE: |
-    ~/.cargo/bin/
-    ~/.cargo/registry/index/
-    ~/.cargo/registry/cache/
-    ~/.cargo/git/db/
-    target/
+  CACHE: true
 
 jobs:
   publish_release:
     name: Publish
     runs-on: ubuntu-latest
     steps:
+      - name: Ensure CARGO_REGISTRY_TOKEN variable is set
+        env:
+          token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
+        if: ${{ env.token == '' }}
+        run: |
+          echo "The CARGO_REGISTRY_TOKEN secret variable is not set"
+          echo "Go to \"Settings\" -> \"Secrets and variables\" -> \"Actions\" -> \"New repository secret\"."
+          exit 1
+
       - name: Git checkout
         uses: actions/checkout@v4
 
-      - name: Install Rust
-        uses: dtolnay/rust-toolchain@master
+      - name: Setup Environment
+        uses: ./.github/actions/setup
         with:
-          toolchain: stable
+          cargo-cache-key: cargo-publish
+          toolchain: test
+          components: semver-checks
+          solana: true
+
+      - name: Build
+        run: pnpm build-sbf ${{ inputs.crate }}
+
+      - name: Test
+        run: pnpm test ${{ inputs.crate }}
+
+      - name: Set Git Author
+        run: |
+          git config --global user.email "github-actions@github.com"
+          git config --global user.name "github-actions"
 
       - name: Check semver
-        uses: obi1kenobi/cargo-semver-checks-action@v2
+        run: |
+          pnpm semver ${{ inputs.crate }} --release-type ${{ inputs.level }}
 
-      - name: Publish crate
+      - name: Publish Crate
+        id: publish
         env:
           CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
         run: |
-          MANIFEST="./sdk/${{ inputs.crate }}/Cargo.toml"
-
           if [ "${{ inputs.dry_run }}" == "true" ]; then
             OPTIONS="--dry-run"
           else
             OPTIONS=""
           fi
 
-          cargo publish --manifest-path $MANIFEST $OPTIONS
+          pnpm tsx ./scripts/publish.mts ${{ inputs.crate }} ${{ inputs.level }} $OPTIONS
+
+      - name: Create GitHub release
+        if: github.event.inputs.dry_run != 'true' && github.event.inputs.create_release == 'true'
+        uses: ncipollo/release-action@v1
+        with:
+          tag: ${{ steps.publish.outputs.crate }}@v${{ steps.publish.outputs.version }}

+ 1 - 0
.gitignore

@@ -1 +1,2 @@
+/node_modules
 /target

+ 9 - 0
.prettierrc

@@ -0,0 +1,9 @@
+{
+  "semi": true,
+  "singleQuote": true,
+  "trailingComma": "es5",
+  "useTabs": false,
+  "tabWidth": 2,
+  "arrowParens": "always",
+  "printWidth": 80
+}

+ 11 - 2
Cargo.toml

@@ -17,5 +17,14 @@ repository = "https://github.com/anza-xyz/pinocchio"
 
 [workspace.dependencies]
 five8_const = "0.1.3"
-pinocchio = { path = "sdk/pinocchio", version = ">= 0.6, <= 0.7" }
-pinocchio-pubkey = { path = "sdk/pubkey", version = "0.2.1" }
+pinocchio = { path = "sdk/pinocchio", version = "0.7" }
+pinocchio-pubkey = { path = "sdk/pubkey", version = "0.2" }
+
+[workspace.metadata.cli]
+solana = "2.1.0"
+
+[workspace.metadata.toolchains]
+build = "1.81.0"
+format = "nightly-2024-08-08"
+lint = "nightly-2024-08-08"
+test = "1.81.0"

+ 24 - 0
package.json

@@ -0,0 +1,24 @@
+{
+  "private": true,
+  "scripts": {
+    "audit": "tsx ./scripts/audit.mjs",
+    "build-sbf": "tsx ./scripts/build-sbf.mjs",
+    "clippy": "tsx ./scripts/clippy.mjs",
+    "doc": "tsx ./scripts/doc.mjs",
+    "format": "tsx ./scripts/format.mjs",
+    "hack": "tsx ./scripts/hack.mjs",
+    "lint": "tsx ./scripts/lint.mjs",
+    "semver": "tsx ./scripts/semver.mjs",
+    "test": "tsx ./scripts/test.mjs"
+  },
+  "devDependencies": {
+    "@iarna/toml": "^2.2.5",
+    "tsx": "^4.19.2",
+    "typescript": "^5.5.2",
+    "zx": "^7.2.3"
+  },
+  "engines": {
+    "node": ">=v20.0.0"
+  },
+  "packageManager": "pnpm@9.1.0"
+}

+ 737 - 0
pnpm-lock.yaml

@@ -0,0 +1,737 @@
+lockfileVersion: '9.0'
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false
+
+importers:
+
+  .:
+    devDependencies:
+      '@iarna/toml':
+        specifier: ^2.2.5
+        version: 2.2.5
+      tsx:
+        specifier: ^4.19.2
+        version: 4.19.2
+      typescript:
+        specifier: ^5.5.2
+        version: 5.7.3
+      zx:
+        specifier: ^7.2.3
+        version: 7.2.3
+
+packages:
+
+  '@esbuild/aix-ppc64@0.23.1':
+    resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==}
+    engines: {node: '>=18'}
+    cpu: [ppc64]
+    os: [aix]
+
+  '@esbuild/android-arm64@0.23.1':
+    resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [android]
+
+  '@esbuild/android-arm@0.23.1':
+    resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==}
+    engines: {node: '>=18'}
+    cpu: [arm]
+    os: [android]
+
+  '@esbuild/android-x64@0.23.1':
+    resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [android]
+
+  '@esbuild/darwin-arm64@0.23.1':
+    resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@esbuild/darwin-x64@0.23.1':
+    resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [darwin]
+
+  '@esbuild/freebsd-arm64@0.23.1':
+    resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [freebsd]
+
+  '@esbuild/freebsd-x64@0.23.1':
+    resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@esbuild/linux-arm64@0.23.1':
+    resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [linux]
+
+  '@esbuild/linux-arm@0.23.1':
+    resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==}
+    engines: {node: '>=18'}
+    cpu: [arm]
+    os: [linux]
+
+  '@esbuild/linux-ia32@0.23.1':
+    resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==}
+    engines: {node: '>=18'}
+    cpu: [ia32]
+    os: [linux]
+
+  '@esbuild/linux-loong64@0.23.1':
+    resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==}
+    engines: {node: '>=18'}
+    cpu: [loong64]
+    os: [linux]
+
+  '@esbuild/linux-mips64el@0.23.1':
+    resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==}
+    engines: {node: '>=18'}
+    cpu: [mips64el]
+    os: [linux]
+
+  '@esbuild/linux-ppc64@0.23.1':
+    resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==}
+    engines: {node: '>=18'}
+    cpu: [ppc64]
+    os: [linux]
+
+  '@esbuild/linux-riscv64@0.23.1':
+    resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==}
+    engines: {node: '>=18'}
+    cpu: [riscv64]
+    os: [linux]
+
+  '@esbuild/linux-s390x@0.23.1':
+    resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==}
+    engines: {node: '>=18'}
+    cpu: [s390x]
+    os: [linux]
+
+  '@esbuild/linux-x64@0.23.1':
+    resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [linux]
+
+  '@esbuild/netbsd-x64@0.23.1':
+    resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [netbsd]
+
+  '@esbuild/openbsd-arm64@0.23.1':
+    resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [openbsd]
+
+  '@esbuild/openbsd-x64@0.23.1':
+    resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [openbsd]
+
+  '@esbuild/sunos-x64@0.23.1':
+    resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [sunos]
+
+  '@esbuild/win32-arm64@0.23.1':
+    resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [win32]
+
+  '@esbuild/win32-ia32@0.23.1':
+    resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==}
+    engines: {node: '>=18'}
+    cpu: [ia32]
+    os: [win32]
+
+  '@esbuild/win32-x64@0.23.1':
+    resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [win32]
+
+  '@iarna/toml@2.2.5':
+    resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==}
+
+  '@nodelib/fs.scandir@2.1.5':
+    resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+    engines: {node: '>= 8'}
+
+  '@nodelib/fs.stat@2.0.5':
+    resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+    engines: {node: '>= 8'}
+
+  '@nodelib/fs.walk@1.2.8':
+    resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+    engines: {node: '>= 8'}
+
+  '@types/fs-extra@11.0.4':
+    resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==}
+
+  '@types/jsonfile@6.1.4':
+    resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==}
+
+  '@types/minimist@1.2.5':
+    resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==}
+
+  '@types/node@18.19.75':
+    resolution: {integrity: sha512-UIksWtThob6ZVSyxcOqCLOUNg/dyO1Qvx4McgeuhrEtHTLFTf7BBhEazaE4K806FGTPtzd/2sE90qn4fVr7cyw==}
+
+  '@types/ps-tree@1.1.6':
+    resolution: {integrity: sha512-PtrlVaOaI44/3pl3cvnlK+GxOM3re2526TJvPvh7W+keHIXdV4TE0ylpPBAcvFQCbGitaTXwL9u+RF7qtVeazQ==}
+
+  '@types/which@3.0.4':
+    resolution: {integrity: sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w==}
+
+  braces@3.0.3:
+    resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+    engines: {node: '>=8'}
+
+  chalk@5.4.1:
+    resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==}
+    engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+
+  data-uri-to-buffer@4.0.1:
+    resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
+    engines: {node: '>= 12'}
+
+  dir-glob@3.0.1:
+    resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
+    engines: {node: '>=8'}
+
+  duplexer@0.1.2:
+    resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
+
+  esbuild@0.23.1:
+    resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==}
+    engines: {node: '>=18'}
+    hasBin: true
+
+  event-stream@3.3.4:
+    resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==}
+
+  fast-glob@3.3.3:
+    resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
+    engines: {node: '>=8.6.0'}
+
+  fastq@1.19.0:
+    resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==}
+
+  fetch-blob@3.2.0:
+    resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
+    engines: {node: ^12.20 || >= 14.13}
+
+  fill-range@7.1.1:
+    resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+    engines: {node: '>=8'}
+
+  formdata-polyfill@4.0.10:
+    resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
+    engines: {node: '>=12.20.0'}
+
+  from@0.1.7:
+    resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==}
+
+  fs-extra@11.3.0:
+    resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==}
+    engines: {node: '>=14.14'}
+
+  fsevents@2.3.3:
+    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+    os: [darwin]
+
+  fx@35.0.0:
+    resolution: {integrity: sha512-O07q+Lknrom5RUX/u53tjo2KTTLUnL0K703JbqMYb19ORijfJNvijzFqqYXEjdk25T9R14S6t6wHD8fCWXCM0g==}
+    hasBin: true
+
+  get-tsconfig@4.10.0:
+    resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==}
+
+  glob-parent@5.1.2:
+    resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+    engines: {node: '>= 6'}
+
+  globby@13.2.2:
+    resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+  graceful-fs@4.2.11:
+    resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+  ignore@5.3.2:
+    resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+    engines: {node: '>= 4'}
+
+  is-extglob@2.1.1:
+    resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+    engines: {node: '>=0.10.0'}
+
+  is-glob@4.0.3:
+    resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+    engines: {node: '>=0.10.0'}
+
+  is-number@7.0.0:
+    resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+    engines: {node: '>=0.12.0'}
+
+  isexe@2.0.0:
+    resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+  jsonfile@6.1.0:
+    resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
+
+  map-stream@0.1.0:
+    resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==}
+
+  merge2@1.4.1:
+    resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+    engines: {node: '>= 8'}
+
+  micromatch@4.0.8:
+    resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+    engines: {node: '>=8.6'}
+
+  minimist@1.2.8:
+    resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
+  node-domexception@1.0.0:
+    resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
+    engines: {node: '>=10.5.0'}
+
+  node-fetch@3.3.1:
+    resolution: {integrity: sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+  path-type@4.0.0:
+    resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
+    engines: {node: '>=8'}
+
+  pause-stream@0.0.11:
+    resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==}
+
+  picomatch@2.3.1:
+    resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+    engines: {node: '>=8.6'}
+
+  ps-tree@1.2.0:
+    resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==}
+    engines: {node: '>= 0.10'}
+    hasBin: true
+
+  queue-microtask@1.2.3:
+    resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+  resolve-pkg-maps@1.0.0:
+    resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
+  reusify@1.0.4:
+    resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
+    engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+  run-parallel@1.2.0:
+    resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+  slash@4.0.0:
+    resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==}
+    engines: {node: '>=12'}
+
+  split@0.3.3:
+    resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==}
+
+  stream-combiner@0.0.4:
+    resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==}
+
+  through@2.3.8:
+    resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
+
+  to-regex-range@5.0.1:
+    resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+    engines: {node: '>=8.0'}
+
+  tsx@4.19.2:
+    resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==}
+    engines: {node: '>=18.0.0'}
+    hasBin: true
+
+  typescript@5.7.3:
+    resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==}
+    engines: {node: '>=14.17'}
+    hasBin: true
+
+  undici-types@5.26.5:
+    resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
+
+  universalify@2.0.1:
+    resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
+    engines: {node: '>= 10.0.0'}
+
+  web-streams-polyfill@3.3.3:
+    resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
+    engines: {node: '>= 8'}
+
+  webpod@0.0.2:
+    resolution: {integrity: sha512-cSwwQIeg8v4i3p4ajHhwgR7N6VyxAf+KYSSsY6Pd3aETE+xEU4vbitz7qQkB0I321xnhDdgtxuiSfk5r/FVtjg==}
+    hasBin: true
+
+  which@3.0.1:
+    resolution: {integrity: sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==}
+    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+    hasBin: true
+
+  yaml@2.7.0:
+    resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==}
+    engines: {node: '>= 14'}
+    hasBin: true
+
+  zx@7.2.3:
+    resolution: {integrity: sha512-QODu38nLlYXg/B/Gw7ZKiZrvPkEsjPN3LQ5JFXM7h0JvwhEdPNNl+4Ao1y4+o3CLNiDUNcwzQYZ4/Ko7kKzCMA==}
+    engines: {node: '>= 16.0.0'}
+    hasBin: true
+
+snapshots:
+
+  '@esbuild/aix-ppc64@0.23.1':
+    optional: true
+
+  '@esbuild/android-arm64@0.23.1':
+    optional: true
+
+  '@esbuild/android-arm@0.23.1':
+    optional: true
+
+  '@esbuild/android-x64@0.23.1':
+    optional: true
+
+  '@esbuild/darwin-arm64@0.23.1':
+    optional: true
+
+  '@esbuild/darwin-x64@0.23.1':
+    optional: true
+
+  '@esbuild/freebsd-arm64@0.23.1':
+    optional: true
+
+  '@esbuild/freebsd-x64@0.23.1':
+    optional: true
+
+  '@esbuild/linux-arm64@0.23.1':
+    optional: true
+
+  '@esbuild/linux-arm@0.23.1':
+    optional: true
+
+  '@esbuild/linux-ia32@0.23.1':
+    optional: true
+
+  '@esbuild/linux-loong64@0.23.1':
+    optional: true
+
+  '@esbuild/linux-mips64el@0.23.1':
+    optional: true
+
+  '@esbuild/linux-ppc64@0.23.1':
+    optional: true
+
+  '@esbuild/linux-riscv64@0.23.1':
+    optional: true
+
+  '@esbuild/linux-s390x@0.23.1':
+    optional: true
+
+  '@esbuild/linux-x64@0.23.1':
+    optional: true
+
+  '@esbuild/netbsd-x64@0.23.1':
+    optional: true
+
+  '@esbuild/openbsd-arm64@0.23.1':
+    optional: true
+
+  '@esbuild/openbsd-x64@0.23.1':
+    optional: true
+
+  '@esbuild/sunos-x64@0.23.1':
+    optional: true
+
+  '@esbuild/win32-arm64@0.23.1':
+    optional: true
+
+  '@esbuild/win32-ia32@0.23.1':
+    optional: true
+
+  '@esbuild/win32-x64@0.23.1':
+    optional: true
+
+  '@iarna/toml@2.2.5': {}
+
+  '@nodelib/fs.scandir@2.1.5':
+    dependencies:
+      '@nodelib/fs.stat': 2.0.5
+      run-parallel: 1.2.0
+
+  '@nodelib/fs.stat@2.0.5': {}
+
+  '@nodelib/fs.walk@1.2.8':
+    dependencies:
+      '@nodelib/fs.scandir': 2.1.5
+      fastq: 1.19.0
+
+  '@types/fs-extra@11.0.4':
+    dependencies:
+      '@types/jsonfile': 6.1.4
+      '@types/node': 18.19.75
+
+  '@types/jsonfile@6.1.4':
+    dependencies:
+      '@types/node': 18.19.75
+
+  '@types/minimist@1.2.5': {}
+
+  '@types/node@18.19.75':
+    dependencies:
+      undici-types: 5.26.5
+
+  '@types/ps-tree@1.1.6': {}
+
+  '@types/which@3.0.4': {}
+
+  braces@3.0.3:
+    dependencies:
+      fill-range: 7.1.1
+
+  chalk@5.4.1: {}
+
+  data-uri-to-buffer@4.0.1: {}
+
+  dir-glob@3.0.1:
+    dependencies:
+      path-type: 4.0.0
+
+  duplexer@0.1.2: {}
+
+  esbuild@0.23.1:
+    optionalDependencies:
+      '@esbuild/aix-ppc64': 0.23.1
+      '@esbuild/android-arm': 0.23.1
+      '@esbuild/android-arm64': 0.23.1
+      '@esbuild/android-x64': 0.23.1
+      '@esbuild/darwin-arm64': 0.23.1
+      '@esbuild/darwin-x64': 0.23.1
+      '@esbuild/freebsd-arm64': 0.23.1
+      '@esbuild/freebsd-x64': 0.23.1
+      '@esbuild/linux-arm': 0.23.1
+      '@esbuild/linux-arm64': 0.23.1
+      '@esbuild/linux-ia32': 0.23.1
+      '@esbuild/linux-loong64': 0.23.1
+      '@esbuild/linux-mips64el': 0.23.1
+      '@esbuild/linux-ppc64': 0.23.1
+      '@esbuild/linux-riscv64': 0.23.1
+      '@esbuild/linux-s390x': 0.23.1
+      '@esbuild/linux-x64': 0.23.1
+      '@esbuild/netbsd-x64': 0.23.1
+      '@esbuild/openbsd-arm64': 0.23.1
+      '@esbuild/openbsd-x64': 0.23.1
+      '@esbuild/sunos-x64': 0.23.1
+      '@esbuild/win32-arm64': 0.23.1
+      '@esbuild/win32-ia32': 0.23.1
+      '@esbuild/win32-x64': 0.23.1
+
+  event-stream@3.3.4:
+    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
+
+  fast-glob@3.3.3:
+    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.8
+
+  fastq@1.19.0:
+    dependencies:
+      reusify: 1.0.4
+
+  fetch-blob@3.2.0:
+    dependencies:
+      node-domexception: 1.0.0
+      web-streams-polyfill: 3.3.3
+
+  fill-range@7.1.1:
+    dependencies:
+      to-regex-range: 5.0.1
+
+  formdata-polyfill@4.0.10:
+    dependencies:
+      fetch-blob: 3.2.0
+
+  from@0.1.7: {}
+
+  fs-extra@11.3.0:
+    dependencies:
+      graceful-fs: 4.2.11
+      jsonfile: 6.1.0
+      universalify: 2.0.1
+
+  fsevents@2.3.3:
+    optional: true
+
+  fx@35.0.0: {}
+
+  get-tsconfig@4.10.0:
+    dependencies:
+      resolve-pkg-maps: 1.0.0
+
+  glob-parent@5.1.2:
+    dependencies:
+      is-glob: 4.0.3
+
+  globby@13.2.2:
+    dependencies:
+      dir-glob: 3.0.1
+      fast-glob: 3.3.3
+      ignore: 5.3.2
+      merge2: 1.4.1
+      slash: 4.0.0
+
+  graceful-fs@4.2.11: {}
+
+  ignore@5.3.2: {}
+
+  is-extglob@2.1.1: {}
+
+  is-glob@4.0.3:
+    dependencies:
+      is-extglob: 2.1.1
+
+  is-number@7.0.0: {}
+
+  isexe@2.0.0: {}
+
+  jsonfile@6.1.0:
+    dependencies:
+      universalify: 2.0.1
+    optionalDependencies:
+      graceful-fs: 4.2.11
+
+  map-stream@0.1.0: {}
+
+  merge2@1.4.1: {}
+
+  micromatch@4.0.8:
+    dependencies:
+      braces: 3.0.3
+      picomatch: 2.3.1
+
+  minimist@1.2.8: {}
+
+  node-domexception@1.0.0: {}
+
+  node-fetch@3.3.1:
+    dependencies:
+      data-uri-to-buffer: 4.0.1
+      fetch-blob: 3.2.0
+      formdata-polyfill: 4.0.10
+
+  path-type@4.0.0: {}
+
+  pause-stream@0.0.11:
+    dependencies:
+      through: 2.3.8
+
+  picomatch@2.3.1: {}
+
+  ps-tree@1.2.0:
+    dependencies:
+      event-stream: 3.3.4
+
+  queue-microtask@1.2.3: {}
+
+  resolve-pkg-maps@1.0.0: {}
+
+  reusify@1.0.4: {}
+
+  run-parallel@1.2.0:
+    dependencies:
+      queue-microtask: 1.2.3
+
+  slash@4.0.0: {}
+
+  split@0.3.3:
+    dependencies:
+      through: 2.3.8
+
+  stream-combiner@0.0.4:
+    dependencies:
+      duplexer: 0.1.2
+
+  through@2.3.8: {}
+
+  to-regex-range@5.0.1:
+    dependencies:
+      is-number: 7.0.0
+
+  tsx@4.19.2:
+    dependencies:
+      esbuild: 0.23.1
+      get-tsconfig: 4.10.0
+    optionalDependencies:
+      fsevents: 2.3.3
+
+  typescript@5.7.3: {}
+
+  undici-types@5.26.5: {}
+
+  universalify@2.0.1: {}
+
+  web-streams-polyfill@3.3.3: {}
+
+  webpod@0.0.2: {}
+
+  which@3.0.1:
+    dependencies:
+      isexe: 2.0.0
+
+  yaml@2.7.0: {}
+
+  zx@7.2.3:
+    dependencies:
+      '@types/fs-extra': 11.0.4
+      '@types/minimist': 1.2.5
+      '@types/node': 18.19.75
+      '@types/ps-tree': 1.1.6
+      '@types/which': 3.0.4
+      chalk: 5.4.1
+      fs-extra: 11.3.0
+      fx: 35.0.0
+      globby: 13.2.2
+      minimist: 1.2.8
+      node-fetch: 3.3.1
+      ps-tree: 1.2.0
+      webpod: 0.0.2
+      which: 3.0.1
+      yaml: 2.7.0

+ 53 - 0
scripts/audit.mts

@@ -0,0 +1,53 @@
+#!/usr/bin/env zx
+import 'zx/globals';
+
+const advisories = [
+  // === main repo ===
+  //
+  // Crate:     ed25519-dalek
+  // Version:   1.0.1
+  // Title:     Double Public Key Signing Function Oracle Attack on `ed25519-dalek`
+  // Date:      2022-06-11
+  // ID:        RUSTSEC-2022-0093
+  // URL:       https://rustsec.org/advisories/RUSTSEC-2022-0093
+  // Solution:  Upgrade to >=2
+  'RUSTSEC-2022-0093',
+
+  // Crate:     idna
+  // Version:   0.1.5
+  // Title:     `idna` accepts Punycode labels that do not produce any non-ASCII when decoded
+  // Date:      2024-12-09
+  // ID:        RUSTSEC-2024-0421
+  // URL:       https://rustsec.org/advisories/RUSTSEC-2024-0421
+  // Solution:  Upgrade to >=1.0.0
+  // need to solve this depentant tree:
+  // jsonrpc-core-client v18.0.0 -> jsonrpc-client-transports v18.0.0 -> url v1.7.2 -> idna v0.1.5
+  'RUSTSEC-2024-0421',
+
+  // === programs/sbf ===
+  //
+  // Crate:     curve25519-dalek
+  // Version:   3.2.1
+  // Title:     Timing variability in `curve25519-dalek`'s `Scalar29::sub`/`Scalar52::sub`
+  // Date:      2024-06-18
+  // ID:        RUSTSEC-2024-0344
+  // URL:       https://rustsec.org/advisories/RUSTSEC-2024-0344
+  // Solution:  Upgrade to >=4.1.3
+  'RUSTSEC-2024-0344',
+
+  // Crate:     tonic
+  // Version:   0.9.2
+  // Title:     Remotely exploitable Denial of Service in Tonic
+  // Date:      2024-10-01
+  // ID:        RUSTSEC-2024-0376
+  // URL:       https://rustsec.org/advisories/RUSTSEC-2024-0376
+  // Solution:  Upgrade to >=0.12.3
+  'RUSTSEC-2024-0376',
+];
+const ignores: string[] = [];
+advisories.forEach((x) => {
+  ignores.push('--ignore');
+  ignores.push(x);
+});
+
+await $`cargo audit ${ignores}`;

+ 10 - 0
scripts/build-sbf.mts

@@ -0,0 +1,10 @@
+#!/usr/bin/env zx
+import 'zx/globals';
+import { cliArguments, workingDirectory } from './setup/shared.mts';
+
+const [folder, ...args] = cliArguments();
+
+const buildArgs = [...args, '--', '--all-targets', '--all-features'];
+const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml');
+
+await $`cargo-build-sbf --manifest-path ${manifestPath} ${buildArgs}`;

+ 31 - 0
scripts/clippy.mts

@@ -0,0 +1,31 @@
+#!/usr/bin/env zx
+import 'zx/globals';
+import {
+  cliArguments,
+  getToolchainArgument,
+  popArgument,
+  workingDirectory,
+} from './setup/shared.mts';
+
+const [folder, ...args] = cliArguments();
+
+const lintArgs = [
+  '-Zunstable-options',
+  '--all-targets',
+  '--all-features',
+  '--no-deps',
+  '--',
+  '--deny=warnings',
+  ...args,
+];
+
+const fix = popArgument(lintArgs, '--fix');
+const toolchain = getToolchainArgument('lint');
+const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml');
+
+// Check the client using Clippy.
+if (fix) {
+  await $`cargo ${toolchain} clippy --manifest-path ${manifestPath} --fix ${lintArgs}`;
+} else {
+  await $`cargo ${toolchain} clippy --manifest-path ${manifestPath} ${lintArgs}`;
+}

+ 16 - 0
scripts/doc.mts

@@ -0,0 +1,16 @@
+#!/usr/bin/env zx
+import 'zx/globals';
+import {
+  cliArguments,
+  getToolchainArgument,
+  workingDirectory,
+} from './setup/shared.mts';
+
+const [folder, ...args] = cliArguments();
+const docArgs = ['--all-features', '--no-deps', ...args];
+
+const toolchain = getToolchainArgument('lint');
+const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml');
+
+$.env['RUSTDOCFLAGS'] = '--cfg docsrs -D warnings';
+await $`cargo ${toolchain} doc --manifest-path ${manifestPath} ${docArgs}`;

+ 24 - 0
scripts/format.mts

@@ -0,0 +1,24 @@
+#!/usr/bin/env zx
+import 'zx/globals';
+import {
+  cliArguments,
+  getToolchainArgument,
+  partitionArguments,
+  popArgument,
+  workingDirectory,
+} from './setup/shared.mts';
+
+const [folder, ...formatArgs] = cliArguments();
+
+const toolchain = getToolchainArgument('format');
+const fix = popArgument(formatArgs, '--fix');
+
+const [cargoArgs, fmtArgs] = partitionArguments(formatArgs, '--');
+const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml');
+
+// Format the client.
+if (fix) {
+  await $`cargo ${toolchain} fmt --manifest-path ${manifestPath} ${cargoArgs} -- ${fmtArgs}`;
+} else {
+  await $`cargo ${toolchain} fmt --manifest-path ${manifestPath} ${cargoArgs} -- --check ${fmtArgs}`;
+}

+ 15 - 0
scripts/hack.mts

@@ -0,0 +1,15 @@
+#!/usr/bin/env zx
+import 'zx/globals';
+import {
+  cliArguments,
+  getToolchainArgument,
+  workingDirectory,
+} from './setup/shared.mts';
+
+const [folder, ...args] = cliArguments();
+const checkArgs = ['--all-targets', '--feature-powerset', ...args];
+
+const toolchain = getToolchainArgument('lint');
+const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml');
+
+await $`cargo ${toolchain} hack check --manifest-path ${manifestPath} ${checkArgs}`;

+ 9 - 0
scripts/lint.mts

@@ -0,0 +1,9 @@
+import 'zx/globals';
+
+const args = process.argv.slice(2);
+
+await Promise.all([
+  $`tsx ./scripts/clippy.mts ${args}`,
+  $`tsx ./scripts/doc.mts ${args}`,
+  $`tsx ./scripts/hack.mts ${args}`,
+]);

+ 45 - 0
scripts/publish.mts

@@ -0,0 +1,45 @@
+#!/usr/bin/env zx
+import 'zx/globals';
+import {
+  cliArguments,
+  getCargo,
+  popArgument,
+  workingDirectory,
+} from './setup/shared.mts';
+
+const [folder, ...args] = cliArguments();
+const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml');
+
+const fix = popArgument(args, '--dry-run');
+const dryRun = argv['dry-run'] ?? false;
+
+const [level] = args;
+if (!level) {
+  throw new Error('A version level — e.g. "patch" — must be provided.');
+}
+
+// Get the crate name.
+const crate = getCargo(folder).package['name'];
+
+// Go to the crate folder to release.
+cd(path.dirname(manifestPath));
+
+// Publish the new version.
+const releaseArgs = dryRun
+  ? []
+  : ['--tag-name', `${crate}@v{{version}}`, '--no-confirm', '--execute'];
+await $`cargo release ${level} ${releaseArgs}`;
+
+// Stop here if this is a dry run.
+if (dryRun) {
+  process.exit(0);
+}
+
+// Get the updated version number.
+const version = getCargo(folder).package['version'];
+
+// Expose the new version to CI if needed.
+if (process.env.CI) {
+  await $`echo "crate=${crate}" >> $GITHUB_OUTPUT`;
+  await $`echo "version=${version}" >> $GITHUB_OUTPUT`;
+}

+ 8 - 0
scripts/semver.mts

@@ -0,0 +1,8 @@
+#!/usr/bin/env zx
+import 'zx/globals';
+import { cliArguments, workingDirectory } from './setup/shared.mts';
+
+const [folder, ...args] = cliArguments();
+const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml');
+
+await $`cargo semver-checks --manifest-path ${manifestPath} ${args}`;

+ 8 - 0
scripts/setup/ci.mts

@@ -0,0 +1,8 @@
+#!/usr/bin/env zx
+import { getSolanaVersion, getToolchain } from './shared.mts';
+
+await $`echo "SOLANA_VERSION=${getSolanaVersion()}" >> $GITHUB_ENV`;
+await $`echo "TOOLCHAIN_BUILD=${getToolchain('build')}" >> $GITHUB_ENV`;
+await $`echo "TOOLCHAIN_FORMAT=${getToolchain('format')}" >> $GITHUB_ENV`;
+await $`echo "TOOLCHAIN_LINT=${getToolchain('lint')}" >> $GITHUB_ENV`;
+await $`echo "TOOLCHAIN_TEST=${getToolchain('test')}" >> $GITHUB_ENV`;

+ 5 - 0
scripts/setup/members.mts

@@ -0,0 +1,5 @@
+import 'zx/globals';
+import { getCargo } from './shared.mts';
+
+const members = getCargo().workspace['members'] as string[];
+await $`echo members=${JSON.stringify(members)} >> $GITHUB_OUTPUT`;

+ 103 - 0
scripts/setup/shared.mts

@@ -0,0 +1,103 @@
+import 'zx/globals';
+import { JsonMap, parse as parseToml } from '@iarna/toml';
+
+process.env.FORCE_COLOR = '3';
+process.env.CARGO_TERM_COLOR = 'always';
+
+export const workingDirectory = (await $`pwd`.quiet()).toString().trim();
+
+export function getCargo(folder?: string): JsonMap {
+  return parseToml(
+    fs.readFileSync(
+      path.resolve(
+        workingDirectory,
+        path.join(folder ? folder : '.', 'Cargo.toml')
+      ),
+      'utf8'
+    )
+  );
+}
+
+export function getCargoMetadata(folder?: string) {
+  const cargo = getCargo(folder);
+  return folder ? cargo?.package?.['metadata'] : cargo?.workspace?.['metadata'];
+}
+
+export function getSolanaVersion(): string {
+  return getCargoMetadata()?.cli?.solana;
+}
+
+export function getToolchain(operation): string {
+  return getCargoMetadata()?.toolchains?.[operation];
+}
+
+export function getToolchainArgument(operation): string {
+  const channel = getToolchain(operation);
+  return channel ? `+${channel}` : '';
+}
+
+export function cliArguments(): string[] {
+  return process.argv.slice(2);
+}
+
+export function popArgument(args: string[], arg: string) {
+  const index = args.indexOf(arg);
+  if (index >= 0) {
+    args.splice(index, 1);
+  }
+  return index >= 0;
+}
+
+export function partitionArguments(
+  args: string[],
+  delimiter: string,
+  defaultArgs?: string[]
+): [string[], string[]] {
+  const index = args.indexOf(delimiter);
+  const [providedCargoArgs, providedCommandArgs] =
+    index >= 0 ? [args.slice(0, index), args.slice(index + 1)] : [args, []];
+
+  if (defaultArgs) {
+    const [defaultCargoArgs, defaultCommandArgs] = partitionArguments(
+      defaultArgs,
+      delimiter
+    );
+    return [
+      [...defaultCargoArgs, ...providedCargoArgs],
+      [...defaultCommandArgs, ...providedCommandArgs],
+    ];
+  }
+  return [providedCargoArgs, providedCommandArgs];
+}
+
+export async function getInstalledSolanaVersion(): Promise<string | undefined> {
+  try {
+    const { stdout } = await $`solana --version`.quiet();
+    return stdout.match(/(\d+\.\d+\.\d+)/)?.[1];
+  } catch (error) {
+    return '';
+  }
+}
+
+export function parseCliArguments(): {
+  command: string;
+  libraryPath: string;
+  args: string[];
+} {
+  const command = process.argv[2];
+  const args = process.argv.slice(3);
+
+  // Extract the relative crate directory from the command-line arguments. This
+  // is the only required argument.
+  const relativePath = args.shift();
+
+  if (!relativePath) {
+    throw new Error('Missing relative manifest path');
+  }
+
+  return {
+    command,
+    libraryPath: path.join(workingDirectory, relativePath),
+    args,
+  };
+}

+ 16 - 0
scripts/test.mts

@@ -0,0 +1,16 @@
+#!/usr/bin/env zx
+import 'zx/globals';
+import {
+  cliArguments,
+  getToolchainArgument,
+  workingDirectory,
+} from './setup/shared.mts';
+
+const [folder, ...args] = cliArguments();
+
+const testArgs = ['--all-features', ...args];
+const toolchain = getToolchainArgument('test');
+
+const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml');
+
+await $`cargo ${toolchain} test --manifest-path ${manifestPath} ${testArgs}`;