proc-cli 2.2.0

Semantic CLI tool for process management. Target by port, PID, name or path.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
name: Release

on:
  push:
    tags:
      - 'v*'
  # A dry run (RELEASE.md, step 1): every build and package job and the
  # release files, from the branch it is started on, and nothing published.
  # Started this way the version checks print instead of failing, since a
  # branch build is a -dev build
  workflow_dispatch:

env:
  CARGO_TERM_COLOR: always
  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
  build-release:
    name: Build Release
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          # Linux builds link musl statically, so the binaries don't depend on the
          # runner's glibc version and also run on older distributions and on Alpine
          - os: ubuntu-latest
            target: x86_64-unknown-linux-musl
            artifact: proc-linux-x86_64
          - os: ubuntu-latest
            target: aarch64-unknown-linux-musl
            artifact: proc-linux-aarch64
          - os: macos-latest
            target: x86_64-apple-darwin
            artifact: proc-darwin-x86_64
          - os: macos-latest
            target: aarch64-apple-darwin
            artifact: proc-darwin-aarch64
          - os: windows-latest
            target: x86_64-pc-windows-msvc
            artifact: proc-windows-x86_64.exe
          # FreeBSD and NetBSD build in their own VMs (build-bsd below)

    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
        with:
          targets: ${{ matrix.target }}

      - name: Install cross-compilation tools (Linux ARM64)
        if: matrix.target == 'aarch64-unknown-linux-musl'
        run: |
          sudo apt-get update
          sudo apt-get install -y gcc-aarch64-linux-gnu
          echo '[target.aarch64-unknown-linux-musl]' >> ~/.cargo/config.toml
          echo 'linker = "aarch64-linux-gnu-gcc"' >> ~/.cargo/config.toml

      - name: Build release
        run: cargo build --release --target ${{ matrix.target }}

      # proc.exe links the C runtime statically (.cargo/config.toml), so it runs
      # without the Visual C++ Redistributable. The runners have that runtime
      # installed, so a missing DLL never shows in a run: read the import table.
      - name: Check proc.exe needs no Visual C++ runtime
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
          $dumpbin = & $vswhere -latest -products * -find 'VC\Tools\MSVC\*\bin\Hostx64\x64\dumpbin.exe' | Select-Object -First 1
          $deps = & $dumpbin /nologo /dependents "target/${{ matrix.target }}/release/proc.exe"
          $deps
          if ($deps -match 'VCRUNTIME|MSVCP|api-ms-win-crt') {
            echo "::error::proc.exe imports the Visual C++ runtime; it must link it statically"
            exit 1
          }

      # Every publish job needs this one, so a binary that would call itself a
      # -dev build, or a tag that doesn't match Cargo.toml's version, stops the
      # release here. Cross-compiled targets can't run on the runner, so they
      # only get the marker check.
      - name: Check the binary reports the tag's version
        shell: bash
        run: |
          bin=target/${{ matrix.target }}/release/proc${{ runner.os == 'Windows' && '.exe' || '' }}
          if [ "$GITHUB_EVENT_NAME" != push ]; then
            echo "dry run: $("$bin" --version 2>/dev/null | head -1 | tr -d '\r' || echo "$bin can't run here")"
            exit 0
          fi
          if LC_ALL=C grep -a -q -- '-dev (' "$bin"; then
            echo "::error::$bin is labelled as a dev build; the tag must be v<Cargo.toml version>"
            exit 1
          fi
          if [ "${{ matrix.target }}" != aarch64-unknown-linux-musl ] && [ "${{ matrix.target }}" != x86_64-apple-darwin ]; then
            got=$("$bin" --version | head -1 | tr -d '\r')
            want="proc ${GITHUB_REF_NAME#v}"
            if [ "$got" != "$want" ]; then
              echo "::error::$bin --version printed '$got', expected '$want'"
              exit 1
            fi
            echo "$got"
          fi

      - name: Rename binary (Unix)
        if: runner.os != 'Windows'
        run: |
          cp target/${{ matrix.target }}/release/proc ${{ matrix.artifact }}
          chmod +x ${{ matrix.artifact }}

      - name: Rename binary (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: Copy-Item "target/${{ matrix.target }}/release/proc.exe" -Destination "${{ matrix.artifact }}"

      - name: Create tarball (Unix)
        if: runner.os != 'Windows'
        run: tar -czvf ${{ matrix.artifact }}.tar.gz ${{ matrix.artifact }}

      - name: Create zip (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: Compress-Archive -Path ${{ matrix.artifact }} -DestinationPath "${{ matrix.artifact }}.zip"

      - name: Upload artifact
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: ${{ matrix.artifact }}
          path: |
            ${{ matrix.artifact }}
            ${{ matrix.artifact }}.tar.gz
            ${{ matrix.artifact }}.zip
          if-no-files-found: warn

  # .deb and .rpm files for the GitHub Release (not an apt or dnf repository, so
  # they don't update themselves). nfpm packs the static musl binaries; the man
  # page and completions come from running the x86_64 binary on this runner.
  package-linux:
    name: Package .deb and .rpm
    needs: build-release
    runs-on: ubuntu-latest
    env:
      # Pinned release; the checksum is the one in that release's checksums.txt
      NFPM_VERSION: 2.48.0
      NFPM_SHA256: 6d7f9a28b2f397934403950fcbba83f598b08d02edbe79a0815b3b4897341750

    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

      - name: Download the Linux binaries
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          pattern: proc-linux-*
          merge-multiple: true
          path: linux

      - name: Install nfpm
        run: |
          curl -fsSL -o nfpm.tar.gz "https://github.com/goreleaser/nfpm/releases/download/v${NFPM_VERSION}/nfpm_${NFPM_VERSION}_Linux_x86_64.tar.gz"
          echo "${NFPM_SHA256}  nfpm.tar.gz" | sha256sum -c -
          mkdir -p "$HOME/.local/bin"
          tar -xzf nfpm.tar.gz -C "$HOME/.local/bin" nfpm
          echo "$HOME/.local/bin" >> "$GITHUB_PATH"

      - name: Generate the man page and completions
        run: |
          chmod +x linux/proc-linux-x86_64 linux/proc-linux-aarch64
          pkg/nfpm/generate-docs.sh linux/proc-linux-x86_64 docs-out

      - name: Build the packages
        run: |
          # A dry run has no tag: the version comes from Cargo.toml
          if [ "$GITHUB_EVENT_NAME" = push ]; then
            VERSION="${GITHUB_REF_NAME#v}"
          else
            VERSION=$(sed -n 's/^version = "\(.*\)"$/\1/p' Cargo.toml | head -n1)
          fi
          pkg/nfpm/build.sh "$VERSION" amd64 linux/proc-linux-x86_64 docs-out packages
          pkg/nfpm/build.sh "$VERSION" arm64 linux/proc-linux-aarch64 docs-out packages
          ls -la packages
          test "$(find packages -type f | wc -l)" -eq 4

      - name: Upload packages
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: linux-packages
          path: packages/*
          if-no-files-found: error

  # FreeBSD and NetBSD build natively in their own VMs (scripts/bsd-build.sh:
  # cross's images lack libraries proc links). Each binary must print the
  # tag's version and read the port table there before anything is published.
  # The target directory stays outside the workspace, which is copied back
  build-bsd:
    name: Build ${{ matrix.os }}
    timeout-minutes: 45
    runs-on: ubuntu-latest
    strategy:
      matrix:
        os: [freebsd, netbsd]

    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

      - name: Build on FreeBSD
        if: matrix.os == 'freebsd'
        uses: vmactions/freebsd-vm@c46abacb49f09938ca4e1702d15d836285d694cc # v1.5.9
        with:
          release: "15.1"
          cpu: 4
          mem: 8192
          prepare: pkg install -y rust
          run: |
            cd "$GITHUB_WORKSPACE"
            export CARGO_TARGET_DIR="$HOME/proc-target"
            rustc --version
            # a dry run expects no version (GITHUB_* reach the VM)
            want=""; [ "$GITHUB_EVENT_NAME" = push ] && want="proc ${GITHUB_REF_NAME#v}"
            sh scripts/bsd-build.sh proc-freebsd-x86_64 "$want"

      - name: Build on NetBSD
        if: matrix.os == 'netbsd'
        uses: vmactions/netbsd-vm@c8a0d7ddb84619a7a8cc4e652efa7950b3fd9f92 # v1.5.2
        with:
          release: "11.0"
          cpu: 4
          mem: 8192
          prepare: /usr/sbin/pkg_add rust
          run: |
            cd "$GITHUB_WORKSPACE"
            export CARGO_TARGET_DIR="$HOME/proc-target"
            rustc --version
            want=""; [ "$GITHUB_EVENT_NAME" = push ] && want="proc ${GITHUB_REF_NAME#v}"
            sh scripts/bsd-build.sh proc-netbsd-x86_64 "$want"

      - name: Create tarball
        run: tar -czvf proc-${{ matrix.os }}-x86_64.tar.gz proc-${{ matrix.os }}-x86_64

      - name: Upload artifact
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: proc-${{ matrix.os }}-x86_64
          path: |
            proc-${{ matrix.os }}-x86_64
            proc-${{ matrix.os }}-x86_64.tar.gz
          if-no-files-found: error

  create-release:
    name: Create Release
    needs: [build-release, package-linux, build-bsd]
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

      - name: Download all artifacts
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          path: artifacts

      - name: Prepare release assets
        run: |
          mkdir -p release
          find artifacts -type f -exec cp {} release/ \;
          ls -la release/

      - name: Generate checksums
        working-directory: release
        run: |
          sha256sum -- * > checksums.txt
          cat checksums.txt

      - name: Create GitHub Release
        if: github.event_name == 'push'
        uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
        with:
          files: release/*
          generate_release_notes: true
          make_latest: true

  publish-crate:
    name: Publish to crates.io
    needs: create-release
    if: github.event_name == 'push'
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable

      - name: Get crates.io token via OIDC
        uses: rust-lang/crates-io-auth-action@3268eb413133e2960fa4ae0e963ca76380c54cd9 # v1.0.6
        id: crates-io-auth

      - name: Publish to crates.io
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.crates-io-auth.outputs.token }}
        run: |
          # Without pipefail, the `if` tests tee's exit status and a failed publish looks successful
          set -o pipefail
          VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
          echo "Local version: $VERSION"

          if cargo publish --allow-dirty 2>&1 | tee /tmp/publish.log; then
            echo "Successfully published $VERSION to crates.io"
          else
            if grep -q "already exists" /tmp/publish.log; then
              echo "Version $VERSION already published to crates.io, skipping"
            else
              echo "Failed to publish"
              cat /tmp/publish.log
              exit 1
            fi
          fi

  publish-npm:
    name: Publish to npm
    needs: create-release
    if: github.event_name == 'push'
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

      - name: Setup Node.js
        uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
        with:
          node-version: '24'
          registry-url: 'https://registry.npmjs.org'

      # Trusted publishing (OIDC, no token) needs npm 11.5.1 or newer
      - name: Install npm 11
        run: |
          npm install -g npm@^11.5.1
          npm --version

      # Authenticates through the trusted publisher configured on npmjs.com
      # (yazeed/proc-cli, release.yml), using this job's id-token permission
      - name: Publish to npm
        working-directory: pkg/npm
        run: |
          # Without pipefail, the `if` tests tee's exit status and a failed publish looks successful
          set -o pipefail
          VERSION=$(node -p "require('./package.json').version")
          echo "Publishing version: $VERSION"

          if npm publish --provenance --access public 2>&1 | tee /tmp/npm-publish.log; then
            echo "Successfully published $VERSION to npm"
          else
            if grep -q "already exists" /tmp/npm-publish.log || grep -q "previously published" /tmp/npm-publish.log; then
              echo "Version $VERSION already published to npm, skipping"
            else
              echo "Failed to publish"
              cat /tmp/npm-publish.log
              exit 1
            fi
          fi

  update-homebrew:
    name: Update Homebrew Tap
    needs: create-release
    if: github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout homebrew-proc tap
        uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
        with:
          repository: yazeed/homebrew-proc
          token: ${{ secrets.HOMEBREW_TAP_TOKEN }}

      - name: Download checksums
        run: |
          gh release download ${{ github.ref_name }} --repo yazeed/proc-cli --pattern "checksums.txt" --output checksums.txt
          cat checksums.txt
        env:
          GH_TOKEN: ${{ github.token }}

      - name: Update formula
        run: |
          VERSION="${{ github.ref_name }}"
          VERSION="${VERSION#v}"

          DARWIN_ARM64=$(grep "proc-darwin-aarch64.tar.gz" checksums.txt | cut -d' ' -f1)
          DARWIN_X86=$(grep "proc-darwin-x86_64.tar.gz" checksums.txt | cut -d' ' -f1)
          LINUX_ARM64=$(grep "proc-linux-aarch64.tar.gz" checksums.txt | cut -d' ' -f1)
          LINUX_X86=$(grep "proc-linux-x86_64.tar.gz" checksums.txt | cut -d' ' -f1)

          cat > Formula/proc.rb << EOF
          class Proc < Formula
            desc "Semantic CLI tool for process management"
            homepage "https://proc-cli.com"
            version "${VERSION}"
            license "MIT"

            on_macos do
              if Hardware::CPU.arm?
                url "https://github.com/yazeed/proc-cli/releases/download/v#{version}/proc-darwin-aarch64.tar.gz"
                sha256 "${DARWIN_ARM64}"
              else
                url "https://github.com/yazeed/proc-cli/releases/download/v#{version}/proc-darwin-x86_64.tar.gz"
                sha256 "${DARWIN_X86}"
              end
            end

            on_linux do
              if Hardware::CPU.arm?
                url "https://github.com/yazeed/proc-cli/releases/download/v#{version}/proc-linux-aarch64.tar.gz"
                sha256 "${LINUX_ARM64}"
              else
                url "https://github.com/yazeed/proc-cli/releases/download/v#{version}/proc-linux-x86_64.tar.gz"
                sha256 "${LINUX_X86}"
              end
            end

            def install
              binary = Dir["proc-*"].first
              bin.install binary => "proc"
            end

            test do
              assert_match "proc", shell_output("#{bin}/proc --version")
            end
          end
          EOF

          # Remove leading spaces from heredoc
          sed -i 's/^          //' Formula/proc.rb

      - name: Commit and push
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add Formula/proc.rb
          git diff --staged --quiet || git commit -m "proc ${{ github.ref_name }}"
          git push

  update-scoop:
    name: Update Scoop Bucket
    needs: create-release
    if: github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout scoop-bucket-proc
        uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
        with:
          repository: yazeed/scoop-bucket-proc
          token: ${{ secrets.SCOOP_BUCKET_TOKEN }}

      - name: Download checksums
        run: |
          gh release download ${{ github.ref_name }} --repo yazeed/proc-cli --pattern "checksums.txt" --output checksums.txt
          cat checksums.txt
        env:
          GH_TOKEN: ${{ github.token }}

      - name: Update manifest
        run: |
          VERSION="${{ github.ref_name }}"
          VERSION="${VERSION#v}"
          WINDOWS_HASH=$(grep "proc-windows-x86_64.exe.zip" checksums.txt | cut -d' ' -f1)

          cat > bucket/proc.json << 'EOF'
          {
              "version": "VERSION_PLACEHOLDER",
              "description": "Semantic CLI tool for process management. Target by port, PID, name or path.",
              "homepage": "https://proc-cli.com",
              "license": "MIT",
              "architecture": {
                  "64bit": {
                      "url": "https://github.com/yazeed/proc-cli/releases/download/vVERSION_PLACEHOLDER/proc-windows-x86_64.exe.zip",
                      "hash": "HASH_PLACEHOLDER"
                  }
              },
              "bin": [["proc-windows-x86_64.exe", "proc"]],
              "checkver": "github",
              "autoupdate": {
                  "architecture": {
                      "64bit": {
                          "url": "https://github.com/yazeed/proc-cli/releases/download/v$version/proc-windows-x86_64.exe.zip"
                      }
                  }
              }
          }
          EOF

          sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" bucket/proc.json
          sed -i "s/HASH_PLACEHOLDER/${WINDOWS_HASH}/g" bucket/proc.json
          sed -i 's/^          //' bucket/proc.json

      - name: Commit and push
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add bucket/proc.json
          git diff --staged --quiet || git commit -m "proc ${{ github.ref_name }}"
          git push