Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 34 additions & 58 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
inputs:
tag_name:
description: 'Release tag (e.g. v1.5.5)'
required: true
default: 'v1.5.5'

permissions:
contents: write

jobs:
build:
strategy:
# don't kill the macOS build if Windows fails (or vice versa) — release
# still ships partial artifacts for users on the platform that built ok.
fail-fast: false
matrix:
include:
Expand All @@ -32,18 +36,12 @@ jobs:
runs-on: ${{ matrix.os }}
timeout-minutes: 45
env:
# flag at job level for the cert-import step's `if:` condition.
# we DON'T put the actual cert material at job level — if we did,
# an empty APPLE_CERTIFICATE env var would leak into tauri-action,
# which sees the var as "set" and tries to `security import` empty
# data, hard-failing the bundle step.
HAS_APPLE_CERT: ${{ secrets.APPLE_CERTIFICATE != '' }}
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag_name || github.ref_name }}
steps:
- name: checkout
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
# need full history + tags so we can derive a per-release commit list
# via `git log <prev_tag>..<this_tag>` for the changelog.
fetch-depth: 0
fetch-tags: true

Expand All @@ -52,18 +50,16 @@ jobs:
shell: bash
run: |
set -e
# find the most recent tag BEFORE the one we're building. `git tag --sort`
# gives semver-ordered tags; we filter out the current ref and pick the next.
PREV_TAG=$(git tag --sort=-version:refname | grep -v "^${{ github.ref_name }}$" | head -1 || true)
PREV_TAG=$(git tag --sort=-version:refname | grep -v "^${{ env.RELEASE_TAG }}$" | head -1 || true)
if [ -z "$PREV_TAG" ]; then
COMMITS="- first public release 🐙"
DIFF_URL=""
else
COMMITS=$(git log "$PREV_TAG..${{ github.ref_name }}" --pretty=format:'- %s' --no-merges --reverse)
COMMITS=$(git log "$PREV_TAG..${{ env.RELEASE_TAG }}" --pretty=format:'- %s' --no-merges --reverse)
if [ -z "$COMMITS" ]; then
COMMITS="- (no commit changes since $PREV_TAG — version bump only)"
fi
DIFF_URL="full diff: https://github.com/mattenarle10/markamd/compare/$PREV_TAG...${{ github.ref_name }}"
DIFF_URL="full diff: https://github.com/${{ github.repository }}/compare/$PREV_TAG...${{ env.RELEASE_TAG }}"
fi
echo "PREV_TAG=$PREV_TAG" >> "$GITHUB_OUTPUT"
{
Expand All @@ -79,7 +75,7 @@ jobs:
env:
COMMITS: ${{ steps.changelog.outputs.COMMITS }}
DIFF_URL: ${{ steps.changelog.outputs.DIFF_URL }}
TAG_NAME: ${{ github.ref_name }}
TAG_NAME: ${{ env.RELEASE_TAG }}
run: |
set -e
NOTES_FILE="docs/release-notes/${TAG_NAME}.md"
Expand Down Expand Up @@ -111,7 +107,7 @@ jobs:
targets: ${{ matrix.target }}

- name: cache cargo
uses: actions/cache@v5
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
Expand All @@ -136,6 +132,16 @@ jobs:
- name: install dependencies
run: bun install

- name: disable updater artifact signing (no signing key required)
shell: bash
run: |
node -e "
const fs = require('fs');
const cfg = JSON.parse(fs.readFileSync('src-tauri/tauri.conf.json','utf8'));
cfg.bundle.createUpdaterArtifacts = false;
fs.writeFileSync('src-tauri/tauri.conf.json', JSON.stringify(cfg, null, 2));
"

- name: import apple cert + propagate signing env (macOS only)
if: matrix.os == 'macos-latest' && env.HAS_APPLE_CERT == 'true'
env:
Expand All @@ -155,10 +161,6 @@ jobs:
security import /tmp/cert.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
rm /tmp/cert.p12
# propagate the signing env to later steps so tauri-action can read it.
# this step is skipped when HAS_APPLE_CERT=false or non-macOS → these
# vars are never written → tauri-action's env contains no APPLE_* →
# Tauri auto-skips signing instead of running `codesign --sign ""`.
{
echo "APPLE_SIGNING_IDENTITY=$APPLE_SIGNING_IDENTITY"
echo "APPLE_ID=$APPLE_ID"
Expand All @@ -170,43 +172,24 @@ jobs:
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Apple signing env injected by the cert-import step via $GITHUB_ENV
# (macOS only — Windows runner never sees these).
# Tauri updater signing — signs the bundle + emits latest.json. Set on
# both runners so macOS .app.tar.gz.sig and Windows .nsis.zip.sig are
# produced and the updater can verify upgrades cross-platform.
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
tagName: ${{ github.ref_name }}
releaseName: "marka.md ${{ github.ref_name }}"
tagName: ${{ env.RELEASE_TAG }}
releaseName: "marka.md ${{ env.RELEASE_TAG }}"
releaseBody: ${{ steps.release_body.outputs.BODY }}
# publish as a DRAFT so the matrix runners can both upload artifacts
# (including their per-platform latest.json entries) without racing
# to overwrite each other on the same live release. the publish-release
# job below flips the draft to public AFTER both legs complete + a
# consolidated latest.json with both `darwin-aarch64` and
# `windows-x86_64` platforms is in place.
# ref: https://v2.tauri.app/distribute/pipelines/github/
releaseDraft: true
prerelease: false
args: ${{ matrix.args }}

# tauri-action emits `marka.md_<version>_<arch>.dmg` by default. Rename
# per arch so users get clean filenames + the landing dropdown can keep
# linking to `marka.md.dmg` (apple-silicon default). Intel gets a clear
# `_intel` suffix. Both rename steps are guarded by their matrix target
# so the two macOS legs don't race.
- name: rename dmg → marka.md.dmg (apple silicon)
if: matrix.target == 'aarch64-apple-darwin' && success()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
set -e
RELEASE_ID=$(gh api repos/${{ github.repository }}/releases --jq '.[] | select(.tag_name == "${{ github.ref_name }}") | .id' | head -1)
RELEASE_ID=$(gh api repos/${{ github.repository }}/releases --jq '.[] | select(.tag_name == "${{ env.RELEASE_TAG }}") | .id' | head -1)
if [ -z "$RELEASE_ID" ]; then
echo "no release found for tag ${{ github.ref_name }} — skipping rename"
echo "no release found for tag ${{ env.RELEASE_TAG }} — skipping rename"
exit 0
fi
ASSET_ID=$(gh api "repos/${{ github.repository }}/releases/$RELEASE_ID/assets" --jq '.[] | select(.name | test("aarch64.*\\.dmg$")) | .id' | head -1)
Expand All @@ -224,9 +207,9 @@ jobs:
shell: bash
run: |
set -e
RELEASE_ID=$(gh api repos/${{ github.repository }}/releases --jq '.[] | select(.tag_name == "${{ github.ref_name }}") | .id' | head -1)
RELEASE_ID=$(gh api repos/${{ github.repository }}/releases --jq '.[] | select(.tag_name == "${{ env.RELEASE_TAG }}") | .id' | head -1)
if [ -z "$RELEASE_ID" ]; then
echo "no release found for tag ${{ github.ref_name }} — skipping rename"
echo "no release found for tag ${{ env.RELEASE_TAG }} — skipping rename"
exit 0
fi
ASSET_ID=$(gh api "repos/${{ github.repository }}/releases/$RELEASE_ID/assets" --jq '.[] | select(.name | test("x64.*\\.dmg$")) | .id' | head -1)
Expand All @@ -237,31 +220,24 @@ jobs:
echo "no dmg asset found — skipping rename"
fi

# flip the draft release to public AFTER both matrix runners have finished
# uploading their artifacts + latest.json entries. this prevents the
# cross-platform race where each runner could overwrite the other's
# latest.json on a live release.
publish-release:
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
env:
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag_name || github.ref_name }}
steps:
- name: publish draft release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "publishing draft release for ${{ github.ref_name }}"
gh release edit "${{ github.ref_name }}" \
echo "publishing draft release for ${{ env.RELEASE_TAG }}"
gh release edit "${{ env.RELEASE_TAG }}" \
--repo "${{ github.repository }}" \
--draft=false \
--latest

# fires a Vercel deploy hook after a release publishes so markamd.vercel.app
# rebuilds and picks up the new version automatically (no manual empty
# commit needed). gracefully no-ops if the secret isn't set yet.
# NOTE: GitHub Actions does not allow `secrets.*` in `if:` expressions —
# they must be evaluated through env first (same pattern as HAS_APPLE_CERT).
notify-site:
needs: publish-release
runs-on: ubuntu-latest
Expand All @@ -273,7 +249,7 @@ jobs:
env:
DEPLOY_HOOK: ${{ secrets.VERCEL_DEPLOY_HOOK }}
run: |
echo "triggering markamd-site rebuild for ${{ github.ref_name }}"
echo "triggering markamd-site rebuild"
curl -fsS -X POST "$DEPLOY_HOOK"
- name: no hook configured
if: env.HAS_HOOK != 'true'
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ dist-ssr
# personal scratchpad — never committed
notes/

# local MSVC build environment override (machine-specific paths, breaks Linux CI)
src-tauri/.cargo/config.toml

# secrets — defensive ignores. real secrets live in 1Password + GH secrets,
# but if someone accidentally drops a .p12 / .key / .pem into the repo, git
# will refuse to track it instead of leaking it to a PR.
Expand Down
4 changes: 2 additions & 2 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "marka"
version = "1.5.4"
version = "1.5.6"
description = "marka.md — a local markdown editor for the notes you share with ai"
authors = ["Matt Enarle"]
edition = "2021"
Expand All @@ -18,7 +18,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2", features = [] }

[dependencies]
tauri = { version = "2", features = ["macos-private-api"] }
tauri = { version = "2", features = [] }
tauri-plugin-opener = "2"
tauri-plugin-fs = "2"
tauri-plugin-dialog = "2"
Expand Down
35 changes: 34 additions & 1 deletion src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,39 @@ use tauri::{Emitter, Manager, RunEvent};

struct PendingOpenFiles(Mutex<Vec<String>>);

#[tauri::command]
fn reveal_in_file_manager(path: String) {
let p = std::path::Path::new(&path);
#[cfg(target_os = "windows")]
{
let target = if p.is_dir() {
path.clone()
} else {
p.parent()
.and_then(|d| d.to_str())
.unwrap_or("")
.to_string()
};
let _ = std::process::Command::new("explorer").arg(target).spawn();
}
#[cfg(target_os = "macos")]
{
let _ = std::process::Command::new("open").args(["-R", &path]).spawn();
}
#[cfg(target_os = "linux")]
{
let target = if p.is_dir() {
p.to_str().unwrap_or("").to_string()
} else {
p.parent()
.and_then(|d| d.to_str())
.unwrap_or("")
.to_string()
};
let _ = std::process::Command::new("xdg-open").arg(target).spawn();
}
}

#[tauri::command]
fn take_pending_open_files(state: State<'_, PendingOpenFiles>) -> Vec<String> {
let mut pending = state
Expand All @@ -32,7 +65,7 @@ pub fn run() {
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_process::init())
.invoke_handler(tauri::generate_handler![take_pending_open_files])
.invoke_handler(tauri::generate_handler![take_pending_open_files, reveal_in_file_manager])
.setup(|_app| {
#[cfg(target_os = "macos")]
{
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://schema.tauri.app/config/2",
"productName": "marka.md",
"mainBinaryName": "marka.md",
"version": "1.5.4",
"version": "1.5.6",
"identifier": "com.mattenarle.markamd",
"build": {
"beforeDevCommand": "bun run dev",
Expand Down
8 changes: 8 additions & 0 deletions src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@
.mdv-app > .mdv-shell { grid-row: 3; }
.mdv-app > .mdv-statusbar { grid-row: 4; }

/* titlebar hidden mode */
.mdv-app.has-hidden-titlebar {
grid-template-rows: 0 var(--breadcrumb-h) 1fr var(--statusbar-h);
}
.mdv-app.has-hidden-titlebar .mdv-titlebar {
display: none;
}

/* reading mode — Preview is rendered directly as flex-1 child of .mdv-shell */
.mdv-app.is-reading {
grid-template-rows: var(--titlebar-h) 0 1fr 0;
Expand Down
Loading