markdown-tui-explorer 1.34.18

A terminal-based markdown file browser and viewer with search, syntax highlighting, and live reload
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
# release.yml — markdown-tui-explorer binary release pipeline
#
# WHEN IT FIRES
#   Push of a tag matching v*.*.* (e.g. v1.35.0). Arbitrary pushes and
#   branch pushes are ignored. A `workflow_dispatch` trigger is also present
#   so any tag can be back-filled manually from the Actions UI.
#
# WHAT IT PRODUCES
#   Per-platform release archives uploaded to the corresponding GitHub Release:
#     markdown-reader-<version>-x86_64-unknown-linux-gnu.tar.gz
#     markdown-reader-<version>-aarch64-unknown-linux-gnu.tar.gz
#     markdown-reader-<version>-x86_64-unknown-linux-musl.tar.gz
#     markdown-reader-<version>-x86_64-apple-darwin.tar.gz
#     markdown-reader-<version>-aarch64-apple-darwin.tar.gz
#     markdown-reader-<version>-x86_64-pc-windows-msvc.zip
#     SHA256SUMS
#
#   Each archive also contains LICENSE and README.md.
#
# MACOS SIGNING STATUS — UNSIGNED (Gatekeeper warning expected)
#   The macOS binaries are NOT code-signed or notarized. Users on macOS will
#   see a Gatekeeper warning ("cannot be opened because it is from an
#   unidentified developer") and must right-click → Open to proceed once.
#
# TODO: Adding Apple Developer ID signing + notarization
#   1. Obtain an Apple Developer ID Application certificate from
#      https://developer.apple.com/account/resources/certificates/
#   2. Export the certificate + private key as a .p12 and base64-encode it:
#        base64 -i cert.p12 | pbcopy
#   3. Add these repository secrets:
#        APPLE_DEVELOPER_ID_CERT   — the base64-encoded .p12
#        APPLE_DEVELOPER_ID_PASS   — the .p12 export password
#        APPLE_NOTARY_PROFILE      — name of the notarytool credential
#                                    (see `xcrun notarytool store-credentials`)
#        APPLE_TEAM_ID             — your 10-character Apple Team ID
#   4. In the build-macos job, after the "Strip binary" step, add:
#        - name: Sign binary
#          run: |
#            echo "$APPLE_DEVELOPER_ID_CERT" | base64 --decode > cert.p12
#            security create-keychain -p "" build.keychain
#            security import cert.p12 -k build.keychain \
#              -P "$APPLE_DEVELOPER_ID_PASS" -T /usr/bin/codesign
#            security set-key-partition-list -S apple-tool:,apple: \
#              -s -k "" build.keychain
#            codesign --force --options runtime \
#              --sign "Developer ID Application: <Your Name> ($APPLE_TEAM_ID)" \
#              target/${{ matrix.target }}/release/markdown-reader
#          env:
#            APPLE_DEVELOPER_ID_CERT: ${{ secrets.APPLE_DEVELOPER_ID_CERT }}
#            APPLE_DEVELOPER_ID_PASS: ${{ secrets.APPLE_DEVELOPER_ID_PASS }}
#            APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
#        - name: Notarize binary
#          run: |
#            xcrun notarytool submit target/${{ matrix.target }}/release/markdown-reader \
#              --keychain-profile "$APPLE_NOTARY_PROFILE" --wait
#            xcrun stapler staple target/${{ matrix.target }}/release/markdown-reader
#          env:
#            APPLE_NOTARY_PROFILE: ${{ secrets.APPLE_NOTARY_PROFILE }}

name: Release

# Triggered by `v*.*.*` tags (the parent crate `markdown-tui-explorer`'s
# version line). Builds cross-platform binaries of `markdown-reader`,
# attaches them to a GitHub Release, publishes the parent crate to
# crates.io, and updates the Homebrew tap formula.
#
# The library crate `mermaid-text` has its own tag pattern
# (`mermaid-text-*`) handled by `release-mermaid-text.yml` — that one
# only publishes to crates.io, no binary release.

on:
  push:
    tags:
      - "v[0-9]*.[0-9]*.[0-9]*"
  workflow_dispatch:
    inputs:
      tag:
        description: "Tag to build (e.g. v1.35.0) — used for manual back-fills"
        required: false
      publish_crate:
        description: "Publish to crates.io"
        type: boolean
        default: true

permissions:
  contents: write

env:
  CARGO_TERM_COLOR: always
  CARGO_INCREMENTAL: 0

jobs:
  build-linux:
    name: Build Linux (${{ matrix.target }})
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        target:
          - x86_64-unknown-linux-gnu
          - aarch64-unknown-linux-gnu
          - x86_64-unknown-linux-musl
    steps:
      - uses: actions/checkout@v6

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2
        with:
          key: linux-${{ matrix.target }}

      - name: Install cross
        run: cargo install cross --locked

      - name: Build binary
        # Build the parent crate's `markdown-reader` binary.
        # `--package markdown-tui-explorer` ensures we don't try to build
        # mermaid-text's binary (which is a separate workspace member
        # released on its own tag pattern).
        #
        # `cross` handles cross-compilation for aarch64-unknown-linux-gnu
        # via a Docker image with the correct GCC cross-toolchain — no
        # manual linker setup required.
        run: cross build --release --target ${{ matrix.target }} --package markdown-tui-explorer

      - name: Strip binary
        run: |
          TARGET_STRIP=""
          case "${{ matrix.target }}" in
            aarch64-unknown-linux-gnu)
              TARGET_STRIP="aarch64-linux-gnu-strip"
              sudo apt-get install -y binutils-aarch64-linux-gnu
              ;;
            *)
              TARGET_STRIP="strip"
              ;;
          esac
          $TARGET_STRIP target/${{ matrix.target }}/release/markdown-reader

      - name: Package tarball
        run: |
          VERSION=${GITHUB_REF_NAME#v}
          STAGING="markdown-reader-${VERSION}-${{ matrix.target }}"
          mkdir -p "$STAGING"
          cp target/${{ matrix.target }}/release/markdown-reader "$STAGING/"
          cp LICENSE README.md "$STAGING/"
          TARBALL="${STAGING}.tar.gz"
          tar -czf "$TARBALL" "$STAGING"
          echo "TARBALL=$TARBALL" >> "$GITHUB_ENV"

      - name: Upload artifact
        uses: actions/upload-artifact@v7
        with:
          name: markdown-reader-${{ matrix.target }}
          path: ${{ env.TARBALL }}
          retention-days: 1

  build-macos:
    name: Build macOS (${{ matrix.target }})
    # Both targets build on macos-latest (Apple Silicon). The x86_64
    # target cross-compiles via `rustup target add x86_64-apple-darwin`
    # — the host toolchain and `strip` both handle it natively.
    #
    # NOTE: Binaries are NOT signed or notarized. See the comment block at
    # the top of this file for instructions on adding signing later.
    runs-on: macos-latest
    strategy:
      fail-fast: false
      matrix:
        target:
          - x86_64-apple-darwin
          - aarch64-apple-darwin
    steps:
      - uses: actions/checkout@v6

      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - uses: Swatinem/rust-cache@v2
        with:
          key: macos-${{ matrix.target }}

      - name: Build binary
        run: cargo build --release --target ${{ matrix.target }} --package markdown-tui-explorer

      - name: Strip binary
        run: strip target/${{ matrix.target }}/release/markdown-reader

      - name: Package tarball
        run: |
          VERSION=${GITHUB_REF_NAME#v}
          STAGING="markdown-reader-${VERSION}-${{ matrix.target }}"
          mkdir -p "$STAGING"
          cp target/${{ matrix.target }}/release/markdown-reader "$STAGING/"
          cp LICENSE README.md "$STAGING/"
          TARBALL="${STAGING}.tar.gz"
          tar -czf "$TARBALL" "$STAGING"
          echo "TARBALL=$TARBALL" >> "$GITHUB_ENV"

      - name: Upload artifact
        uses: actions/upload-artifact@v7
        with:
          name: markdown-reader-${{ matrix.target }}
          path: ${{ env.TARBALL }}
          retention-days: 1

  build-windows:
    name: Build Windows (x86_64-pc-windows-msvc)
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v6

      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: x86_64-pc-windows-msvc

      - uses: Swatinem/rust-cache@v2
        with:
          key: windows-x86_64-pc-windows-msvc

      - name: Build binary
        run: cargo build --release --target x86_64-pc-windows-msvc --package markdown-tui-explorer

      - name: Package zip
        shell: pwsh
        run: |
          $version = $env:GITHUB_REF_NAME -replace '^v',''
          $staging = "markdown-reader-${version}-x86_64-pc-windows-msvc"
          New-Item -ItemType Directory -Path $staging | Out-Null
          Copy-Item "target\x86_64-pc-windows-msvc\release\markdown-reader.exe" $staging\
          Copy-Item "LICENSE" $staging\
          Copy-Item "README.md" $staging\
          $zipName = "${staging}.zip"
          Compress-Archive -Path $staging -DestinationPath $zipName
          "ZIPFILE=$zipName" | Out-File -FilePath $env:GITHUB_ENV -Append

      - name: Upload artifact
        uses: actions/upload-artifact@v7
        with:
          name: markdown-reader-x86_64-pc-windows-msvc
          path: ${{ env.ZIPFILE }}
          retention-days: 1

  release:
    name: Create GitHub Release
    needs: [build-linux, build-macos, build-windows]
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Download all artifacts
        uses: actions/download-artifact@v8
        with:
          path: artifacts
          merge-multiple: true

      - name: Generate SHA-256 checksums
        working-directory: artifacts
        run: sha256sum *.tar.gz *.zip > SHA256SUMS

      - name: Extract changelog section
        id: changelog
        run: |
          VERSION=${GITHUB_REF_NAME#v}
          if [ -f CHANGELOG.md ]; then
            # Extract the block between ## [version] and the next ## [
            BODY=$(awk "/^## \[${VERSION}\]/{found=1; next} found && /^## \[/{exit} found{print}" CHANGELOG.md)
          else
            BODY="Release ${GITHUB_REF_NAME}"
          fi
          printf '%s' "$BODY" > release_notes.md

      - name: Publish GitHub Release
        uses: softprops/action-gh-release@v3
        with:
          body_path: release_notes.md
          files: |
            artifacts/*.tar.gz
            artifacts/*.zip
            artifacts/SHA256SUMS
          token: ${{ secrets.GITHUB_TOKEN }}

  publish-crate:
    name: Publish parent crate to crates.io
    needs: [release]
    runs-on: ubuntu-latest
    if: >
      (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) ||
      (github.event_name == 'workflow_dispatch' && inputs.publish_crate == true)
    steps:
      - uses: actions/checkout@v6

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2

      - name: Publish parent crate
        # Only the parent (markdown-tui-explorer) ships on `v*.*.*` tags.
        # The library (mermaid-text) is released independently via
        # `mermaid-text-*` tags; if a `v*.*.*` release depends on a new
        # mermaid-text version, tag mermaid-text first.
        run: cargo publish --package markdown-tui-explorer --token ${{ secrets.CARGO_REGISTRY_TOKEN }}

  publish-homebrew:
    name: Update Homebrew tap
    needs: [release]
    runs-on: ubuntu-latest
    # Note: GitHub Actions does not allow `secrets.*` in job-level `if:`
    # (https://github.com/orgs/community/discussions/17245). Instead,
    # every step guards on a `HAS_TOKEN` job env that evaluates the
    # secret into a plain string. When the token is absent the job
    # "runs" but all steps no-op, leaving a clean summary.
    env:
      HAS_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN != '' && 'true' || 'false' }}
    steps:
      - name: Report skip reason
        if: env.HAS_TOKEN != 'true'
        run: echo "HOMEBREW_TAP_TOKEN is not set on this repo — skipping Homebrew formula publish."

      - name: Checkout source repo
        if: env.HAS_TOKEN == 'true'
        uses: actions/checkout@v6

      - name: Download release checksums
        if: env.HAS_TOKEN == 'true'
        # SHA256SUMS is an asset on the GitHub Release, not an inter-job
        # build artifact — it's generated inside the `release` job and
        # attached via softprops/action-gh-release. Fetch it directly.
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          mkdir -p artifacts
          gh release download "$GITHUB_REF_NAME" \
            -R "$GITHUB_REPOSITORY" \
            -p SHA256SUMS \
            -D artifacts

      - name: Checkout tap repo
        if: env.HAS_TOKEN == 'true'
        uses: actions/checkout@v6
        with:
          repository: leboiko/homebrew-tap
          token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
          path: homebrew-tap

      - name: Render formula
        if: env.HAS_TOKEN == 'true'
        run: |
          VERSION=${GITHUB_REF_NAME#v}
          mkdir -p homebrew-tap/Formula
          ./scripts/render-homebrew-formula.sh "$VERSION" artifacts/SHA256SUMS \
            > homebrew-tap/Formula/markdown-reader.rb

      - name: Commit and push
        if: env.HAS_TOKEN == 'true'
        working-directory: homebrew-tap
        run: |
          VERSION=${GITHUB_REF_NAME#v}
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          # Stage first, then check the index. Plain `git diff --quiet`
          # misses brand-new files (Formula/markdown-reader.rb on a
          # first-ever release wouldn't exist in the tap yet — the file
          # would be untracked, so unstaged-diff returns clean and the
          # workflow would exit thinking it was a no-op).
          git add Formula/markdown-reader.rb
          if git diff --cached --quiet; then
            echo "Formula already up to date for v${VERSION}"
            exit 0
          fi
          git commit -m "markdown-reader ${VERSION}"
          git push

  publish-aur:
    name: Publish AUR -bin package
    needs: [release]
    runs-on: ubuntu-latest
    # Same `HAS_KEY` gate pattern as `publish-homebrew` — the job runs
    # on every release, but every step is no-op'd when the secret is
    # missing so an unconfigured fork stays green.
    env:
      HAS_KEY: ${{ secrets.AUR_SSH_KEY != '' && 'true' || 'false' }}
    steps:
      - name: Report skip reason
        if: env.HAS_KEY != 'true'
        run: echo "AUR_SSH_KEY is not set on this repo — skipping AUR publish."

      - name: Checkout source repo
        if: env.HAS_KEY == 'true'
        uses: actions/checkout@v6

      - name: Download release checksums
        if: env.HAS_KEY == 'true'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          mkdir -p artifacts
          gh release download "$GITHUB_REF_NAME" \
            -R "$GITHUB_REPOSITORY" \
            -p SHA256SUMS \
            -D artifacts

      - name: Set up SSH for AUR
        if: env.HAS_KEY == 'true'
        env:
          AUR_SSH_KEY: ${{ secrets.AUR_SSH_KEY }}
        run: |
          mkdir -p ~/.ssh
          echo "$AUR_SSH_KEY" > ~/.ssh/aur
          chmod 600 ~/.ssh/aur
          # Pin the AUR host key fingerprint to prevent MITM. Pulled
          # from `ssh-keyscan -H aur.archlinux.org` and verified
          # against the published fingerprint at
          # https://wiki.archlinux.org/title/Aurweb_RPC_interface.
          ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts 2>/dev/null
          {
            echo "Host aur.archlinux.org"
            echo "  IdentityFile ~/.ssh/aur"
            echo "  User aur"
            echo "  StrictHostKeyChecking yes"
          } > ~/.ssh/config
          chmod 600 ~/.ssh/config

      - name: Clone AUR repo
        if: env.HAS_KEY == 'true'
        run: |
          # Clone (creates a local copy of the AUR-side repo for this
          # package). On first publish the AUR-side repo must exist —
          # see docs/RELEASING-AUR.md for the manual one-time setup.
          git clone ssh://aur@aur.archlinux.org/markdown-reader-bin.git aur-pkg

      - name: Render PKGBUILD + .SRCINFO
        if: env.HAS_KEY == 'true'
        run: |
          VERSION=${GITHUB_REF_NAME#v}
          ./scripts/render-aur-pkgbuild.sh "$VERSION" artifacts/SHA256SUMS aur-pkg

      - name: Commit and push
        if: env.HAS_KEY == 'true'
        working-directory: aur-pkg
        run: |
          VERSION=${GITHUB_REF_NAME#v}
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add PKGBUILD .SRCINFO
          if git diff --cached --quiet; then
            echo "AUR PKGBUILD already up to date for v${VERSION}"
            exit 0
          fi
          git commit -m "markdown-reader ${VERSION}"
          git push