diff --git a/.data/demo.gif b/.data/demo.gif index 0341005..54de266 100644 Binary files a/.data/demo.gif and b/.data/demo.gif differ diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d7d5066..3d08c35 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -24,7 +24,6 @@ jobs: id: static-analysis with: token: ${{ secrets.GITHUB_TOKEN }} - # This check name is defined as the github action job name (in .github/workflows/validations.yaml) checkName: "Static analysis" ref: ${{ github.event.pull_request.head.sha || github.sha }} @@ -33,55 +32,21 @@ jobs: id: unit with: token: ${{ secrets.GITHUB_TOKEN }} - # This check name is defined as the github action job name (in .github/workflows/validations.yaml) checkName: "Unit tests (ubuntu-latest)" ref: ${{ github.event.pull_request.head.sha || github.sha }} - - name: Check acceptance test results (linux) - uses: fountainhead/action-wait-for-check@v1.2.0 - id: acceptance-linux - with: - token: ${{ secrets.GITHUB_TOKEN }} - # This check name is defined as the github action job name (in .github/workflows/validations.yaml) - checkName: "Acceptance tests (Linux)" - ref: ${{ github.event.pull_request.head.sha || github.sha }} - - - name: Check acceptance test results (mac) - uses: fountainhead/action-wait-for-check@v1.2.0 - id: acceptance-mac - with: - token: ${{ secrets.GITHUB_TOKEN }} - # This check name is defined as the github action job name (in .github/workflows/validations.yaml) - checkName: "Acceptance tests (Mac)" - ref: ${{ github.event.pull_request.head.sha || github.sha }} - - - name: Check acceptance test results (windows) - uses: fountainhead/action-wait-for-check@v1.2.0 - id: acceptance-windows - with: - token: ${{ secrets.GITHUB_TOKEN }} - # This check name is defined as the github action job name (in .github/workflows/validations.yaml) - checkName: "Acceptance tests (Windows)" - ref: ${{ github.event.pull_request.head.sha || github.sha }} - - name: Quality gate - if: steps.static-analysis.outputs.conclusion != 'success' || steps.unit.outputs.conclusion != 'success' || steps.acceptance-linux.outputs.conclusion != 'success' || steps.acceptance-mac.outputs.conclusion != 'success' || steps.acceptance-windows.outputs.conclusion != 'success' + if: steps.static-analysis.outputs.conclusion != 'success' || steps.unit.outputs.conclusion != 'success' run: | echo "Static Analysis Status: ${{ steps.static-analysis.conclusion }}" echo "Unit Test Status: ${{ steps.unit.outputs.conclusion }}" - echo "Acceptance Test (Linux) Status: ${{ steps.acceptance-linux.outputs.conclusion }}" - echo "Acceptance Test (Mac) Status: ${{ steps.acceptance-mac.outputs.conclusion }}" - echo "Acceptance Test (Windows) Status: ${{ steps.acceptance-windows.outputs.conclusion }}" - false release: needs: [quality-gate] runs-on: ubuntu-latest permissions: - # for tagging contents: write - # for pushing container images packages: write steps: - uses: actions/checkout@v4 @@ -91,15 +56,8 @@ jobs: - name: Bootstrap environment uses: ./.github/actions/bootstrap - - name: Login to Docker Hub - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 #v3.4.0 - with: - registry: docker.io - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Login to GitHub Container Registry - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 #v3.4.0 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -121,10 +79,27 @@ jobs: - name: Build & publish release artifacts run: make ci-release env: - # for creating the release (requires write access to content) GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # for updating brew formula in wagoodman/homebrew-dive - TAP_GITHUB_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }} - - name: Smoke test published image - run: make ci-test-docker-image + npm-publish: + needs: [release] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org' + + - name: Update package.json version + run: | + VERSION="${{ github.event.inputs.version }}" + VERSION="${VERSION#v}" + npm version $VERSION --no-git-tag-version + + - name: Publish to npm + run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/validations.yaml b/.github/workflows/validations.yaml index 21461cd..43a5324 100644 --- a/.github/workflows/validations.yaml +++ b/.github/workflows/validations.yaml @@ -8,7 +8,6 @@ on: jobs: Static-Analysis: - # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline name: "Static analysis" runs-on: ubuntu-latest steps: @@ -21,15 +20,11 @@ jobs: run: make static-analysis Unit-Test: - # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline name: "Unit tests" strategy: matrix: platform: - ubuntu-latest - # - macos-latest # todo: mac runners are expensive minute-wise - # - windows-latest # todo: support windows - runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v4 @@ -58,25 +53,16 @@ jobs: - name: Build snapshot artifacts run: make snapshot - - run: docker images wagoodman/dive - - # todo: compare against known json output in shared volume - - name: Test production image - run: make ci-test-docker-image - - # why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach). - # see https://github.com/actions/upload-artifact/issues/199 for more info - name: Upload snapshot artifacts uses: actions/cache/save@v4 with: path: snapshot key: snapshot-build-${{ github.run_id }} - # ... however the cache trick doesn't work on windows :( - uses: actions/upload-artifact@v4 with: name: windows-artifacts - path: snapshot/dive_windows_amd64_v1/dive.exe + path: snapshot/superdive_windows_amd64_v1/superdive.exe Acceptance-Linux: name: "Acceptance tests (Linux)" @@ -94,12 +80,6 @@ jobs: - name: Test linux run run: make ci-test-linux-run - - name: Test DEB package installation - run: make ci-test-deb-package-install - - - name: Test RPM package installation - run: make ci-test-rpm-package-install - Acceptance-Mac: name: "Acceptance tests (Mac)" needs: [Build-Snapshot-Artifacts] diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 4ab778b..2ca9f72 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -14,7 +14,8 @@ env: - CGO_ENABLED=0 builds: - - binary: dive + - id: superdive + binary: superdive dir: ./cmd/dive env: - CGO_ENABLED=0 @@ -25,7 +26,6 @@ builds: goarch: - amd64 - arm64 - - ppc64le ldflags: -w -s @@ -35,40 +35,35 @@ builds: -X main.buildDate={{.Date}} -X main.gitDescription={{.Summary}} -brews: - - repository: - owner: wagoodman - name: homebrew-dive - token: "{{.Env.TAP_GITHUB_TOKEN}}" - homepage: &project_url "https://github.com/wagoodman/dive/" - description: &description "A tool for exploring layers in a docker image" - archives: - - format: tar.gz + - id: superdive-archives + builds: + - superdive + name_template: "superdive_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + format: tar.gz format_overrides: - goos: windows format: zip nfpms: - license: MIT - maintainer: Alex Goodman - homepage: *project_url - description: *description + maintainer: Aslan Dukaev + homepage: "https://github.com/dukaev/superdive" + description: "A tool for exploring layers in a docker image" formats: - rpm - deb dockers: - # docker.io amd64 - - &dockerhub_amd64 + - &docker_amd64 id: docker-amd64 ids: - - dive + - superdive use: buildx goarch: amd64 image_templates: - - docker.io/wagoodman/dive:latest - - docker.io/wagoodman/dive:v{{.Version}}-amd64 + - ghcr.io/dukaev/superdive:latest + - ghcr.io/dukaev/superdive:v{{.Version}}-amd64 build_flag_templates: - "--build-arg=DOCKER_CLI_VERSION={{.Env.DOCKER_CLI_VERSION}}" - "--platform=linux/amd64" @@ -80,17 +75,15 @@ dockers: - "--label=org.opencontainers.image.version={{.Version}}" - "--label=org.opencontainers.image.revision={{.FullCommit}}" - "--label=org.opencontainers.image.licenses=MIT" - - "--label=org.opencontainers.image.authors=Alex Goodman <@wagoodman>" - # docker.io arm64 - - &dockerhub_arm64 + - &docker_arm64 id: docker-arm64 ids: - - dive + - superdive use: buildx goarch: arm64 image_templates: - - docker.io/wagoodman/dive:v{{.Version}}-arm64 + - ghcr.io/dukaev/superdive:v{{.Version}}-arm64 build_flag_templates: - "--build-arg=DOCKER_CLI_VERSION={{.Env.DOCKER_CLI_VERSION}}" - "--platform=linux/arm64/v8" @@ -102,47 +95,14 @@ dockers: - "--label=org.opencontainers.image.version={{.Version}}" - "--label=org.opencontainers.image.revision={{.FullCommit}}" - "--label=org.opencontainers.image.licenses=MIT" - - "--label=org.opencontainers.image.authors=Alex Goodman <@wagoodman>" - - # ghcr.io amd64 - - id: ghcr-amd64 - <<: *dockerhub_amd64 - image_templates: - - ghcr.io/wagoodman/dive:v{{.Version}}-amd64 - - # ghcr.io arm64 - - id: ghcr-arm64 - <<: *dockerhub_arm64 - image_templates: - - ghcr.io/wagoodman/dive:v{{.Version}}-arm64 docker_manifests: - # docker.io manifests - - name_template: docker.io/wagoodman/dive:latest - image_templates: &dockerhub_images - - docker.io/wagoodman/dive:v{{.Version}}-amd64 - - docker.io/wagoodman/dive:v{{.Version}}-arm64 + - name_template: ghcr.io/dukaev/superdive:latest + image_templates: + - ghcr.io/dukaev/superdive:v{{.Version}}-amd64 + - ghcr.io/dukaev/superdive:v{{.Version}}-arm64 - - name_template: docker.io/wagoodman/dive:v{{.Major}} - image_templates: *dockerhub_images - - - name_template: docker.io/wagoodman/dive:v{{.Major}}.{{.Minor}} - image_templates: *dockerhub_images - - - name_template: docker.io/wagoodman/dive:v{{.Version}} - image_templates: *dockerhub_images - - # ghcr.io manifests - - name_template: ghcr.io/wagoodman/dive:latest - image_templates: &ghcr_images - - ghcr.io/wagoodman/dive:v{{.Version}}-amd64 - - ghcr.io/wagoodman/dive:v{{.Version}}-arm64 - - - name_template: ghcr.io/wagoodman/dive:v{{.Major}} - image_templates: *ghcr_images - - - name_template: ghcr.io/wagoodman/dive:v{{.Major}}.{{.Minor}} - image_templates: *ghcr_images - - - name_template: ghcr.io/wagoodman/dive:v{{.Version}} - image_templates: *ghcr_images + - name_template: ghcr.io/dukaev/superdive:v{{.Version}} + image_templates: + - ghcr.io/dukaev/superdive:v{{.Version}}-amd64 + - ghcr.io/dukaev/superdive:v{{.Version}}-arm64 diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..3dfb866 --- /dev/null +++ b/.npmignore @@ -0,0 +1,38 @@ +# Source files +cmd/ +dive/ +internal/ +*.go +go.mod +go.sum + +# Build files +Makefile +Taskfile.yaml +Dockerfile +.goreleaser.yaml +.golangci.yaml +coverage.out +snapshot/ + +# Git/GitHub +.git/ +.github/ +.gitignore + +# Config files +.binny.yaml +.bouncer.yaml +.dockerignore +.dive-ci + +# Documentation (keep README) +RELEASE.md +AGENTS.md + +# Data +.data/ + +# Test files +*_test.go +testdata/ diff --git a/Makefile b/Makefile index 138d231..9c2b840 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -OWNER = wagoodman -PROJECT = dive +OWNER = dukaev +PROJECT = superdive TOOL_DIR = .tool BINNY = $(TOOL_DIR)/binny @@ -41,7 +41,7 @@ $(TASKS): $(TASK) ## actual targets ci-test-windows-run: - dive.exe --source docker-archive .data/test-docker-image.tar --ci --ci-config .data/.dive-ci + superdive.exe --source docker-archive .data/test-docker-image.tar --ci --ci-config .data/.dive-ci help: $(TASK) @$(TASK) -l diff --git a/README.md b/README.md index b327b4a..e5e32c3 100644 --- a/README.md +++ b/README.md @@ -2,47 +2,41 @@ **A tool for exploring a Docker image, layer contents, and discovering ways to shrink the size of your Docker/OCI image.** - ![Image](.data/demo.gif) -To analyze a Docker image simply run dive with an image tag/id/digest: -```bash -dive -``` +## Installation -or you can dive with Docker directly: -``` -alias dive="docker run -ti --rm -v /var/run/docker.sock:/var/run/docker.sock docker.io/wagoodman/dive" -dive - -# for example -dive nginx:latest -``` - -or if you want to build your image then jump straight into analyzing it: -```bash -dive build -t . -``` - -Building on macOS (supporting only the Docker container engine): +### bun/npm (recommended) ```bash -docker run --rm -it \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v "$(pwd)":"$(pwd)" \ - -w "$(pwd)" \ - -v "$HOME/.dive.yaml":"$HOME/.dive.yaml" \ - docker.io/wagoodman/dive:latest build -t . +bun install -g superdive +npm install -g superdive +``` + +### From source + +```bash +git clone https://github.com/dukaev/superdive.git +cd superdive +go build -o superdive ./cmd/dive +``` + +## Usage + +To analyze a Docker image simply run superdive with an image tag/id/digest: +```bash +superdive +``` + +Or if you want to build your image then jump straight into analyzing it: +```bash +superdive build -t . ``` Additionally you can run this in your CI pipeline to ensure you're keeping wasted space to a minimum (this skips the UI): +```bash +CI=true superdive ``` -CI=true dive -``` - -![Image](.data/demo-ci.png) - -**This is beta quality!** *Feel free to submit an issue if you want a new feature or find a bug :)* ## Basic Features @@ -61,24 +55,25 @@ The lower left pane shows basic layer info and an experimental metric that will **Quick build/analysis cycles** You can build a Docker image and do an immediate analysis with one command: -`dive build -t some-tag .` +```bash +superdive build -t some-tag . +``` -You only need to replace your `docker build` command with the same `dive build` -command. +You only need to replace your `docker build` command with the same `superdive build` command. **CI Integration** -Analyze an image and get a pass/fail result based on the image efficiency and wasted space. Simply set `CI=true` in the environment when invoking any valid dive command. +Analyze an image and get a pass/fail result based on the image efficiency and wasted space. Simply set `CI=true` in the environment when invoking any valid superdive command. **Multiple Image Sources and Container Engines Supported** With the `--source` option, you can select where to fetch the container image from: ```bash -dive --source +superdive --source ``` or ```bash -dive :// +superdive :// ``` With valid `source` options as such: @@ -86,11 +81,10 @@ With valid `source` options as such: - `docker-archive`: A Docker Tar Archive from disk - `podman`: Podman engine (linux only) - ## CI Integration -When running dive with the environment variable `CI=true` then the dive UI will be bypassed and will instead analyze your docker image, giving it a pass/fail indication via return code. Currently there are three metrics supported via a `.dive-ci` file that you can put at the root of your repo: -``` +When running superdive with the environment variable `CI=true` then the UI will be bypassed and will instead analyze your docker image, giving it a pass/fail indication via return code. Currently there are three metrics supported via a `.dive-ci` file that you can put at the root of your repo: +```yaml rules: # If the efficiency is measured below X%, mark as failed. # Expressed as a ratio between 0-1. @@ -106,3 +100,7 @@ rules: highestUserWastedPercent: 0.20 ``` You can override the CI config path with the `--ci-config` option. + +## License + +MIT diff --git a/Taskfile.yaml b/Taskfile.yaml index 0f775cc..bdea2ab 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -1,8 +1,8 @@ version: "3" vars: - OWNER: wagoodman - PROJECT: dive + OWNER: dukaev + PROJECT: superdive # static file dirs TOOL_DIR: .tool @@ -202,23 +202,9 @@ tasks: ci-test-linux: cmds: - task: ci-test-linux-run - - task: ci-test-docker-image - task: ci-test-deb-package-install - task: ci-test-rpm-package-install - ci-test-docker-image: - desc: Test using the docker image - cmds: - - | - docker run \ - --rm \ - -t \ - --env CLICOLOR_FORCE=true \ - -v /var/run/docker.sock:/var/run/docker.sock \ - 'docker.io/wagoodman/dive:latest' \ - '{{ .TEST_IMAGE }}' \ - --ci - ci-test-deb-package-install: desc: Test debian package installation cmds: @@ -237,9 +223,9 @@ tasks: tar -vxzf - docker/docker --strip-component=1 && \ mv docker /usr/local/bin/ &&\ docker version && \ - apt install ./snapshot/dive_*_linux_amd64.deb -y && \ - dive --version && \ - dive '{{ .TEST_IMAGE }}' --ci \ + apt install ./snapshot/superdive_*_linux_amd64.deb -y && \ + superdive --version && \ + superdive '{{ .TEST_IMAGE }}' --ci \ " ci-test-rpm-package-install: @@ -258,9 +244,9 @@ tasks: tar -vxzf - docker/docker --strip-component=1 && \ mv docker /usr/local/bin/ &&\ docker version && \ - dnf install ./snapshot/dive_*_linux_amd64.rpm -y && \ - dive --version && \ - dive '{{ .TEST_IMAGE }}' --ci \ + dnf install ./snapshot/superdive_*_linux_amd64.rpm -y && \ + superdive --version && \ + superdive '{{ .TEST_IMAGE }}' --ci \ " generate-compressed-test-images: @@ -271,7 +257,7 @@ tasks: for exporter in docker image; do \ docker buildx build \ -f .data/Dockerfile.minimal \ - --tag test-dive-${exporter}:${alg} \ + --tag test-superdive-${exporter}:${alg} \ --output type=${exporter},force-compression=true,compression=${alg} . ; \ done ; \ done && \ @@ -298,16 +284,16 @@ tasks: cmds: - | ls -la {{ .SNAPSHOT_DIR }} - ls -la {{ .SNAPSHOT_DIR }}/dive_linux_amd64_v1 - chmod 755 {{ .SNAPSHOT_DIR }}/dive_linux_amd64_v1/dive && \ - {{ .SNAPSHOT_DIR }}/dive_linux_amd64_v1/dive '{{ .TEST_IMAGE }}' --ci && \ - {{ .SNAPSHOT_DIR }}/dive_linux_amd64_v1/dive --source docker-archive .data/test-kaniko-image.tar --ci --ci-config .data/.dive-ci + ls -la {{ .SNAPSHOT_DIR }}/superdive_linux_amd64_v1 + chmod 755 {{ .SNAPSHOT_DIR }}/superdive_linux_amd64_v1/superdive && \ + {{ .SNAPSHOT_DIR }}/superdive_linux_amd64_v1/superdive '{{ .TEST_IMAGE }}' --ci && \ + {{ .SNAPSHOT_DIR }}/superdive_linux_amd64_v1/superdive --source docker-archive .data/test-kaniko-image.tar --ci --ci-config .data/.dive-ci - | for alg in uncompressed gzip estargz zstd; do \ for exporter in docker image; do \ - {{ .SNAPSHOT_DIR }}/dive_linux_amd64_v1/dive "test-dive-${exporter}:${alg}" --ci ; \ + {{ .SNAPSHOT_DIR }}/superdive_linux_amd64_v1/superdive "test-superdive-${exporter}:${alg}" --ci ; \ done && \ - {{ .SNAPSHOT_DIR }}/dive_linux_amd64_v1/dive --source docker-archive .data/test-oci-${alg}-image.tar --ci --ci-config .data/.dive-ci; \ + {{ .SNAPSHOT_DIR }}/superdive_linux_amd64_v1/superdive --source docker-archive .data/test-oci-${alg}-image.tar --ci --ci-config .data/.dive-ci; \ done ci-test-mac-run: @@ -315,8 +301,8 @@ tasks: deps: [ci-check] cmds: - | - chmod 755 {{ .SNAPSHOT_DIR }}/dive_darwin_amd64_v1/dive && \ - {{ .SNAPSHOT_DIR }}/dive_darwin_amd64_v1/dive --source docker-archive .data/test-docker-image.tar --ci --ci-config .data/.dive-ci + chmod 755 {{ .SNAPSHOT_DIR }}/superdive_darwin_amd64_v1/superdive && \ + {{ .SNAPSHOT_DIR }}/superdive_darwin_amd64_v1/superdive --source docker-archive .data/test-docker-image.tar --ci --ci-config .data/.dive-ci ## Build-related targets ################################# diff --git a/package.json b/package.json new file mode 100644 index 0000000..88b8a01 --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "superdive", + "version": "0.14.0", + "description": "A tool for exploring a Docker image, layer contents, and discovering ways to shrink the size of your Docker/OCI image.", + "bin": { + "superdive": "bin/superdive.js" + }, + "scripts": { + "postinstall": "node scripts/install.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/dukaev/superdive.git" + }, + "keywords": [ + "docker", + "container", + "image", + "oci", + "layer", + "dive", + "cli" + ], + "author": "Aslan Dukaev", + "license": "MIT", + "bugs": { + "url": "https://github.com/dukaev/superdive/issues" + }, + "homepage": "https://github.com/dukaev/superdive#readme", + "engines": { + "node": ">=14" + }, + "files": [ + "bin", + "scripts" + ] +} diff --git a/scripts/install.js b/scripts/install.js new file mode 100755 index 0000000..c3ac1e2 --- /dev/null +++ b/scripts/install.js @@ -0,0 +1,115 @@ +#!/usr/bin/env node + +const https = require('https'); +const fs = require('fs'); +const path = require('path'); +const os = require('os'); +const { execSync } = require('child_process'); + +const VERSION = require('../package.json').version; +const REPO = 'dukaev/superdive'; + +function getPlatform() { + const platform = os.platform(); + switch (platform) { + case 'darwin': return 'darwin'; + case 'linux': return 'linux'; + case 'win32': return 'windows'; + default: throw new Error(`Unsupported platform: ${platform}`); + } +} + +function getArch() { + const arch = os.arch(); + switch (arch) { + case 'x64': return 'amd64'; + case 'arm64': return 'arm64'; + case 'ia32': return '386'; + default: throw new Error(`Unsupported architecture: ${arch}`); + } +} + +function getDownloadUrl() { + const platform = getPlatform(); + const arch = getArch(); + const ext = platform === 'windows' ? '.zip' : '.tar.gz'; + return `https://github.com/${REPO}/releases/download/v${VERSION}/superdive_${VERSION}_${platform}_${arch}${ext}`; +} + +function download(url, dest) { + return new Promise((resolve, reject) => { + const file = fs.createWriteStream(dest); + + const request = (url) => { + https.get(url, (response) => { + if (response.statusCode === 302 || response.statusCode === 301) { + request(response.headers.location); + return; + } + + if (response.statusCode !== 200) { + reject(new Error(`Failed to download: ${response.statusCode}`)); + return; + } + + response.pipe(file); + file.on('finish', () => { + file.close(); + resolve(); + }); + }).on('error', (err) => { + fs.unlink(dest, () => {}); + reject(err); + }); + }; + + request(url); + }); +} + +async function install() { + const platform = getPlatform(); + const binDir = path.join(__dirname, '..', 'bin'); + const binaryName = platform === 'windows' ? 'superdive.exe' : 'superdive'; + const binaryPath = path.join(binDir, binaryName); + + // Skip if binary already exists + if (fs.existsSync(binaryPath)) { + console.log('superdive binary already exists'); + return; + } + + const url = getDownloadUrl(); + const ext = platform === 'windows' ? '.zip' : '.tar.gz'; + const archivePath = path.join(binDir, `superdive${ext}`); + + console.log(`Downloading superdive from ${url}...`); + + try { + await download(url, archivePath); + + console.log('Extracting...'); + + if (platform === 'windows') { + execSync(`powershell -command "Expand-Archive -Path '${archivePath}' -DestinationPath '${binDir}'"`, { stdio: 'inherit' }); + } else { + execSync(`tar -xzf "${archivePath}" -C "${binDir}"`, { stdio: 'inherit' }); + } + + // Make binary executable + if (platform !== 'windows') { + fs.chmodSync(binaryPath, 0o755); + } + + // Clean up archive + fs.unlinkSync(archivePath); + + console.log('superdive installed successfully!'); + } catch (err) { + console.error(`Failed to install superdive: ${err.message}`); + console.error('Please install manually from https://github.com/dukaev/superdive/releases'); + process.exit(1); + } +} + +install();