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
47 changes: 47 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Bug report
description: Report reproducible behavior that is not working as expected.
title: "[Bug]: "
labels:
- bug
body:
- type: markdown
attributes:
value: Please search existing issues before filing a new report.
- type: input
id: version
attributes:
label: Ledge version
placeholder: "1.0.0"
validations:
required: true
- type: input
id: macos
attributes:
label: macOS and Mac model
placeholder: "macOS 15.6, MacBook Pro 14-inch (M3 Pro)"
validations:
required: true
- type: textarea
id: steps
attributes:
label: Reproduction steps
description: Include the smallest reliable sequence that triggers the problem.
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual behavior
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional context
description: Add screenshots or relevant Console logs. Remove personal data and credentials.
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Report a security vulnerability
url: https://github.com/aramr/Ledge/security/advisories/new
about: Submit security concerns privately.
24 changes: 24 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Feature request
description: Suggest a focused improvement to Ledge.
title: "[Feature]: "
labels:
- enhancement
body:
- type: textarea
id: problem
attributes:
label: Problem
description: What user problem would this solve?
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposed behavior
description: Describe the interaction and expected result.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
13 changes: 13 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: 2
updates:
- package-ecosystem: swift
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5

- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
11 changes: 11 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## Summary

Describe the user-visible change and why it is needed.

## Verification

- [ ] `Scripts/ci.sh` passes
- [ ] I tested the affected flow on macOS 15 or later
- [ ] I added or updated tests where practical
- [ ] I updated user-facing documentation when needed
- [ ] This change does not add credentials, generated builds, or personal data
20 changes: 20 additions & 0 deletions .github/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
changelog:
exclude:
labels:
- skip-changelog
categories:
- title: Features
labels:
- enhancement
- title: Fixes
labels:
- bug
- title: Documentation
labels:
- documentation
- title: Dependencies
labels:
- dependencies
- title: Other changes
labels:
- "*"
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: CI

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
build-and-test:
runs-on: macos-26
timeout-minutes: 30

steps:
- name: Check out source
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0

- name: Select Xcode 26
run: |
xcode_path="$(find /Applications -maxdepth 1 -type d -name 'Xcode_26*.app' | sort -V | tail -1)"
test -n "$xcode_path"
sudo xcode-select --switch "$xcode_path/Contents/Developer"
xcodebuild -version

- name: Build and test
run: Scripts/ci.sh
171 changes: 171 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
name: Release

on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"

permissions:
contents: write
id-token: write
attestations: write

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
release:
if: github.repository == 'aramr/Ledge'
runs-on: macos-26
timeout-minutes: 60

env:
APPLE_TEAM_ID: M7PFX75L8L
SIGNING_IDENTITY: Developer ID Application

steps:
- name: Check out source
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
fetch-depth: 0

- name: Select Xcode 26
run: |
xcode_path="$(find /Applications -maxdepth 1 -type d -name 'Xcode_26*.app' | sort -V | tail -1)"
test -n "$xcode_path"
sudo xcode-select --switch "$xcode_path/Contents/Developer"
xcodebuild -version

- name: Validate release version
id: version
run: |
version="${GITHUB_REF_NAME#v}"
project_version="$(xcodebuild -project MacDynamicIsland.xcodeproj -scheme Ledge -configuration Release -showBuildSettings | awk -F ' = ' '/ MARKETING_VERSION = / { print $2; exit }')"
test "$version" = "$project_version"
echo "value=$version" >> "$GITHUB_OUTPUT"

- name: Validate release credentials
env:
CERTIFICATE_P12_BASE64: ${{ secrets.MACOS_CERTIFICATE_P12_BASE64 }}
CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
API_KEY_P8_BASE64: ${{ secrets.APPLE_API_KEY_P8_BASE64 }}
API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
HOMEBREW_TAP_DEPLOY_KEY: ${{ secrets.HOMEBREW_TAP_DEPLOY_KEY }}
run: |
missing=()
for variable_name in \
CERTIFICATE_P12_BASE64 \
CERTIFICATE_PASSWORD \
API_KEY_P8_BASE64 \
API_KEY_ID \
API_ISSUER_ID \
SPARKLE_PRIVATE_KEY \
HOMEBREW_TAP_DEPLOY_KEY
do
if test -z "${!variable_name}"; then
missing+=("$variable_name")
fi
done
if (( ${#missing[@]} > 0 )); then
printf 'Missing release credential: %s\n' "${missing[@]}" >&2
exit 1
fi

- name: Run release gates
run: Scripts/ci.sh

- name: Import Developer ID certificate
env:
CERTIFICATE_P12_BASE64: ${{ secrets.MACOS_CERTIFICATE_P12_BASE64 }}
CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
run: |
test -n "$CERTIFICATE_P12_BASE64"
test -n "$CERTIFICATE_PASSWORD"
certificate_path="$RUNNER_TEMP/developer-id.p12"
keychain_path="$RUNNER_TEMP/ledge-signing.keychain-db"
keychain_password="$(openssl rand -hex 32)"
printf '%s' "$CERTIFICATE_P12_BASE64" | base64 -D > "$certificate_path"
security create-keychain -p "$keychain_password" "$keychain_path"
security set-keychain-settings -lut 21600 "$keychain_path"
security unlock-keychain -p "$keychain_password" "$keychain_path"
security import "$certificate_path" -P "$CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$keychain_path"
security set-key-partition-list -S apple-tool:,apple: -s -k "$keychain_password" "$keychain_path"
security list-keychains -d user -s "$keychain_path"
security find-identity -v -p codesigning "$keychain_path"

- name: Prepare notarization credentials
env:
API_KEY_P8_BASE64: ${{ secrets.APPLE_API_KEY_P8_BASE64 }}
run: |
test -n "$API_KEY_P8_BASE64"
printf '%s' "$API_KEY_P8_BASE64" | base64 -D > "$RUNNER_TEMP/AuthKey.p8"
chmod 600 "$RUNNER_TEMP/AuthKey.p8"

- name: Build signed archive
run: Scripts/build-release.sh "$RUNNER_TEMP/LedgeRelease"

- name: Notarize and package release
env:
APPLE_API_KEY_PATH: ${{ runner.temp }}/AuthKey.p8
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
SPARKLE_BIN_DIRECTORY: ${{ runner.temp }}/LedgeRelease/DerivedData/SourcePackages/artifacts/sparkle/Sparkle/bin
run: |
Scripts/package-release.sh "$RUNNER_TEMP/LedgeRelease/Export/Ledge.app" dist

- name: Attest release artifacts
uses: actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d # v4.2.1
with:
subject-path: |
dist/Ledge-${{ steps.version.outputs.value }}.dmg
dist/Ledge-${{ steps.version.outputs.value }}.zip
dist/appcast.xml
dist/SHA256SUMS.txt

- name: Publish GitHub release
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ steps.version.outputs.value }}
run: |
assets=(
"dist/Ledge-$VERSION.dmg"
"dist/Ledge-$VERSION.zip"
"dist/appcast.xml"
"dist/SHA256SUMS.txt"
)
if test -f "dist/Ledge-$VERSION-dSYM.zip"; then
assets+=("dist/Ledge-$VERSION-dSYM.zip")
fi
gh release create "$GITHUB_REF_NAME" "${assets[@]}" \
--generate-notes \
--title "Ledge $VERSION" \
--verify-tag

- name: Update Homebrew tap
env:
HOMEBREW_TAP_DEPLOY_KEY: ${{ secrets.HOMEBREW_TAP_DEPLOY_KEY }}
VERSION: ${{ steps.version.outputs.value }}
run: |
test -n "$HOMEBREW_TAP_DEPLOY_KEY"
mkdir -p "$HOME/.ssh"
chmod 700 "$HOME/.ssh"
printf '%s\n' "$HOMEBREW_TAP_DEPLOY_KEY" > "$HOME/.ssh/ledge-homebrew-tap"
chmod 600 "$HOME/.ssh/ledge-homebrew-tap"
ssh-keyscan github.com >> "$HOME/.ssh/known_hosts"
export GIT_SSH_COMMAND="ssh -i $HOME/.ssh/ledge-homebrew-tap -o IdentitiesOnly=yes"
brew tap aramr/tap git@github.com:aramr/homebrew-tap.git
tap_directory="$(brew --repository aramr/tap)"
mkdir -p "$tap_directory/Casks"
dmg_sha256="$(shasum -a 256 "dist/Ledge-$VERSION.dmg" | awk '{print $1}')"
Scripts/generate-cask.sh "$VERSION" "$dmg_sha256" > "$tap_directory/Casks/ledge.rb"
brew style "$tap_directory/Casks/ledge.rb"
brew audit --cask --strict aramr/tap/ledge
git -C "$tap_directory" config user.name "github-actions[bot]"
git -C "$tap_directory" config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git -C "$tap_directory" add Casks/ledge.rb
git -C "$tap_directory" commit -m "Update Ledge to $VERSION"
git -C "$tap_directory" push
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
DerivedData/
.derivedData/
.distributionDerivedData/
.build/
.DS_Store
xcuserdata/
*.xcuserstate
Expand Down
23 changes: 23 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Contributor Covenant Code of Conduct

## Our pledge

We pledge to make participation in this project a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socioeconomic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.

## Our standards

Examples of behavior that contributes to a positive environment include demonstrating empathy, respecting differing opinions, giving and accepting constructive feedback, taking responsibility for mistakes, and focusing on what is best for the community.

Unacceptable behavior includes sexualized language or attention, trolling or insulting comments, harassment, publishing others' private information without permission, and other conduct that could reasonably be considered inappropriate in a professional setting.

## Enforcement

Project maintainers are responsible for clarifying and enforcing these standards. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported privately through the contact method in `SECURITY.md`. All reports will be reviewed promptly and fairly.

Maintainers may remove, edit, or reject contributions and may temporarily or permanently ban contributors for behavior they deem inappropriate, threatening, offensive, or harmful.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
34 changes: 34 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Contributing to Ledge

Thanks for helping improve Ledge.

## Before opening a change

- Search existing issues and pull requests first.
- Open an issue before starting a large feature or architectural change.
- Keep pull requests focused and avoid unrelated formatting changes.
- Never commit credentials, signing material, generated build products, or user data.

## Development

Ledge requires macOS 15 or later and Xcode 26 or later.

```sh
git clone https://github.com/aramr/Ledge.git
cd Ledge
Scripts/ci.sh
```

You can also open `MacDynamicIsland.xcodeproj`, select the Ledge scheme, and run the app on My Mac. Debug builds use `com.aramrahimi.Ledge.debug`; production releases use `com.aramrahimi.Ledge`.

## Pull requests

Every pull request should:

- explain the user-visible behavior and motivation;
- include tests for new model or service behavior where practical;
- pass `Scripts/ci.sh`;
- preserve the local-first privacy model described in `PRIVACY.md`;
- update documentation when installation, settings, or user-facing behavior changes.

By contributing, you agree that your contribution is licensed under the MIT License.
Loading