Skip to content
Merged
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
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,3 @@ Package.resolved
.DS_Store
*.swp
*.swo

# CLI wrapper (generated by build.sh)
/macnotifier
Binary file added Resources/AppIcon.icns
Binary file not shown.
13 changes: 13 additions & 0 deletions Resources/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
84 changes: 84 additions & 0 deletions bin/macnotifier
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/bin/bash
set -euo pipefail

# Resolve symlinks so the script works when invoked via symlink (e.g. Homebrew).
SELF="$0"
if [ -L "$SELF" ]; then
TARGET="$(readlink "$SELF")"
case "$TARGET" in
/*) SELF="$TARGET" ;;
*) SELF="$(dirname "$SELF")/$TARGET" ;;
esac
fi
SCRIPT_DIR="$(cd "$(dirname "$SELF")" && pwd)"
APP_BUNDLE="$(cd "$SCRIPT_DIR/.." && pwd)/macnotifier.app"

# Handle -h/--help directly so output is visible in the terminal.
# open -n does not connect the app's stdout/stderr to the terminal.
for arg in "$@"; do
case "$arg" in
-h|--help)
cat <<'USAGE'
Usage: macnotifier [options]

Options:
-t, --title <title> Notification title (default: "macnotifier")
-m, --message <message> Notification message (required)
-e, --execute <command> Shell command to execute on click
-a, --activate <id> Bundle ID of app to activate on click
--sound <name> Sound name in ~/Library/Sounds or /System/Library/Sounds (e.g. "Glass")
--icon <path> Path to image file to attach as icon
-h, --help Show this help message
USAGE
exit 0
;;
esac
done

# Validate -m is present with a value before launching the app.
has_message=false
expect_value=false
for arg in "$@"; do
if [ "$expect_value" = true ]; then
has_message=true
break
fi
case "$arg" in
-m|--message) expect_value=true ;;
esac
done

if [ "$expect_value" = true ] && [ "$has_message" = false ]; then
echo "Error: -m requires a value" >&2
exit 1
fi

if [ "$has_message" = false ]; then
echo "Error: -m (message) is required" >&2
exit 1
fi

if [ ! -d "$APP_BUNDLE" ]; then
echo "Error: $APP_BUNDLE not found (run scripts/build.sh first)" >&2
exit 1
fi

# Convert --icon relative paths to absolute paths.
# open -n changes the working directory, so relative paths would break.
args=()
while [ $# -gt 0 ]; do
if [ "$1" = "--icon" ] && [ $# -ge 2 ]; then
args+=("$1")
shift
case "$1" in
/*) args+=("$1") ;; # absolute path: pass through
~*) args+=("$1") ;; # tilde path: Swift expands it
*) args+=("$PWD/$1") ;; # relative path: convert to absolute
esac
else
args+=("$1")
fi
shift
done

open -n "$APP_BUNDLE" --args "${args[@]}"
2 changes: 2 additions & 0 deletions scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ cat > "$APP_BUNDLE/Contents/Info.plist" <<PLIST
<string>1.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>LSUIElement</key>
<true/>
</dict>
Expand Down
63 changes: 63 additions & 0 deletions scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,69 @@ run_test_output "-h shows --sound option" 0 "--sound" -h
# Test: -h shows --icon option
run_test_output "-h shows --icon option" 0 "--icon" -h

# CLI wrapper tests
WRAPPER="$PROJECT_DIR/bin/macnotifier"

run_wrapper_test() {
local name="$1"
local expected_exit="$2"
local expected_pattern="$3"
shift 3

set +e
local output
output=$("$WRAPPER" "$@" 2>&1)
local actual_exit=$?
set -e

if [ "$actual_exit" -ne "$expected_exit" ]; then
echo "FAIL: $name (expected exit $expected_exit, got $actual_exit)"
failed=$((failed + 1))
return
fi

if echo "$output" | grep -qi -- "$expected_pattern"; then
echo "PASS: $name"
passed=$((passed + 1))
else
echo "FAIL: $name (output missing '$expected_pattern')"
failed=$((failed + 1))
fi
}

if [ ! -x "$WRAPPER" ]; then
echo "FAIL: CLI wrapper not found or not executable at $WRAPPER"
failed=$((failed + 1))
else
echo "PASS: CLI wrapper exists and is executable"
passed=$((passed + 1))

# Test: wrapper -h shows usage with "Usage" in output
run_wrapper_test "wrapper -h shows help" 0 "Usage" -h

# Test: wrapper without -m shows error
run_wrapper_test "wrapper missing -m exits 1" 1 "required"

# Test: wrapper -m without value shows error
run_wrapper_test "wrapper -m without value exits 1" 1 "requires" -m

# Test: wrapper works via symlink
SYMLINK_DIR="$(mktemp -d)"
ln -s "$WRAPPER" "$SYMLINK_DIR/macnotifier"
set +e
symlink_output=$("$SYMLINK_DIR/macnotifier" -h 2>&1)
symlink_exit=$?
set -e
rm -rf "$SYMLINK_DIR"
if [ "$symlink_exit" -eq 0 ] && echo "$symlink_output" | grep -qi "Usage"; then
echo "PASS: wrapper works via symlink"
passed=$((passed + 1))
else
echo "FAIL: wrapper via symlink (exit $symlink_exit)"
failed=$((failed + 1))
fi
fi

echo ""
echo "Results: $passed passed, $failed failed"

Expand Down