feat: migrate pmg to nx based release automation (#293)

* feat: Migrate release system to Nx with platform-specific npm packages

* add go.work.sum

* fix: CI deprecations, stale action pins, and signal propagation

* fix: update e2e workflows to pnpm 11 and latest action SHAs

* fix: update pmg-e2e.yml to Node 24 with Go and pnpm caching

* fix: resolve E2E failures, remove goreleaser-test, update action SHAs

* fix: restore goreleaser-test (required check)

* fix: escape pnpm workspace detection for yarn/pnpx tests, update action versions
This commit is contained in:
Arunanshu Biswas
2026-05-28 17:22:11 +05:30
committed by GitHub
parent 19b9f2ca1f
commit 1c25395d74
40 changed files with 3205 additions and 698 deletions
+7 -7
View File
@@ -26,8 +26,8 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24
- uses: ./
@@ -65,8 +65,8 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24
- name: Write custom PMG config
@@ -106,8 +106,8 @@ jobs:
matrix:
driver: [landlock, bubblewrap]
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24
- uses: ./
@@ -142,7 +142,7 @@ jobs:
runs-on: ${{ matrix.os }}
timeout-minutes: 5
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Action must exit non-zero
id: run
continue-on-error: true
+43 -9
View File
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Source
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
@@ -45,7 +45,7 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout Source
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
@@ -63,7 +63,7 @@ jobs:
timeout-minutes: 15
steps:
- name: Checkout Source
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
@@ -71,15 +71,13 @@ jobs:
go-version-file: go.mod
- name: Setup Node
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 20
check-latest: true
- name: Setup PNPM
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
with:
version: 10
- name: Build Binary
run: make
@@ -93,7 +91,7 @@ jobs:
timeout-minutes: 15
steps:
- name: Checkout Source
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
@@ -101,7 +99,7 @@ jobs:
go-version-file: go.mod
- name: Run Goreleaser
uses: goreleaser/goreleaser-action@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 # v7
uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7.2.2
with:
distribution: goreleaser
version: "~> v2"
@@ -112,7 +110,7 @@ jobs:
timeout-minutes: 15
steps:
- name: Checkout Source
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
@@ -124,3 +122,39 @@ jobs:
run: |
docker buildx build --platform linux/amd64 --load \
-t build-container-test:latest .
release-preflight:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: go.mod
cache: true
- name: Enable corepack
run: corepack enable
- name: Set up Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "24"
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install GoReleaser
uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7.2.2
with:
install-only: true
version: "~> v2"
- name: Release preflight
run: pnpm nx run pmg:release-preflight
+75 -38
View File
@@ -11,21 +11,17 @@ permissions:
contents: read
jobs:
goreleaser:
release:
timeout-minutes: 60
environment: npm-release
outputs:
hashes: ${{ steps.hash.outputs.hashes }}
permissions:
contents: write # for goreleaser/goreleaser-action to create a GitHub release
packages: write # for goreleaser/goreleaser-action to publish docker images
runs-on: ubuntu-latest
env:
# Required for buildx on docker 19.x
DOCKER_CLI_EXPERIMENTAL: "enabled"
permissions:
contents: write
id-token: write
attestations: write
steps:
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
@@ -33,39 +29,80 @@ jobs:
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: go.mod
cache: true
- name: Run GoReleaser
id: run-goreleaser
uses: goreleaser/goreleaser-action@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 # v7
- name: Enable corepack
run: corepack enable
- name: Set up Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
distribution: goreleaser
node-version: "24"
registry-url: "https://registry.npmjs.org"
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Extract version
id: version
shell: bash
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Install GoReleaser
uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7.2.2
with:
install-only: true
version: "~> v2"
args: release --clean
- name: Build release (goreleaser via Nx)
run: pnpm nx run pmg:build-release
env:
GITHUB_TOKEN: ${{ secrets.GORELEASER_GITHUB_TOKEN }}
- name: Upload dist Folder
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: dist-artifacts
path: dist/
provenance:
needs: [goreleaser]
permissions:
actions: read # To read the workflow path.
id-token: write # To sign the provenance.
attestations: write # To write attestations
artifact-metadata: write # To write artifact metadata
runs-on: ubuntu-latest
steps:
- name: Download dist folder
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: dist-artifacts
path: dist/
- name: Attest build provenance (checksums)
uses: actions/attest-build-provenance@b3e506e8c389afc651c5bacf2b8f2a1ea0557215 # v4
- name: Attest Go binary provenance
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-checksums: dist/checksums.txt
- name: Publish npm packages (sync + build + publish via Nx)
run: pnpm nx run pmg:publish-npm
env:
VERSION: ${{ steps.version.outputs.version }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
test-installation:
needs: release
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: ["18", "20", "22", "24"]
steps:
- name: Set up Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ matrix.node }}
- name: Extract version
id: version
shell: bash
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Wait for package on registry
shell: bash
run: |
for i in $(seq 1 20); do
npm view "@safedep/pmg@${{ steps.version.outputs.version }}" version > /dev/null 2>&1 && break
echo "Waiting... ($i/20)"
sleep 30
[ "$i" -eq 20 ] && echo "Package not available after 10 minutes" && exit 1
done
- name: Install
run: npm install -g "@safedep/pmg@${{ steps.version.outputs.version }}"
- name: Smoke test
run: pmg version
+36 -32
View File
@@ -31,26 +31,27 @@ jobs:
shell: bash
steps:
- name: Checkout Source
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: go.mod
cache: true
- name: Enable corepack
run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 20
check-latest: true
node-version: "24"
- name: Setup PNPM
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
with:
version: 10
- name: Setup Bun
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: latest
@@ -60,7 +61,7 @@ jobs:
python-version: "3.11"
- name: Setup uv
uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- name: Install Poetry
uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a
@@ -224,7 +225,7 @@ jobs:
- name: Test PNPM - Single Package & Manifest
run: |
echo "Testing PNPM single package installation..."
mkdir pnpm-test && cd pnpm-test
PNPM_TESTDIR=$(mktemp -d) && cd "$PNPM_TESTDIR"
pmg --proxy-mode=false pnpm init
pmg --proxy-mode=false pnpm add express@5.2.1
pmg --proxy-mode=false pnpm add lodash@4.17.21
@@ -242,7 +243,7 @@ jobs:
test -f pnpm-lock.yaml
test -d node_modules/express
test -d node_modules/lodash
cd .. && rm -rf pnpm-test
cd - && rm -rf "$PNPM_TESTDIR"
- name: Test Bun - Single Package & Manifest
run: |
@@ -283,7 +284,7 @@ jobs:
npm install -g yarn@1.22.22
yarn --version
mkdir yarn-test && cd yarn-test
YARN_TESTDIR=$(mktemp -d) && cd "$YARN_TESTDIR"
pmg --proxy-mode=false yarn init -y
pmg --proxy-mode=false yarn add express@5.2.1
pmg --proxy-mode=false yarn add lodash@4.17.21
@@ -301,7 +302,7 @@ jobs:
test -f yarn.lock
test -d node_modules/express
test -d node_modules/lodash
cd .. && rm -rf yarn-test
cd - && rm -rf "$YARN_TESTDIR"
- name: Test NPX - Package Execution
run: |
@@ -331,7 +332,7 @@ jobs:
- name: Test PNPX - Package Execution
run: |
echo "Testing PNPX package execution..."
mkdir pnpx-test && cd pnpx-test
PNPX_TESTDIR=$(mktemp -d) && cd "$PNPX_TESTDIR"
echo "Testing pnpx with a simple package..."
pmg --proxy-mode=false pnpx cowsay@1.6.0 "Hello from pmg pnpx" | tee pnpx-output.txt
@@ -351,7 +352,7 @@ jobs:
# Verification: dry-run should NOT produce cowsay ASCII art (cow face ^__^ should not appear)
! grep -q '\^__\^' pnpx-dry-output.txt
cd .. && rm -rf pnpx-test
cd - && rm -rf "$PNPX_TESTDIR"
- name: Test Pip - Single Package & Manifest
run: |
@@ -516,23 +517,24 @@ jobs:
timeout-minutes: 10
steps:
- name: Checkout Source
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: go.mod
cache: true
- name: Enable corepack
run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 20
check-latest: true
node-version: "24"
- name: Setup PNPM
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
with:
version: 10
- name: Build PMG
run: make
@@ -571,23 +573,24 @@ jobs:
PMG_SANDBOX_DRIVER: bubblewrap
steps:
- name: Checkout Source
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: go.mod
cache: true
- name: Enable corepack
run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 20
check-latest: true
node-version: "24"
- name: Setup PNPM
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
with:
version: 10
- name: Install Bubblewrap
run: sudo apt-get update && sudo apt-get install -y bubblewrap
@@ -646,23 +649,24 @@ jobs:
npm_config_cache: /tmp/npm-cache
steps:
- name: Checkout Source
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: go.mod
cache: true
- name: Enable corepack
run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 20
check-latest: true
node-version: "24"
- name: Setup PNPM
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
with:
version: 10
- name: Build PMG
run: make
-97
View File
@@ -1,97 +0,0 @@
name: Publish NPM Package
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
jobs:
publish-npm:
runs-on: ubuntu-latest
environment: npm-release
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
- name: Extract version from tag
id: version
run: |
echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Wait for GitHub release
run: |
echo "Waiting for GitHub release v${{ steps.version.outputs.version }}..."
i=1
while [ $i -le 30 ]; do
if curl -s -f "https://api.github.com/repos/safedep/pmg/releases/tags/v${{ steps.version.outputs.version }}" > /dev/null; then
echo "Release found!"
break
fi
if [ $i -eq 30 ]; then
echo "Release not found after 10 minutes"
exit 1
fi
echo "Waiting... ($i/30)"
sleep 20
i=$((i + 1))
done
- name: Prepare package
run: |
cd publish/npm
npm version ${{ steps.version.outputs.version }} --no-git-tag-version
- name: Publish to npm
run: |
cd publish/npm
npm publish --provenance
test-installation:
needs: publish-npm
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: ["16", "18", "20"]
steps:
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
node-version: ${{ matrix.node-version }}
- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Wait for npm package
shell: bash
run: |
echo "Waiting for npm package..."
i=1
while [ $i -le 20 ]; do
if npm view @safedep/pmg@${{ steps.version.outputs.version }} > /dev/null 2>&1; then
echo "Package available!"
break
fi
if [ $i -eq 20 ]; then
echo "Package not available after 10 minutes"
exit 1
fi
echo "Waiting... ($i/20)"
sleep 30
i=$((i + 1))
done
- name: Test installation
run: |
npm install -g @safedep/pmg@${{ steps.version.outputs.version }}
pmg version
pmg --help || true
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
steps:
- name: Checkout
id: checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Enable Cloud Mode
run: echo "SAFEDEP_CLOUD_MODE=true" >> $GITHUB_ENV
+11 -3
View File
@@ -17,9 +17,6 @@
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
go.work.sum
bin/
# env file
@@ -27,3 +24,14 @@ bin/
dist/
.DS_Store
# Nx
.nx/
node_modules/
# Platform package binaries (populated by sync-binaries at build time)
packages/*/bin/
# Build artifacts in wrapper package
packages/pmg/dist/
packages/pmg/README.md
+22 -1
View File
@@ -11,6 +11,16 @@ builds:
- linux
- windows
- darwin
goarch:
- amd64
- arm64
ignore:
- goos: windows
goarch: arm64
ldflags:
- -s -w
- -X github.com/safedep/pmg/internal/version.Version={{.Version}}
- -X github.com/safedep/pmg/internal/version.Commit={{.Commit}}
archives:
- formats: [tar.gz]
@@ -39,11 +49,22 @@ checksum:
universal_binaries:
- replace: true
brews:
homebrew_casks:
- name: pmg
directory: Casks
homepage: https://github.com/safedep/pmg
description: "PMG protects developers from malicious packages"
license: "Apache-2.0"
binaries:
- pmg
url:
verified: github.com/safedep/pmg
hooks:
post:
install: |
if OS.mac?
system_command "/usr/bin/xattr", args: ["-dr", "com.apple.quarantine", "#{staged_path}/pmg"]
end
repository:
owner: safedep
name: homebrew-tap
+5 -5
View File
@@ -44,13 +44,13 @@ require (
github.com/denisbrodbeck/machineid v1.0.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-git/go-git/v5 v5.14.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.28.0 // indirect
github.com/go-playground/validator/v10 v10.30.2 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
@@ -81,10 +81,10 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/text v0.35.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
+10 -12
View File
@@ -58,8 +58,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
@@ -78,8 +78,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
@@ -174,8 +174,6 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/safedep/dry v0.0.0-20260513152148-f809919cc4ce h1:0FxzXTmSrMWse6u0Wv2CbZOjXB1EFZE1qjhF4vB9NOg=
github.com/safedep/dry v0.0.0-20260513152148-f809919cc4ce/go.mod h1:tKhOr0osgpefdBbxG8n4H5gYH1GLXidKS+hzNbRQ9LQ=
github.com/safedep/dry v0.0.0-20260524092302-4815730a17cf h1:1PorZhAZWANkKup1Ao4kS8s+L6a+dmR+G0waIQonjdY=
github.com/safedep/dry v0.0.0-20260524092302-4815730a17cf/go.mod h1:tKhOr0osgpefdBbxG8n4H5gYH1GLXidKS+hzNbRQ9LQ=
github.com/safedep/ptyx v0.2.1-0.20260119085117-f667570c2d12 h1:NzARvPtncPbVI8a8Z0JKpJ7XJCSPpsRstV7wAgEtmOU=
@@ -243,8 +241,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -262,8 +260,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -285,8 +283,8 @@ golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+4
View File
@@ -0,0 +1,4 @@
go 1.25.1
use .
use ./scripts
+260
View File
@@ -0,0 +1,260 @@
buf.build/gen/go/bufbuild/protovalidate/grpc/go v1.6.1-20240508200655-46a4cf4ba109.1/go.mod h1:12iIaR0LjReZQXXxBXMzWTMUIN6n4Y3HuSTwPpUQYSg=
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
cloud.google.com/go v0.121.2/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
cloud.google.com/go/profiler v0.4.3/go.mod h1:3xFodugWfPIQZWFcXdUmfa+yTiiyQ8fWrdT+d2Sg4J0=
cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
deps.dev/api/v3 v3.0.0-20250307021655-d811e36f9cad/go.mod h1:o6PwfRErnKxbT8Sdij64ZMiTqMNxPsYwfymiyjXPh7A=
deps.dev/api/v3alpha v0.0.0-20250429014815-ac0aa6a085fa/go.mod h1:qL3BV/n9j5WptUB9hWZrWM6yISapPTUYIQmHMWrGEmk=
deps.dev/util/maven v0.0.0-20250307021655-d811e36f9cad/go.mod h1:eGrXziwI7scSGrwIj+5EBHtTeSxAZD/yi8Hb3nFXesA=
deps.dev/util/pypi v0.0.0-20250307021655-d811e36f9cad/go.mod h1:qmA0z/Lsfa1FMtuLd9JmVZLMHR3GBX/EmbM6z1X3EDU=
deps.dev/util/resolve v0.0.0-20250310223405-f4cf91c9e684/go.mod h1:y8YIleznCUaoZGI9XKQrVNrNd8gcVuPzvAJ7BBcJo5Q=
deps.dev/util/semver v0.0.0-20250307021655-d811e36f9cad/go.mod h1:jjJweVqtuMQ7Q4zlTQ/kCHpboojkRvpMYlhy/c93DVU=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/ClickHouse/ch-go v0.66.0/go.mod h1:noiHWyLMJAZ5wYuq3R/K0TcRhrNA8h7o1AqHX0klEhM=
github.com/ClickHouse/clickhouse-go/v2 v2.35.0/go.mod h1:O2FFT/rugdpGEW2VKyEGyMUWyQU0ahmenY9/emxLPxs=
github.com/CycloneDX/cyclonedx-go v0.9.0/go.mod h1:NE/EWvzELOFlG6+ljX/QeMlVt9VKcTwu8u0ccsACEsw=
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5/go.mod h1:exZ0C/1emQJAw5tHOaUDyY1ycttqBAPcxuzf7QbY6ec=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.52.0/go.mod h1:ayYHuYU7iNcNtEs1K9k6D/Bju7u1VEHMQm5qQ1n3GtM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.52.0/go.mod h1:gdIm9TxRk5soClCwuB0FtdXsbqtw0aqPwBEurK9tPkw=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU=
github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/anchore/go-struct-converter v0.0.0-20230627203149-c72ef8859ca9/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
github.com/aws/aws-sdk-go-v2/config v1.32.15/go.mod h1:yLJzL0IkI9+4BwjPSOueyHzppJj3t0dhK5tbmmcFk5Q=
github.com/aws/aws-sdk-go-v2/credentials v1.19.14/go.mod h1:cJKuyWB59Mqi0jM3nFYQRmnHVQIcgoxjEMAbLkpr62w=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21/go.mod h1:YWNWJQNjKigKY1RHVJCuupeWDrrHjRqHm0N9rdrWzYI=
github.com/aws/aws-sdk-go-v2/feature/s3/transfermanager v0.1.16/go.mod h1:Iu9wL4lqscFF6ByhqyDO8mgvCUwGn5bqWr7fuOgUjTA=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ=
github.com/aws/aws-sdk-go-v2/service/s3 v1.99.0/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.9/go.mod h1:7yuQJoT+OoH8aqIxw9vwF+8KpvLZ8AWmvmUWHsGQZvI=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.15/go.mod h1:WSvS1NLr7JaPunCXqpJnWk1Bjo7IxzZXrZi1QQCkuqM=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19/go.mod h1:YO8TrYtFdl5w/4vmjL8zaBSsiNp3w0L1FfKVKenZT7w=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.10/go.mod h1:60dv0eZJfeVXfbT1tFJinbHrDfSJ2GZl4Q//OSSNAVw=
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/brunoga/deep v1.2.4/go.mod h1:GDV6dnXqn80ezsLSZ5Wlv1PdKAWAO4L5PnKYtv2dgaI=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.11.5/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/chigopher/pathlib v0.19.1/go.mod h1:tzC1dZLW8o33UQpWkNkhvPwL5n4yyFRFm/jL1YGWFvY=
github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cloudwego/eino v0.7.13/go.mod h1:nA8Vacmuqv3pqKBQbTWENBLQ8MmGmPt/WqiyLeB8ohQ=
github.com/cloudwego/eino-ext/components/model/claude v0.1.17/go.mod h1:2sGGgwpR60LW+RdG/hcjdGBVEVEJ6EkKxlva8mju9BI=
github.com/cloudwego/eino-ext/components/model/gemini v0.1.5/go.mod h1:yWVzN9Y5TU9MERRStVnfa28uXQbbAk+z/2SCi+G32Vc=
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0=
github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc=
github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk=
github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o=
github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/deitch/magic v0.0.0-20240306090643-c67ab88f10cb/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
github.com/eino-contrib/jsonschema v1.0.3/go.mod h1:cpnX4SyKjWjGC7iN2EbhxaTdLqGjCi0e9DxpLYxddD4=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/erikvarga/go-rpmdb v0.0.0-20240208180226-b97e041ef9af/go.mod h1:MiEorPk0IChAoCwpg2FXyqVgbNvOlPWZAYHqqIoDNoY=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/goark/errs v1.3.2/go.mod h1:ZsQucxaDFVfSB8I99j4bxkDRfNOrlKINwg72QMuRWKw=
github.com/goark/go-cvss v1.6.7/go.mod h1:qsmYCGTQnQqW/Lq1Z3lRCEarKD++nx7C+KgsG05MhDA=
github.com/gohugoio/hashstructure v0.5.0/go.mod h1:Ser0TniXuu/eauYmrwM4o64EBvySxNzITEOLlm4igec=
github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI=
github.com/google/go-cpy v0.0.0-20211218193943-a9c933c06932/go.mod h1:cC6EdPbj/17GFCPDK39NRarlMI+kt+O60S12cNB5J9Y=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/groob/plist v0.1.1/go.mod h1:itkABA+w2cw7x5nYUS/pLRef6ludkZKOigbROmCTaFw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY=
github.com/knadh/koanf/providers/env v1.0.0/go.mod h1:mzFyRZueYhb37oPmC1HAv/oGEEuyvJDA98r3XAa8Gak=
github.com/knadh/koanf/providers/file v1.1.2/go.mod h1:/faSBcv2mxPVjFrXck95qeoyoZ5myJ6uxN8OOVNJJCI=
github.com/knadh/koanf/providers/posflag v0.1.0/go.mod h1:SYg03v/t8ISBNrMBRMlojH8OsKowbkXV7giIbBVgbz0=
github.com/knadh/koanf/providers/structs v0.1.0/go.mod h1:sw2YZ3txUcqA3Z27gPlmmBzWn1h8Nt9O6EP/91MkcWE=
github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/labstack/echo-contrib v0.17.4/go.mod h1:9O7ZPAHUeMGTOAfg80YqQduHzt0CzLak36PZRldYrZ0=
github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/michaelkedar/xml v0.0.0-20250310223042-5d14c9302b17/go.mod h1:KUAB0Nhc2O/lzyPLuWF6Jm/HVC4GIRHWpxTWpy14WHM=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg=
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nats-io/nats.go v1.42.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nikolalohinski/gonja v1.5.3/go.mod h1:RmjwxNiXAEqcq1HeK5SSMmqFJvKOfTfXhkJv6YBtPa4=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
github.com/ossf/osv-schema/bindings/go v0.0.0-20250210065807-ab8a4f6e6389/go.mod h1:lILztSxHU7VsdlYqCnwgxSDBhbXMf7iEQWtldJCDXPo=
github.com/pandatix/go-cvss v0.6.2/go.mod h1:jDXYlQBZrc8nvrMUVVvTG8PhmuShOnKrxP53nOFkt8Q=
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c/go.mod h1:kwM/7r/rVluTE8qJbHAffduuqmSv4knVQT2IajGvSiA=
github.com/s2-streamstore/s2-sdk-go v0.14.0/go.mod h1:1a+v2sGqU+s5neI8XwqRJz78ktStkR+mZH/JEi9HNSo=
github.com/saferwall/pe v1.5.6/go.mod h1:mJx+PuptmNpoPFBNhWs/uDMFL/kTHVZIkg0d4OUJFbQ=
github.com/secDre4mer/pkcs7 v0.0.0-20240322103146-665324a4461d/go.mod h1:PegD7EVqlN88z7TpCqH92hHP+GBpfomGCCnw1PFtNOA=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f/go.mod h1:JqzWyvTuI2X4+9wOHmKSQCYxybB/8j6Ko43qVmXDuZg=
github.com/spdx/gordf v0.0.0-20221230105357-b735bd5aac89/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM=
github.com/spdx/tools-golang v0.5.3/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI=
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk=
github.com/vektra/mockery/v3 v3.3.2/go.mod h1:erK6TiB02iqKYIvBtegmiz9k/pQUQzE0RwMIb1Y/510=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA=
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.61.0/go.mod h1:/V0rmKWoHzXI2ROCfKE2PKPoo6hdlU1GRtzwzuO/3jc=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/vuln v1.0.4/go.mod h1:NbJdUQhX8jY++FtuhrXs2Eyx0yePo9pF7nPlIjo9aaQ=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
google.golang.org/api v0.235.0/go.mod h1:QpeJkemzkFKe5VCE/PMv7GsUfn9ZF+u+q1Q7w6ckxTg=
google.golang.org/genai v1.21.0/go.mod h1:QPj5NGJw+3wEOHg+PrsWwJKvG6UC84ex5FR7qAYsN/M=
google.golang.org/genproto v0.0.0-20250528174236-200df99c418a h1:KXuwdBmgjb4T3l4ZzXhP6HxxFKXD9FcK5/8qfJI4WwU=
google.golang.org/genproto v0.0.0-20250528174236-200df99c418a/go.mod h1:Nlk93rrS2X7rV8hiC2gh2A/AJspZhElz9Oh2KGsjLEY=
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:p3MLuOwURrGBRoEyFHBT3GjUwaCQVKeNqqWxlcISGdw=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gorm.io/driver/clickhouse v0.7.0/go.mod h1:TmNo0wcVTsD4BBObiRnCahUgHJHjBIwuRejHwYt3JRs=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
gorm.io/plugin/opentelemetry v0.1.14/go.mod h1:ZAp4v5vU1CCcK9Oo8/va5rl6NStrzpSU+a70evd+W/g=
gorm.io/plugin/prometheus v0.1.0/go.mod h1:5nrc/JrWCUNoDXCY4eOae/FK/J5WjQ0axXuFusCzdTc=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
www.velocidex.com/golang/regparser v0.0.0-20240404115756-2169ac0e3c09/go.mod h1:pxSECT5mWM3goJ4sxB4HCJNKnKqiAlpyT8XnvBwkLGU=
+33
View File
@@ -0,0 +1,33 @@
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"defaultBase": "main",
"analytics": false,
"namedInputs": {
"goWorkspaceSources": [
"{workspaceRoot}/**/*.go",
"{workspaceRoot}/go.mod",
"{workspaceRoot}/go.sum",
"{workspaceRoot}/.goreleaser.yaml",
"!{workspaceRoot}/packages/**",
"!{workspaceRoot}/scripts/**",
"!{workspaceRoot}/dist/**",
"!{workspaceRoot}/bin/**",
"!{workspaceRoot}/node_modules/**"
],
"goSyncSources": [
"{workspaceRoot}/scripts/sync-binaries/**/*.go",
"{workspaceRoot}/scripts/go.mod",
"{workspaceRoot}/scripts/go.sum"
],
"tsPackageSources": [
"{projectRoot}/src/**",
"{projectRoot}/tsconfig.json"
]
},
"targetDefaults": {
"build": { "cache": true },
"build-snapshot": { "cache": true },
"run": { "cache": true },
"typecheck": { "cache": true }
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"name": "pmg-workspace",
"private": true,
"devDependencies": {
"@types/node": "20.19.41",
"nx": "23.0.0-beta.11"
},
"packageManager": "pnpm@11.1.3+sha512.c85357fe17ca12dd23dd7071822666dfd7e3cb76fe214e3370b5ea2fb34f2a231185509b63e717f3cd0acb38dd3f8d82bcd5e8172400ae678b70ea4fbed0896d"
}
+28
View File
@@ -0,0 +1,28 @@
{
"name": "@safedep/pmg-darwin-arm64",
"version": "0.0.0",
"license": "Apache-2.0",
"homepage": "https://safedep.io",
"bugs": {
"url": "https://github.com/safedep/pmg/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/safedep/pmg.git"
},
"publishConfig": {
"access": "public"
},
"os": [
"darwin"
],
"cpu": [
"arm64"
],
"files": [
"bin/**"
],
"bin": {
"pmg": "bin/pmg"
}
}
+28
View File
@@ -0,0 +1,28 @@
{
"name": "@safedep/pmg-darwin-x64",
"version": "0.0.0",
"license": "Apache-2.0",
"homepage": "https://safedep.io",
"bugs": {
"url": "https://github.com/safedep/pmg/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/safedep/pmg.git"
},
"publishConfig": {
"access": "public"
},
"os": [
"darwin"
],
"cpu": [
"x64"
],
"files": [
"bin/**"
],
"bin": {
"pmg": "bin/pmg"
}
}
+28
View File
@@ -0,0 +1,28 @@
{
"name": "@safedep/pmg-linux-arm64",
"version": "0.0.0",
"license": "Apache-2.0",
"homepage": "https://safedep.io",
"bugs": {
"url": "https://github.com/safedep/pmg/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/safedep/pmg.git"
},
"publishConfig": {
"access": "public"
},
"os": [
"linux"
],
"cpu": [
"arm64"
],
"files": [
"bin/**"
],
"bin": {
"pmg": "bin/pmg"
}
}
+28
View File
@@ -0,0 +1,28 @@
{
"name": "@safedep/pmg-linux-x64",
"version": "0.0.0",
"license": "Apache-2.0",
"homepage": "https://safedep.io",
"bugs": {
"url": "https://github.com/safedep/pmg/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/safedep/pmg.git"
},
"publishConfig": {
"access": "public"
},
"os": [
"linux"
],
"cpu": [
"x64"
],
"files": [
"bin/**"
],
"bin": {
"pmg": "bin/pmg"
}
}
+28
View File
@@ -0,0 +1,28 @@
{
"name": "@safedep/pmg-win32-x64",
"version": "0.0.0",
"license": "Apache-2.0",
"homepage": "https://safedep.io",
"bugs": {
"url": "https://github.com/safedep/pmg/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/safedep/pmg.git"
},
"publishConfig": {
"access": "public"
},
"os": [
"win32"
],
"cpu": [
"x64"
],
"files": [
"bin/**"
],
"bin": {
"pmg": "bin/pmg.exe"
}
}
+39
View File
@@ -0,0 +1,39 @@
{
"name": "@safedep/pmg",
"version": "0.0.0",
"description": "PMG - Package Manager Guard: protect developers from malicious packages",
"license": "Apache-2.0",
"keywords": ["security", "supply-chain", "packages", "safedep", "pmg"],
"engines": { "node": ">=18" },
"repository": {
"type": "git",
"url": "git+https://github.com/safedep/pmg.git"
},
"homepage": "https://safedep.io",
"bugs": { "url": "https://github.com/safedep/pmg/issues" },
"publishConfig": { "access": "public" },
"type": "module",
"bin": {
"pmg": "dist/bin.cjs"
},
"files": [
"dist/**"
],
"optionalDependencies": {
"@safedep/pmg-linux-x64": "workspace:*",
"@safedep/pmg-linux-arm64": "workspace:*",
"@safedep/pmg-darwin-x64": "workspace:*",
"@safedep/pmg-darwin-arm64": "workspace:*",
"@safedep/pmg-win32-x64": "workspace:*"
},
"scripts": {
"prepack": "cp ../../README.md README.md",
"build": "tsdown",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"devDependencies": {
"@types/node": "25.9.1",
"tsdown": "0.22.0",
"typescript": "6.0.3"
}
}
+35
View File
@@ -0,0 +1,35 @@
{
"name": "@safedep/pmg",
"root": "packages/pmg",
"targets": {
"build": {
"executor": "nx:run-commands",
"dependsOn": [{"projects": "sync-binaries", "target": "run"}],
"options": {
"command": "pnpm run build",
"cwd": "{projectRoot}"
},
"inputs": ["tsPackageSources", "{projectRoot}/tsdown.config.ts"],
"outputs": ["{projectRoot}/dist"]
},
"build-release": {
"executor": "nx:run-commands",
"dependsOn": [{"projects": "sync-binaries", "target": "run-release"}],
"options": {
"command": "pnpm run build",
"cwd": "{projectRoot}"
},
"inputs": ["tsPackageSources", "{projectRoot}/tsdown.config.ts"],
"outputs": ["{projectRoot}/dist"],
"cache": false
},
"typecheck": {
"executor": "nx:run-commands",
"options": {
"command": "pnpm run typecheck",
"cwd": "{projectRoot}"
},
"inputs": ["tsPackageSources"]
}
}
}
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env node
import { createRequire } from "node:module";
import { dirname, join } from "node:path";
import { existsSync } from "node:fs";
import { spawn } from "node:child_process";
const require = createRequire(import.meta.url);
function pkgNameForHost(): string {
const platform = process.platform; // linux | darwin | win32
const arch = process.arch; // x64 | arm64
return `@safedep/pmg-${platform}-${arch}`;
}
function findBinaryPath(pkgName: string): string {
const pkgJsonPath = require.resolve(`${pkgName}/package.json`);
const pkgRoot = dirname(pkgJsonPath);
const exe = process.platform === "win32" ? "pmg.exe" : "pmg";
const p = join(pkgRoot, "bin", exe);
if (!existsSync(p)) {
throw new Error(
`Binary not found at ${p}. The platform package "${pkgName}" is installed but does not contain bin/${exe}.`
);
}
return p;
}
function main() {
const pkgName = pkgNameForHost();
let binPath: string;
try {
binPath = findBinaryPath(pkgName);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
console.error(
[
"Failed to locate the platform binary.",
`Host: ${process.platform}/${process.arch}`,
`Expected platform package: ${pkgName}`,
msg,
"",
"Common causes:",
"- optionalDependencies were omitted during install",
"- this platform/arch is not published yet",
"- the platform package was published without the binary in bin/",
].join("\n")
);
process.exit(1);
}
const child = spawn(binPath, process.argv.slice(2), { stdio: "inherit" });
child.on("exit", (code: number | null, signal: string | null) => {
if (signal) {
process.kill(process.pid, signal as NodeJS.Signals);
return;
}
process.exit(code ?? 1);
});
child.on("error", (error: Error) => {
console.error(`Failed to spawn the binary: ${error}`);
process.exit(1);
});
}
main();
+11
View File
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"skipLibCheck": true,
"types": ["node"]
},
"include": ["src/**/*"]
}
+8
View File
@@ -0,0 +1,8 @@
import { defineConfig } from "tsdown";
export default defineConfig({
entry: ["src/bin.ts"],
format: ["cjs"],
clean: true,
minify: true,
});
+1681
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
packages:
- 'packages/*'
allowBuilds:
nx: true
dedupePeers: true
minimumReleaseAge: 4320
+55
View File
@@ -0,0 +1,55 @@
{
"name": "pmg",
"root": ".",
"targets": {
"build-snapshot": {
"executor": "nx:run-commands",
"options": {
"command": "goreleaser build --clean --snapshot",
"cwd": "{workspaceRoot}"
},
"inputs": ["goWorkspaceSources"],
"outputs": ["{workspaceRoot}/dist"]
},
"build-release": {
"executor": "nx:run-commands",
"options": {
"command": "goreleaser release --clean",
"cwd": "{workspaceRoot}"
},
"inputs": ["goWorkspaceSources"],
"outputs": ["{workspaceRoot}/dist"],
"cache": false
},
"build-dev": {
"executor": "nx:noop",
"dependsOn": [{"projects": "@safedep/pmg", "target": "build"}]
},
"verify": {
"executor": "nx:run-commands",
"dependsOn": [{"projects": "pmg", "target": "build-dev"}],
"options": {
"command": "node packages/pmg/dist/bin.cjs version",
"cwd": "{workspaceRoot}"
}
},
"publish-npm": {
"executor": "nx:run-commands",
"dependsOn": [{"projects": "@safedep/pmg", "target": "build-release"}],
"options": {
"command": "pnpm publish -r --provenance --no-git-checks --access public",
"cwd": "{workspaceRoot}"
},
"cache": false
},
"release-preflight": {
"executor": "nx:run-commands",
"dependsOn": [{"projects": "pmg", "target": "verify"}],
"options": {
"command": "pnpm publish -r --dry-run --no-git-checks",
"cwd": "{workspaceRoot}"
},
"cache": false
}
}
}
-17
View File
@@ -1,17 +0,0 @@
# Exclude the actual binary (but keep the wrapper script)
bin/pmg
bin/pmg.exe
# Exclude temp files and directories
temp/
.temp/
# Exclude development files
node_modules/
.git/
.gitignore
*.log
.DS_Store
# Keep only the wrapper script in bin/
!bin/pmg.js
-66
View File
@@ -1,66 +0,0 @@
<div align="center">
<img src="https://raw.githubusercontent.com/safedep/pmg/main/docs/assets/pmg-banner.png" alt="PMG banner">
</div>
# Package Manager Guard (PMG)
PMG intercepts package installs and checks them for malware before code executes. Install it once, and your usual package manager workflows can stay the same.
This package is the npm distribution of PMG. The main project README at [`github.com/safedep/pmg`](https://github.com/safedep/pmg) is the source of truth for full documentation.
## Why PMG?
- Protects developers and AI coding agents from malicious packages
- Wraps tools like `npm`, `pnpm`, `yarn`, `pip`, `poetry`, and `uv`
- Adds sandboxing and install-time security checks with minimal workflow changes
## Install
```bash
npm install -g @safedep/pmg
```
You can also install PMG with Homebrew:
```bash
brew install safedep/tap/pmg
```
## Quick Start
Set up PMG so your normal package manager commands are protected automatically:
```bash
pmg setup install
```
After setup, restart your terminal and keep using your tools as usual:
```bash
npm install express
pnpm add react
pip install requests
```
If you prefer, you can also run package manager commands through PMG directly:
```bash
pmg npm install express
pmg pnpm add react
pmg pip install requests
```
## Platform Support
- macOS
- Linux
- Windows
Requires Node.js 14 or higher.
## Learn More
For complete documentation, installation options, troubleshooting, and project updates, see:
- [Main README](https://github.com/safedep/pmg)
- [Quickstart Docs](https://docs.safedep.io/pmg/quickstart)
-76
View File
@@ -1,76 +0,0 @@
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const { spawn } = require("child_process");
const { ORG_NAME, PACKAGE_NAME, BINARY_NAME } = require("../config");
const BINARY_NAME_WITH_EXT =
process.platform === "win32" ? `${BINARY_NAME}.exe` : BINARY_NAME;
const BINARY_PATH = path.join(__dirname, BINARY_NAME_WITH_EXT);
function main() {
// Check if binary exists
if (!fs.existsSync(BINARY_PATH)) {
console.error(`${BINARY_NAME_WITH_EXT} binary not found`);
console.error(
`Try reinstalling: npm install -g ${ORG_NAME}/${PACKAGE_NAME}`,
);
process.exit(1);
}
// Verify binary is executable
try {
fs.accessSync(BINARY_PATH, fs.constants.F_OK | fs.constants.X_OK);
} catch (error) {
console.error(`${BINARY_NAME_WITH_EXT} is not executable`);
console.error(
`Try reinstalling: npm install -g ${ORG_NAME}/${PACKAGE_NAME}`,
);
process.exit(1);
}
// Pass all arguments to the binary
const args = process.argv.slice(2);
// Spawn the binary with inherited stdio for proper terminal interaction
const child = spawn(BINARY_PATH, args, {
stdio: "inherit",
windowsHide: false,
});
// Handle process termination
child.on("error", (error) => {
console.error(
`❌ Failed to execute ${BINARY_NAME_WITH_EXT}: ${error.message}`,
);
console.error(
`Try reinstalling: npm install -g ${ORG_NAME}/${PACKAGE_NAME}`,
);
process.exit(1);
});
// Exit with the same code as the child process
child.on("exit", (code, signal) => {
if (signal) {
process.kill(process.pid, signal);
} else {
process.exit(code || 0);
}
});
// Handle termination signals
process.on("SIGTERM", () => {
child.kill("SIGTERM");
});
process.on("SIGINT", () => {
child.kill("SIGINT");
});
}
if (require.main === module) {
main();
}
module.exports = { main };
-34
View File
@@ -1,34 +0,0 @@
// Configuration for npm binary wrapper
const ORG_NAME = "@safedep";
const PACKAGE_NAME = "pmg";
const BINARY_NAME = "pmg";
// GitHub repository information for releases
const REPO_OWNER = "safedep";
const REPO_NAME = "pmg";
// GitHub releases base URL (constructed from repo info)
const GITHUB_RELEASES_BASE = `https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/download`;
// Platform-specific binary filename patterns (GoReleaser format)
const BINARY_PATTERNS = {
"darwin-x64": `${BINARY_NAME}_Darwin_all.tar.gz`,
"darwin-arm64": `${BINARY_NAME}_Darwin_all.tar.gz`,
"linux-x64": `${BINARY_NAME}_Linux_x86_64.tar.gz`,
"linux-arm64": `${BINARY_NAME}_Linux_arm64.tar.gz`,
"linux-ia32": `${BINARY_NAME}_Linux_i386.tar.gz`,
"win32-x64": `${BINARY_NAME}_Windows_x86_64.zip`,
"win32-arm64": `${BINARY_NAME}_Windows_arm64.zip`,
"win32-ia32": `${BINARY_NAME}_Windows_i386.zip`,
};
module.exports = {
ORG_NAME,
PACKAGE_NAME,
BINARY_NAME,
REPO_OWNER,
REPO_NAME,
GITHUB_RELEASES_BASE,
BINARY_PATTERNS,
};
-243
View File
@@ -1,243 +0,0 @@
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const os = require("os");
const https = require("https");
const crypto = require("crypto");
const { execSync } = require("child_process");
const {
BINARY_NAME,
REPO_OWNER,
REPO_NAME,
GITHUB_RELEASES_BASE,
BINARY_PATTERNS,
} = require("./config");
// Read version from package.json with strict validation
function getValidatedVersion() {
try {
const packageJson = JSON.parse(
fs.readFileSync(path.join(__dirname, "package.json"), "utf8"),
);
const version = packageJson.version;
// Strict validation: must be valid semver (x.y.z)
if (!/^\d+\.\d+\.\d+$/.test(version)) {
throw new Error(`Invalid version format: ${version}`);
}
return `v${version}`;
} catch (error) {
throw new Error(`Failed to read valid version: ${error.message}`);
}
}
const RELEASE_VERSION = getValidatedVersion();
const BASE_URL = `${GITHUB_RELEASES_BASE}/${RELEASE_VERSION}`;
// Platform-specific binary URLs (constructed from config)
const BINARY_URLS = {};
Object.keys(BINARY_PATTERNS).forEach((platform) => {
BINARY_URLS[platform] = `${BASE_URL}/${BINARY_PATTERNS[platform]}`;
});
const CHECKSUMS_URL = `${BASE_URL}/checksums.txt`;
function getPlatformKey() {
const platform = process.platform;
const arch = process.arch;
return `${platform}-${arch}`;
}
function downloadFile(url, dest, maxRedirects = 5) {
return new Promise((resolve, reject) => {
if (maxRedirects < 0) {
reject(new Error("Too many redirects"));
return;
}
const file = fs.createWriteStream(dest);
https
.get(url, (response) => {
if (response.statusCode === 302 || response.statusCode === 301) {
file.close();
fs.unlink(dest, () => {});
return downloadFile(response.headers.location, dest, maxRedirects - 1)
.then(resolve)
.catch(reject);
}
if (response.statusCode !== 200) {
file.close();
fs.unlink(dest, () => {});
reject(new Error(`Download failed: ${response.statusCode}`));
return;
}
response.pipe(file);
file.on("finish", () => {
file.close();
resolve();
});
file.on("error", (err) => {
fs.unlink(dest, () => {});
reject(err);
});
})
.on("error", reject);
});
}
function calculateChecksum(filePath) {
const fileBuffer = fs.readFileSync(filePath);
const hashSum = crypto.createHash("sha256");
hashSum.update(fileBuffer);
return hashSum.digest("hex");
}
function validateChecksum(filePath, expectedChecksum) {
const actualChecksum = calculateChecksum(filePath);
return actualChecksum === expectedChecksum;
}
function extractArchive(archivePath, extractDir) {
const isZip = archivePath.endsWith(".zip");
if (isZip) {
if (process.platform === "win32") {
execSync(
`powershell -NoProfile -Command "Expand-Archive -Force -Path '${archivePath}' -DestinationPath '${extractDir}'"`,
{ stdio: "pipe" },
);
} else {
execSync(`unzip -o "${archivePath}" -d "${extractDir}"`, {
stdio: "pipe",
});
}
} else {
execSync(`tar -xzf "${archivePath}" -C "${extractDir}"`, { stdio: "pipe" });
}
}
async function install() {
let tempWorkspace;
try {
console.log("📦 Installing PMG binary...");
// Get platform-specific URL
const platformKey = getPlatformKey();
const binaryUrl = BINARY_URLS[platformKey];
if (!binaryUrl) {
throw new Error(`Unsupported platform: ${platformKey}`);
}
console.log(`🔍 Platform: ${platformKey}`);
console.log(`📡 Version: ${RELEASE_VERSION}`);
// Create directories
const binDir = path.join(__dirname, "bin");
tempWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), "pmg-install-"));
fs.mkdirSync(binDir, { recursive: true });
// Download binary archive
const archiveFilename = path.basename(binaryUrl);
const archivePath = path.join(tempWorkspace, archiveFilename);
console.log(`⬇️ Downloading binary...`);
await downloadFile(binaryUrl, archivePath);
// Download checksums
const checksumsPath = path.join(tempWorkspace, "checksums.txt");
console.log(`⬇️ Downloading checksums...`);
await downloadFile(CHECKSUMS_URL, checksumsPath);
// Parse checksums file
const checksumsContent = fs.readFileSync(checksumsPath, "utf8");
const checksumLines = checksumsContent.split("\n");
let expectedChecksum = null;
for (const line of checksumLines) {
if (line.includes(archiveFilename)) {
expectedChecksum = line.split(/\s+/)[0];
break;
}
}
if (!expectedChecksum) {
throw new Error(`Checksum not found for ${archiveFilename}`);
}
// Validate checksum
console.log(`🔐 Validating checksum...`);
if (!validateChecksum(archivePath, expectedChecksum)) {
throw new Error(
"Checksum validation failed - binary may be corrupted or tampered",
);
}
console.log(`✅ Checksum validated`);
// Extract archive
console.log(`📂 Extracting binary...`);
extractArchive(archivePath, tempWorkspace);
// Find and move binary
const binaryName =
process.platform === "win32" ? `${BINARY_NAME}.exe` : BINARY_NAME;
const extractedBinaryPath = path.join(tempWorkspace, binaryName);
const finalBinaryPath = path.join(binDir, binaryName);
if (!fs.existsSync(extractedBinaryPath)) {
throw new Error(
`Binary not found at expected location: ${extractedBinaryPath}`,
);
}
// Move binary to final location (handle cross-device links)
try {
fs.renameSync(extractedBinaryPath, finalBinaryPath);
} catch (error) {
if (error.code === "EXDEV") {
// Cross-device link not permitted, copy and delete instead
fs.copyFileSync(extractedBinaryPath, finalBinaryPath);
fs.unlinkSync(extractedBinaryPath);
} else {
throw error;
}
}
// Make executable on Unix systems
if (process.platform !== "win32") {
fs.chmodSync(finalBinaryPath, "755");
}
// Clean up
fs.rmSync(tempWorkspace, { recursive: true, force: true });
console.log("✅ PMG binary installed successfully!");
} catch (error) {
console.error("❌ Installation failed:", error.message);
// Clean up on failure
try {
if (tempWorkspace && fs.existsSync(tempWorkspace)) {
fs.rmSync(tempWorkspace, { recursive: true, force: true });
}
} catch (cleanupError) {
console.warn("⚠️ Failed to clean up:", cleanupError.message);
}
process.exit(1);
}
}
// Run installation
install();
-57
View File
@@ -1,57 +0,0 @@
{
"name": "@safedep/pmg",
"description": "PMG protects developers from getting compromised by malicious packages",
"main": "bin/pmg.js",
"bin": {
"pmg": "bin/pmg.js"
},
"scripts": {
"preinstall": "echo \"Installing PMG binary for your platform...\"",
"postinstall": "node install.js"
},
"keywords": [
"security",
"package-manager",
"malicious-packages",
"npm",
"cli",
"vulnerability",
"dependency-security",
"safedep"
],
"author": "SafeDep <devops@safedep.io>",
"license": "Apache-2.0",
"homepage": "https://github.com/safedep/pmg#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/safedep/pmg.git"
},
"bugs": {
"url": "https://github.com/safedep/pmg/issues"
},
"engines": {
"node": ">=14"
},
"os": [
"darwin",
"linux",
"win32"
],
"cpu": [
"x64",
"arm64",
"ia32"
],
"files": [
"bin/pmg.js",
"install.js",
"config.js",
"test.js",
"README.md",
".npmignore"
],
"publishConfig": {
"access": "public"
},
"dependencies": {}
}
+24
View File
@@ -0,0 +1,24 @@
module github.com/safedep/pmg/scripts
go 1.25.1
require (
github.com/go-playground/validator/v10 v10.30.2
github.com/stretchr/testify v1.11.1
)
require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/text v0.35.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+28
View File
@@ -0,0 +1,28 @@
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+4
View File
@@ -0,0 +1,4 @@
# sync-binaries
Copies GoReleaser build artifacts into the npm platform packages under `packages/`.
Run via Nx: `pnpm nx run sync-binaries:run` (snapshot) or `pnpm nx run sync-binaries:run-release` (release).
+169
View File
@@ -0,0 +1,169 @@
// Sync binaries to packages directory from goreleaser's dist/ directory.
package main
import (
"encoding/json"
"flag"
"fmt"
"io"
"log"
"os"
"path/filepath"
"github.com/go-playground/validator/v10"
)
type GoreleaserArtifact struct {
Path string `json:"path" validate:"required"`
Goos string `json:"goos" validate:"required"`
Goarch string `json:"goarch"`
Type string `json:"type" validate:"required"`
}
var goArchToNodeArchMap = map[string]string{
"amd64": "x64",
"386": "x86",
"arm64": "arm64",
}
var goOsToNodeOsMap = map[string]string{
"windows": "win32",
}
func main() {
artifactsPath := flag.String("artifacts-path", "dist/artifacts.json", "Path to goreleaser artifacts.json")
packagesPath := flag.String("packages-path", "./packages", "Path to the npm packages directory")
strict := flag.Bool("strict", true, "Fail if a package directory does not exist for a built artifact")
setVersion := flag.String("set-version", "", "Semver x.y.z to write into all non-private package.json files under packages-path")
verifyBins := flag.Bool("verify-bins", false, "Verify that each platform package has a non-empty bin/ directory after sync")
flag.Parse()
artifactsBytes, err := os.ReadFile(*artifactsPath)
if err != nil {
log.Fatalf("failed to read artifacts.json (did you run goreleaser build?): %v", err)
}
var artifacts []GoreleaserArtifact
if err := json.Unmarshal(artifactsBytes, &artifacts); err != nil {
log.Fatalf("failed to parse artifacts.json: %v", err)
}
validate := validator.New(validator.WithRequiredStructEnabled())
for _, artifact := range artifacts {
switch artifact.Type {
case "Binary":
if err := validate.Struct(artifact); err != nil {
log.Printf("skipping invalid artifact: %v", err)
continue
}
// goreleaser v2 emits a universal macOS binary as type "Binary"
// with goarch "all" when universal_binaries.replace is true.
// Copy it to both darwin platform packages.
if artifact.Goos == "darwin" && artifact.Goarch == "all" {
for _, nodeArch := range []string{"x64", "arm64"} {
packagePath := filepath.Join(*packagesPath, fmt.Sprintf("pmg-darwin-%s", nodeArch))
if err := copyToBin(artifact.Path, packagePath, "pmg", *strict); err != nil {
log.Fatalf("sync darwin universal -> %s: %v", nodeArch, err)
}
}
continue
}
if err := syncBinary(artifact, *packagesPath, *strict); err != nil {
log.Fatalf("sync: %v", err)
}
case "Universal Binary":
// Retained for compatibility with older goreleaser versions that
// emitted a distinct type for universal binaries.
if artifact.Goos != "darwin" {
log.Printf("unexpected universal binary for goos=%s, skipping", artifact.Goos)
continue
}
for _, nodeArch := range []string{"x64", "arm64"} {
packagePath := filepath.Join(*packagesPath, fmt.Sprintf("pmg-darwin-%s", nodeArch))
if err := copyToBin(artifact.Path, packagePath, "pmg", *strict); err != nil {
log.Fatalf("sync darwin universal -> %s: %v", nodeArch, err)
}
}
}
}
if *setVersion != "" {
if err := setPackageVersions(*packagesPath, *setVersion); err != nil {
log.Fatalf("set-version: %v", err)
}
}
if *verifyBins {
if err := verifyPackageBins(*packagesPath); err != nil {
log.Fatalf("verify-bins: %v", err)
}
}
}
func syncBinary(artifact GoreleaserArtifact, packagesPath string, strict bool) error {
nodeArch, ok := goArchToNodeArchMap[artifact.Goarch]
if !ok {
nodeArch = artifact.Goarch
}
nodeOs, ok := goOsToNodeOsMap[artifact.Goos]
if !ok {
nodeOs = artifact.Goos
}
packagePath := filepath.Join(packagesPath, fmt.Sprintf("pmg-%s-%s", nodeOs, nodeArch))
binName := "pmg"
if artifact.Goos == "windows" {
binName = "pmg.exe"
}
return copyToBin(artifact.Path, packagePath, binName, strict)
}
func copyToBin(src, packagePath, binName string, strict bool) error {
if _, err := os.Stat(packagePath); os.IsNotExist(err) {
if strict {
return fmt.Errorf("package directory %s does not exist (add the platform package or remove the goreleaser target)", packagePath)
}
log.Printf("package directory %s does not exist, skipping", packagePath)
return nil
}
binDir := filepath.Join(packagePath, "bin")
if err := os.MkdirAll(binDir, 0o755); err != nil { //nolint:gosec // bin/ needs execute permission
return fmt.Errorf("create bin dir %s: %w", binDir, err)
}
dst := filepath.Join(binDir, binName)
log.Printf("copying %s -> %s", src, dst)
return copyFile(src, dst)
}
func copyFile(src, dst string) error {
srcFile, err := os.Open(src)
if err != nil {
return fmt.Errorf("open source: %w", err)
}
defer srcFile.Close() //nolint:errcheck // read-only; close error is negligible
dstFile, err := os.Create(dst)
if err != nil {
return fmt.Errorf("create destination: %w", err)
}
defer dstFile.Close() //nolint:errcheck // Sync() is called explicitly before return; deferred close is best-effort
if _, err := io.Copy(dstFile, srcFile); err != nil {
return fmt.Errorf("copy: %w", err)
}
if err := dstFile.Sync(); err != nil {
return fmt.Errorf("sync: %w", err)
}
srcInfo, err := os.Stat(src)
if err != nil {
return fmt.Errorf("stat source: %w", err)
}
return os.Chmod(dst, srcInfo.Mode())
}
+38
View File
@@ -0,0 +1,38 @@
{
"name": "sync-binaries",
"root": "scripts/sync-binaries",
"targets": {
"run": {
"executor": "nx:run-commands",
"dependsOn": [{"projects": "pmg", "target": "build-snapshot"}],
"options": {
"command": "go run ./scripts/sync-binaries/ --strict --verify-bins --artifacts-path dist/artifacts.json --packages-path ./packages",
"cwd": "{workspaceRoot}"
},
"inputs": ["goSyncSources", "{workspaceRoot}/dist/artifacts.json"],
"outputs": [
"{workspaceRoot}/packages/pmg-linux-x64/bin",
"{workspaceRoot}/packages/pmg-linux-arm64/bin",
"{workspaceRoot}/packages/pmg-darwin-x64/bin",
"{workspaceRoot}/packages/pmg-darwin-arm64/bin",
"{workspaceRoot}/packages/pmg-win32-x64/bin"
]
},
"run-release": {
"executor": "nx:run-commands",
"options": {
"command": "go run ./scripts/sync-binaries/ --strict --verify-bins --artifacts-path dist/artifacts.json --packages-path ./packages --set-version $VERSION",
"cwd": "{workspaceRoot}"
},
"inputs": ["goSyncSources", "{workspaceRoot}/dist/artifacts.json"],
"outputs": [
"{workspaceRoot}/packages/pmg-linux-x64/bin",
"{workspaceRoot}/packages/pmg-linux-arm64/bin",
"{workspaceRoot}/packages/pmg-darwin-x64/bin",
"{workspaceRoot}/packages/pmg-darwin-arm64/bin",
"{workspaceRoot}/packages/pmg-win32-x64/bin"
],
"cache": false
}
}
}
+137
View File
@@ -0,0 +1,137 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
)
var semverRe = regexp.MustCompile(`^\d+\.\d+\.\d+$`)
// packageMeta holds the fields from package.json that drive sync decisions.
// Only the fields we actually branch on are declared; json.Unmarshal ignores
// the rest, so the full file content is never disturbed.
type packageMeta struct {
Private bool `json:"private"`
OS []string `json:"os"`
}
func readPackageMeta(path string) (*packageMeta, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var meta packageMeta
if err := json.Unmarshal(data, &meta); err != nil {
return nil, fmt.Errorf("parse: %w", err)
}
return &meta, nil
}
// setPackageVersions scans every immediate subdirectory of packagesPath for a
// package.json, skips those with "private": true, and writes version to the
// rest. Returns an error on the first failure.
func setPackageVersions(packagesPath, version string) error {
if !semverRe.MatchString(version) {
return fmt.Errorf("invalid version %q: must be x.y.z", version)
}
entries, err := os.ReadDir(packagesPath)
if err != nil {
return fmt.Errorf("read packages dir: %w", err)
}
for _, entry := range entries {
if !entry.IsDir() {
continue
}
path := filepath.Join(packagesPath, entry.Name(), "package.json")
if err := setVersionInPackageJSON(path, version); err != nil {
return fmt.Errorf("package %s: %w", entry.Name(), err)
}
}
return nil
}
// versionFieldRe is used to swap the "version" field in raw JSON bytes instead
// of round-tripping through a Go struct, which would re-serialize arrays and
// destroy inline formatting (e.g. "os": ["linux"] would expand to multi-line).
// Anchoring to start-of-line prevents false matches inside string values of
// other keys. Group 1 captures leading whitespace so indentation is unchanged.
var versionFieldRe = regexp.MustCompile(`(?m)^(\s*)"version"\s*:\s*"[^"]*"`)
// setVersionInPackageJSON reads the file at path, sets "version" to version,
// and writes it back. A missing file is silently skipped. Packages with
// "private": true are skipped unchanged.
//
// The replacement is done on the raw bytes so all other formatting (key order,
// inline arrays, whitespace) is preserved byte-for-byte.
func setVersionInPackageJSON(path, version string) error {
data, err := os.ReadFile(path)
if os.IsNotExist(err) {
return nil
}
if err != nil {
return fmt.Errorf("read: %w", err)
}
var meta packageMeta
if err := json.Unmarshal(data, &meta); err != nil {
return fmt.Errorf("parse: %w", err)
}
if meta.Private {
return nil
}
repl := fmt.Appendf(nil, "${1}\"version\": \"%s\"", version)
updated := versionFieldRe.ReplaceAll(data, repl)
// 0o644: package.json must be world-readable for npm tooling.
return os.WriteFile(path, updated, 0o644) //nolint:gosec
}
// verifyPackageBins checks that every platform package under packagesPath has a
// non-empty bin/ directory. Platform packages are identified by the presence of
// an "os" field in their package.json; packages without that field (e.g. the
// meta/shim package) and private packages are skipped.
func verifyPackageBins(packagesPath string) error {
entries, err := os.ReadDir(packagesPath)
if err != nil {
return fmt.Errorf("read packages dir: %w", err)
}
var missing []string
for _, entry := range entries {
if !entry.IsDir() {
continue
}
pkgJSONPath := filepath.Join(packagesPath, entry.Name(), "package.json")
meta, err := readPackageMeta(pkgJSONPath)
if os.IsNotExist(err) {
continue
}
if err != nil {
return fmt.Errorf("%s: %w", entry.Name(), err)
}
if meta.Private || len(meta.OS) == 0 {
continue
}
binDir := filepath.Join(packagesPath, entry.Name(), "bin")
binEntries, err := os.ReadDir(binDir)
if err != nil || len(binEntries) == 0 {
missing = append(missing, entry.Name())
}
}
if len(missing) > 0 {
return fmt.Errorf("platform packages missing bin/: %s", strings.Join(missing, ", "))
}
return nil
}
+246
View File
@@ -0,0 +1,246 @@
package main
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// makeDir creates a directory for test fixtures.
// 0o755 is the conventional permission for directories that need traversal.
func makeDir(t *testing.T, path string) {
t.Helper()
require.NoError(t, os.MkdirAll(path, 0o755)) //nolint:gosec
}
// writeTestFile writes a file for test fixtures.
// 0o644 is the conventional permission for config files like package.json.
func writeTestFile(t *testing.T, path string, content []byte) {
t.Helper()
require.NoError(t, os.WriteFile(path, content, 0o644)) //nolint:gosec
}
func writePkgJSON(t *testing.T, dir, name string, content map[string]any) string {
t.Helper()
pkgDir := filepath.Join(dir, name)
makeDir(t, pkgDir)
data, err := json.MarshalIndent(content, "", " ")
require.NoError(t, err)
path := filepath.Join(pkgDir, "package.json")
writeTestFile(t, path, append(data, '\n'))
return path
}
func readVersion(t *testing.T, path string) string {
t.Helper()
data, err := os.ReadFile(path)
require.NoError(t, err)
var pkg map[string]any
require.NoError(t, json.Unmarshal(data, &pkg))
v, _ := pkg["version"].(string)
return v
}
func TestSetPackageVersions(t *testing.T) {
t.Run("sets version in all non-private packages", func(t *testing.T) {
dir := t.TempDir()
pathA := writePkgJSON(t, dir, "pkg-a", map[string]any{"name": "pkg-a", "version": "0.0.0"})
pathB := writePkgJSON(t, dir, "pkg-b", map[string]any{"name": "pkg-b", "version": "0.0.0"})
require.NoError(t, setPackageVersions(dir, "1.2.3"))
assert.Equal(t, "1.2.3", readVersion(t, pathA))
assert.Equal(t, "1.2.3", readVersion(t, pathB))
})
t.Run("skips private packages", func(t *testing.T) {
dir := t.TempDir()
pathPriv := writePkgJSON(t, dir, "private-pkg", map[string]any{
"name": "private-pkg",
"version": "0.0.0",
"private": true,
})
pathPub := writePkgJSON(t, dir, "public-pkg", map[string]any{"name": "public-pkg", "version": "0.0.0"})
require.NoError(t, setPackageVersions(dir, "2.0.0"))
assert.Equal(t, "0.0.0", readVersion(t, pathPriv))
assert.Equal(t, "2.0.0", readVersion(t, pathPub))
})
t.Run("skips subdirectories without package.json", func(t *testing.T) {
dir := t.TempDir()
makeDir(t, filepath.Join(dir, "no-pkg-json"))
pathA := writePkgJSON(t, dir, "pkg-a", map[string]any{"name": "pkg-a", "version": "0.0.0"})
require.NoError(t, setPackageVersions(dir, "3.0.0"))
assert.Equal(t, "3.0.0", readVersion(t, pathA))
})
t.Run("rejects invalid semver", func(t *testing.T) {
err := setPackageVersions(t.TempDir(), "not-a-version")
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid version")
})
t.Run("returns error when packages dir is missing", func(t *testing.T) {
err := setPackageVersions("/nonexistent/path", "1.0.0")
require.Error(t, err)
})
}
func writeBinary(t *testing.T, dir, pkgName, binName string) {
t.Helper()
binDir := filepath.Join(dir, pkgName, "bin")
makeDir(t, binDir)
// 0o755: binary files need execute permission.
require.NoError(t, os.WriteFile(filepath.Join(binDir, binName), []byte("binary"), 0o755)) //nolint:gosec
}
func TestVerifyPackageBins(t *testing.T) {
t.Run("passes when all platform packages have binaries", func(t *testing.T) {
dir := t.TempDir()
writePkgJSON(t, dir, "cli-linux-x64", map[string]any{
"name": "cli-linux-x64", "version": "0.0.0", "os": []string{"linux"},
})
writeBinary(t, dir, "cli-linux-x64", "pmg")
writePkgJSON(t, dir, "cli-darwin-arm64", map[string]any{
"name": "cli-darwin-arm64", "version": "0.0.0", "os": []string{"darwin"},
})
writeBinary(t, dir, "cli-darwin-arm64", "pmg")
require.NoError(t, verifyPackageBins(dir))
})
t.Run("fails when a platform package has an empty bin/", func(t *testing.T) {
dir := t.TempDir()
writePkgJSON(t, dir, "cli-linux-x64", map[string]any{
"name": "cli-linux-x64", "version": "0.0.0", "os": []string{"linux"},
})
makeDir(t, filepath.Join(dir, "cli-linux-x64", "bin"))
err := verifyPackageBins(dir)
require.Error(t, err)
assert.Contains(t, err.Error(), "cli-linux-x64")
})
t.Run("fails when a platform package has no bin/ directory", func(t *testing.T) {
dir := t.TempDir()
writePkgJSON(t, dir, "cli-linux-x64", map[string]any{
"name": "cli-linux-x64", "version": "0.0.0", "os": []string{"linux"},
})
err := verifyPackageBins(dir)
require.Error(t, err)
assert.Contains(t, err.Error(), "cli-linux-x64")
})
t.Run("skips meta packages without os field", func(t *testing.T) {
dir := t.TempDir()
writePkgJSON(t, dir, "cli", map[string]any{
"name": "cli", "version": "0.0.0",
})
require.NoError(t, verifyPackageBins(dir))
})
t.Run("skips private packages", func(t *testing.T) {
dir := t.TempDir()
writePkgJSON(t, dir, "cli-private", map[string]any{
"name": "cli-private", "version": "0.0.0", "os": []string{"linux"}, "private": true,
})
require.NoError(t, verifyPackageBins(dir))
})
t.Run("reports multiple missing packages", func(t *testing.T) {
dir := t.TempDir()
writePkgJSON(t, dir, "cli-linux-x64", map[string]any{
"name": "cli-linux-x64", "version": "0.0.0", "os": []string{"linux"},
})
writePkgJSON(t, dir, "cli-darwin-x64", map[string]any{
"name": "cli-darwin-x64", "version": "0.0.0", "os": []string{"darwin"},
})
err := verifyPackageBins(dir)
require.Error(t, err)
assert.Contains(t, err.Error(), "cli-linux-x64")
assert.Contains(t, err.Error(), "cli-darwin-x64")
})
}
func TestSetVersionInPackageJSON(t *testing.T) {
t.Run("writes version field", func(t *testing.T) {
dir := t.TempDir()
path := writePkgJSON(t, dir, "pkg", map[string]any{"name": "pkg", "version": "0.0.0"})
require.NoError(t, setVersionInPackageJSON(filepath.Join(dir, "pkg", "package.json"), "4.5.6"))
assert.Equal(t, "4.5.6", readVersion(t, path))
})
t.Run("skips private package", func(t *testing.T) {
dir := t.TempDir()
path := writePkgJSON(t, dir, "pkg", map[string]any{"name": "pkg", "version": "0.0.0", "private": true})
require.NoError(t, setVersionInPackageJSON(filepath.Join(dir, "pkg", "package.json"), "4.5.6"))
assert.Equal(t, "0.0.0", readVersion(t, path))
})
t.Run("missing file is a no-op", func(t *testing.T) {
err := setVersionInPackageJSON("/nonexistent/package.json", "1.0.0")
require.NoError(t, err)
})
t.Run("preserves inline array formatting", func(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "pkg", "package.json")
makeDir(t, filepath.Dir(path))
original := `{
"name": "pkg",
"version": "0.0.0",
"os": ["linux"],
"cpu": ["x64"],
"files": ["bin/**"]
}
`
writeTestFile(t, path, []byte(original))
require.NoError(t, setVersionInPackageJSON(path, "1.2.3"))
data, err := os.ReadFile(path)
require.NoError(t, err)
content := string(data)
assert.Contains(t, content, `"version": "1.2.3"`)
assert.Contains(t, content, `"os": ["linux"]`)
assert.Contains(t, content, `"cpu": ["x64"]`)
assert.Contains(t, content, `"files": ["bin/**"]`)
})
t.Run("does not match version inside a string value", func(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "pkg", "package.json")
makeDir(t, filepath.Dir(path))
original := "{\n \"name\": \"pkg\",\n \"version\": \"0.0.0\",\n \"description\": \"see \\\"version\\\": \\\"1.0.0\\\" in docs\"\n}\n"
writeTestFile(t, path, []byte(original))
require.NoError(t, setVersionInPackageJSON(path, "2.0.0"))
data, err := os.ReadFile(path)
require.NoError(t, err)
content := string(data)
assert.Contains(t, content, `"version": "2.0.0"`)
assert.Contains(t, content, "\"description\": \"see \\\"version\\\": \\\"1.0.0\\\" in docs\"")
})
}