Release macos spm packaging
Skill patrickserrano/lacquer/profiles/ios/skills/release-macos-spm-packaging
Go CLI + profile templates that standardize how Claude Code works across every project
npx -y skills add patrickserrano/lacquer --skill release-macos-spm-packagingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
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.
SKILL.md
7.3 KB, as published. Nobody here has run it
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-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
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
# Debug build
swift build
# Release build
swift build -c release
# Run tests
swift test
Package as .app Bundle
Create Scripts/package_app.sh:
#!/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:
#!/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
# 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
# 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:
xcrun notarytool store-credentials "AC_PASSWORD" \
--apple-id "[email protected]" \
--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:
#!/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):
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
#!/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
# 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 buildsucceeds -
swift testpasses - 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, andstapler validate - Zip created for distribution
- (Optional) Sparkle appcast updated