Skip to content
Back to skills

Release Macos Spm Packaging

ASecurity

Scaffold, build, and package SwiftPM-based macOS apps without Xcode project. Use when you need a from-scratch macOS app layout, SwiftPM targets/resources, custom .app bundle assembly, or signing/notarization steps outside Xcode.

  • 3 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 2, 2026
ai-agentsgoswiftbashgitsecurity

Security analysis

A93/100
  • highPerforms destructive filesystem operations

Pro shows the line behind each finding and how to fix it

Scanned September 2, 2026

npx -y skills add patrickserrano/lacquer --skill release-macos-spm-packaging --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Release Macos Spm Packaging?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Release Macos Spm Packaging
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/patrickserrano-release-macos-spm-packaging/badge)](https://www.skillsdirectory.com/skills/patrickserrano-release-macos-spm-packaging)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: release-macos-spm-packaging
description: Scaffold, build, and package SwiftPM-based macOS apps without Xcode project. Use when you need a from-scratch macOS app layout, SwiftPM targets/resources, custom .app bundle assembly, or signing/notarization steps outside Xcode.
---

# macOS SwiftPM App Packaging

## Overview

Bootstrap a complete SwiftPM macOS app, then build, package, and run it without Xcode. This skill covers the full workflow from project scaffolding to release distribution.

## Project Scaffolding

### Basic Structure

```
MyApp/
├── Package.swift
├── Sources/
│   └── MyApp/
│       ├── MyApp.swift          # @main App entry
│       └── ContentView.swift
├── Resources/
│   ├── Assets.xcassets/
│   └── Info.plist
├── Scripts/
│   ├── package_app.sh
│   ├── compile_and_run.sh
│   └── sign-and-notarize.sh
└── version.env
```

### Package.swift

```swift
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "MyApp",
    platforms: [.macOS(.v14)],
    products: [
        .executable(name: "MyApp", targets: ["MyApp"])
    ],
    targets: [
        .executableTarget(
            name: "MyApp",
            resources: [
                .process("Resources")
            ]
        )
    ]
)
```

### version.env

```bash
APP_NAME="MyApp"
BUNDLE_ID="com.example.myapp"
VERSION="1.0.0"
BUILD_NUMBER="1"
MIN_MACOS="14.0"
# Set to 1 for menu bar apps
MENU_BAR_APP=0
```

## Build and Run

### Build with SwiftPM

```bash
# Debug build
swift build

# Release build
swift build -c release

# Run tests
swift test
```

### Package as .app Bundle

Create `Scripts/package_app.sh`:

```bash
#!/bin/bash
set -e

source version.env

BUILD_DIR=".build/release"
APP_BUNDLE="$BUILD_DIR/$APP_NAME.app"
CONTENTS="$APP_BUNDLE/Contents"
MACOS="$CONTENTS/MacOS"
RESOURCES="$CONTENTS/Resources"

# Build release
swift build -c release

# Create bundle structure
rm -rf "$APP_BUNDLE"
mkdir -p "$MACOS" "$RESOURCES"

# Copy binary
cp "$BUILD_DIR/$APP_NAME" "$MACOS/"

# Copy resources
cp -r Resources/* "$RESOURCES/" 2>/dev/null || true

# Generate Info.plist
cat > "$CONTENTS/Info.plist" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleExecutable</key>
    <string>$APP_NAME</string>
    <key>CFBundleIdentifier</key>
    <string>$BUNDLE_ID</string>
    <key>CFBundleName</key>
    <string>$APP_NAME</string>
    <key>CFBundleVersion</key>
    <string>$BUILD_NUMBER</string>
    <key>CFBundleShortVersionString</key>
    <string>$VERSION</string>
    <key>LSMinimumSystemVersion</key>
    <string>$MIN_MACOS</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
$([ "$MENU_BAR_APP" = "1" ] && echo "    <key>LSUIElement</key>
    <true/>")
</dict>
</plist>
EOF

echo "Created $APP_BUNDLE"
```

### Development Run Script

Create `Scripts/compile_and_run.sh`:

```bash
#!/bin/bash
set -e

source version.env

# Kill existing instance
pkill -x "$APP_NAME" 2>/dev/null || true

# Package
./Scripts/package_app.sh

# Launch
open ".build/release/$APP_NAME.app"
```

## Code Signing

### Development Signing

```bash
# Sign for local development
codesign --force --sign - ".build/release/MyApp.app"

# Or with a specific identity
codesign --force --sign "Developer ID Application: Your Name" ".build/release/MyApp.app"
```

### Create Stable Dev Identity

```bash
# Generate self-signed certificate for consistent dev signing
security create-keychain -p "" dev-signing.keychain
security default-keychain -s dev-signing.keychain
# Follow prompts in Keychain Access to create certificate
```

## Notarization and Release

**One-time setup.** Notarization needs the full Xcode.app, not the lightweight
Command Line Tools package — CLT omits `notarytool`/`stapler`. Store credentials
once so scripts never carry a plaintext password:

```bash
xcrun notarytool store-credentials "AC_PASSWORD" \
    --apple-id "your@email.com" \
    --team-id "TEAM_ID"
# Prompts interactively for an app-specific password (appleid.apple.com,
# Sign-In and Security -> App-Specific Passwords). Stored in the login
# keychain under the given profile name; `--keychain-profile "AC_PASSWORD"`
# below reads it back. Regenerate if the Apple ID password ever changes --
# app-specific passwords go stale silently, with no warning at submit time.
```

Create `Scripts/sign-and-notarize.sh`:

```bash
#!/bin/bash
set -e

source version.env

APP_PATH=".build/release/$APP_NAME.app"
ZIP_PATH=".build/release/$APP_NAME-$VERSION.zip"

# Sign with Developer ID
codesign --force --options runtime --sign "Developer ID Application: Your Name" "$APP_PATH"

# Create zip for notarization
ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH"

# Submit for notarization (--keychain-profile reads the credentials stored
# above by `store-credentials` -- notarytool does not take a literal
# --password value or the altool-style "@keychain:" reference syntax)
xcrun notarytool submit "$ZIP_PATH" \
    --keychain-profile "AC_PASSWORD" \
    --wait

# Staple the ticket
xcrun stapler staple "$APP_PATH"

# Re-zip with stapled ticket
rm "$ZIP_PATH"
ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH"

echo "Release ready: $ZIP_PATH"
```

### Verify the release

Run all three before shipping — each catches a different failure mode (wrong
signing identity, Gatekeeper rejection, missing/unstapled ticket):

```bash
codesign -dv --verbose=4 "$APP_PATH"      # confirms who signed it and with what identity
spctl -a -vvv -t exec "$APP_PATH"         # confirms Gatekeeper will actually accept it
xcrun stapler validate "$APP_PATH"        # confirms the notarization ticket is attached
```

## Sparkle Updates (Optional)

### Generate Appcast Entry

```bash
#!/bin/bash
source version.env

ZIP_PATH=".build/release/$APP_NAME-$VERSION.zip"
SIZE=$(stat -f%z "$ZIP_PATH")
SIGNATURE=$(./bin/sign_update "$ZIP_PATH")
DATE=$(date -R)

cat << EOF
<item>
    <title>Version $VERSION</title>
    <pubDate>$DATE</pubDate>
    <sparkle:version>$BUILD_NUMBER</sparkle:version>
    <sparkle:shortVersionString>$VERSION</sparkle:shortVersionString>
    <enclosure
        url="https://example.com/releases/$APP_NAME-$VERSION.zip"
        length="$SIZE"
        type="application/octet-stream"
        sparkle:edSignature="$SIGNATURE"
    />
</item>
EOF
```

## GitHub Release

```bash
# Create tag
git tag -a "v$VERSION" -m "Release $VERSION"
git push origin "v$VERSION"

# Create GitHub release
gh release create "v$VERSION" \
    ".build/release/$APP_NAME-$VERSION.zip" \
    --title "v$VERSION" \
    --notes "Release notes here"
```

## Checklist

### Scaffolding
- [ ] Package.swift with correct targets and resources
- [ ] version.env with app metadata
- [ ] Info.plist template or generation script
- [ ] Basic app entry point (@main App)

### Build
- [ ] `swift build` succeeds
- [ ] `swift test` passes
- [ ] Resources copied correctly

### Packaging
- [ ] .app bundle structure correct
- [ ] Info.plist generated with correct values
- [ ] App launches from Finder

### Release
- [ ] Code signed with Developer ID
- [ ] Notarized and stapled
- [ ] Verified with `codesign -dv`, `spctl -a -vvv -t exec`, and `stapler validate`
- [ ] Zip created for distribution
- [ ] (Optional) Sparkle appcast updated

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…