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
14 changes: 14 additions & 0 deletions .github/linters/.gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[extend]
useDefault = true

[allowlist]
paths = [
'''.*__tests__/.*\.ts$''',
]

# Allow string comparisons checking for PEM headers (false positives - not actual secrets)
# These match findings where the line contains .includes() checks
regexTarget = "match"
regexes = [
'''\.includes\(''',
]
25 changes: 15 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,37 +68,42 @@ jobs:
run: |
name="Apple Development: Created via API (DEADBEEFACID)"
result=$(security find-certificate -c "$name" -a)
if echo $result | grep -q "$name"; then
if echo "$result" | grep -q "$name"; then
echo "Certificate found in keychain"
echo "::debug::Certificate found in keychain"
else
echo "Unable to find certificate in keychain"
echo "::error title=Certificate not found::Unable to find certificate in keychain"
echo $result
echo "$result"
exit 1
fi

- name: Read contents of authkey into GitHub env
run: npm run ci-load-app-key

- name: Test App Store Connect API Key Import
id: test-api-key
uses: ./
with:
asset-type: app-store-connect-api-key
secret-value: ${{ env.APP_STORE_CONNECT_API_KEY }}

- name: Validate certificate installed
- name: Validate API key installed
run: |
if ! [[ -f $RUNNER_TEMP/.app-store-connect-api-key.p8 ]]; then
echo "App Store Connect API Key not found"
KEY_PATH="${{ steps.test-api-key.outputs.app-store-connect-api-key-key-path }}"
if ! [[ -f "$KEY_PATH" ]]; then
echo "App Store Connect API Key not found at $KEY_PATH"
echo "::error title=App Store Connect API Key not found::App Store Connect API Key not found"
ls -la $RUNNER_TEMP
ls -la ~/.appstoreconnect/private_keys/ 2>/dev/null || echo "Directory does not exist"
exit 1
fi
echo "App Store Connect API Key found at $KEY_PATH"

if ! [[ -f $RUNNER_TEMP/.app-store-connect-api-key.json ]]; then
echo "App Store Connect API Key object not found"
echo "::error title=App Store Connect API Key object not found::App Store Connect API Key object not found"
ls -la $RUNNER_TEMP
KEY_INFO="$HOME/.appstoreconnect/private_keys/keyinfo.json"
if ! [[ -f "$KEY_INFO" ]]; then
echo "App Store Connect API Key info not found at $KEY_INFO"
echo "::error title=App Store Connect API Key info not found::App Store Connect API Key info not found"
ls -la ~/.appstoreconnect/private_keys/ 2>/dev/null || echo "Directory does not exist"
exit 1
fi
echo "App Store Connect API Key info found at $KEY_INFO"
1 change: 1 addition & 0 deletions .github/workflows/linter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,5 @@ jobs:
TYPESCRIPT_DEFAULT_STYLE: prettier
VALIDATE_ALL_CODEBASE: true
VALIDATE_JAVASCRIPT_STANDARD: false
VALIDATE_TYPESCRIPT_STANDARD: false
VALIDATE_JSCPD: false
172 changes: 137 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,47 @@
[![CodeQL](https://github.com/actions/typescript-action/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/actions/typescript-action/actions/workflows/codeql-analysis.yml)
[![Coverage](./badges/coverage.svg)](./badges/coverage.svg)

This action sets up a macOS runner for code signing. It's in the early stages of
development and is not yet ready for production use.
This action sets up a macOS runner for code signing. It's in the early stages
of development and is not yet ready for production use.

| Asset Type | Support |
| ------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| Developer Certificate | ✅ |
| [App Store Connect API Key](https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api) | ✅ |
| Provisioning Profile | ❌ |
| Asset Type | Support |
| ---------- | ------- |
| Developer Certificate | ✅ |
| [App Store Connect API Key][api-key-docs] | ✅ |
| Provisioning Profile | ❌ |

# Sample Workflow
[api-key-docs]: https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api

Apple introduced
[managed signing at WWDC21](https://developer.apple.com/videos/play/wwdc2021/10204/).
By setting up the runner with an App Store Connect API Key, you can use managed
signing to sign your app. No provisioning profile is required.
For an overview of the automated and manual code signing process, see
[Apple Code Signing Overview](apple_codesigning_overview.md).

> [!NOTE]
> Apple Developer Enterprise accounts do not support App Store Connect API keys.
## Sample Workflow

**Note:** Apple Developer Enterprise accounts do not support App Store Connect
API keys.

**Important:** "Automatically manage signing" in Xcode refers to how Xcode
behaves when signed into a developer account through the GUI. This does not
apply to `xcodebuild`, which requires explicit API authorization via the
`-allowProvisioningUpdates` flag and authentication parameters, regardless
of the Xcode project's signing configuration.

Both the API key and development certificate are required for automated
signing. The App Store Connect API key enables `xcodebuild` to communicate
with Apple's servers to download provisioning profiles remotely. However, if
a development certificate is not installed in the keychain before `xcodebuild`
runs, it will automatically create a new certificate in the Developer Portal.
Installing the certificate first ensures `xcodebuild` uses your existing
certificate for signing while still leveraging the API key for remote
provisioning profile management.

The action persists the App Store Connect API key to a file on the runner
because `xcodebuild` requires the `-authenticationKeyPath` parameter to point
to a file path—it does not accept API credentials via environment variables.

## Streamlined Setup (Recommended)

For the common case where you need both API key and certificate:

```yml
name: build with automatic signing
Expand All @@ -39,32 +62,111 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: nodeselector/setup-apple-codesign@v0.0.2
with:
# Certificate (PEM or P12 - P12 must be base64 encoded)
secret-value: ${{ secrets.CERTIFICATE_PEM_OR_P12 }}
# Optional: password for encrypted certificates
certificate-password: ${{ secrets.CERT_PASSWORD }}
# App Store Connect API credentials
app-store-connect-api-key-key-id: ${{ secrets.ASC_KEY_ID }}
app-store-connect-api-key-issuer-id: ${{ secrets.ASC_ISSUER_ID }}
app-store-connect-api-key-base64-private-key: ${{ secrets.ASC_KEY }}
# This example shows xcodebuild invocation for illustration.
# You may use a different action/API to invoke xcodebuild, but ensure
# the necessary flags are set to support automated code signing.
- name: Build with xcodebuild
run: |
xcodebuild -project helloworld.xcodeproj \
CODE_SIGN_STYLE=Automatic \
DEVELOPMENT_TEAM="2KP9M7XQZN" \
-scheme helloworld \
-sdk iphoneos \
-configuration Debug \
-allowProvisioningUpdates \
-authenticationKeyID ${{ secrets.ASC_KEY_ID }} \
-authenticationKeyPath '/path/to/AuthKey.p8' \
-authenticationKeyIssuerID ${{ secrets.ASC_ISSUER_ID }} \
-derivedDataPath build \
build
# Additional steps: archive, export, etc.
```

## Advanced: Separate Setup Steps

For more control, you can set up the API key, certificate, and provisioning
profile separately:

```yml
name: build with manual signing (separate steps)

on:
push:
pull_request:
branches:
- main

jobs:
test:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
# Set up App Store Connect API key
- uses: nodeselector/setup-apple-codesign@v0.0.2
with:
asset-type: "app-store-connect-api-key"
secret-value: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY }}
app-store-connect-api-key-key-id: ${{ secrets.ASC_KEY_ID }}
app-store-connect-api-key-issuer-id: ${{ secrets.ASC_ISSUER_ID }}
app-store-connect-api-key-base64-private-key: ${{ secrets.ASC_KEY }}
# Set up development certificate
- uses: nodeselector/setup-apple-codesign@v0.0.2
with:
asset-type: "certificate"
secret-value: ${{ secrets.CODE_SIGNING_CERTIFICATE_DEVELOPMENT_PEM }}
- uses: nodeselector/xcodebuild@v0.0.2
with:
action: 'archive'
scheme: "helloworld"
project: "helloworld.xcodeproj"
archive-path: "build/helloworld.xcarchive"
destination: "generic/platform=iOS"
allow-provisioning-updates: true
- uses: nodeselector/xcodebuild@v0.0.2
id: export
with:
action: 'export'
archive-path: "build/helloworld.xcarchive"
allow-provisioning-updates: true
export-method: "ad-hoc"
- uses: nodeselector/xcodebuild@v0.0.2
secret-value: ${{ secrets.CERTIFICATE_PEM_OR_P12 }}
certificate-password: ${{ secrets.CERT_PASSWORD }}
# Optional: Install provisioning profile to host machine
# Required for manual signing without -allowProvisioningUpdates
- uses: nodeselector/setup-apple-codesign@v0.0.2
with:
action: 'upload'
product-name: "helloworld"
export-path: ${{ steps.export.outputs.export-path }}
asset-type: "provisioning-profile"
secret-value: ${{ secrets.PROVISIONING_PROFILE_BASE64 }}
- name: Build with xcodebuild
run: |
# Option 1: Use locally installed provisioning profile
# Profile must be installed via the provisioning-profile asset-type
# The -allowProvisioningUpdates flag is unnecessary (and is a no-op)
xcodebuild -project helloworld.xcodeproj \
CODE_SIGN_STYLE=Manual \
CODE_SIGN_IDENTITY="Apple Development" \
PROVISIONING_PROFILE_SPECIFIER="my-ios-profile" \
-scheme helloworld \
-sdk iphoneos \
-configuration Debug \
-derivedDataPath build \
build

# Option 2: Fetch provisioning profile remotely via App Store Connect
# Uses -allowProvisioningUpdates with API authentication flags
# xcodebuild -project helloworld.xcodeproj \
# CODE_SIGN_STYLE=Manual \
# PROVISIONING_PROFILE_SPECIFIER="my-ios-profile" \
# DEVELOPMENT_TEAM="2KP9M7XQZN" \
# -scheme helloworld \
# -sdk iphoneos \
# -configuration Debug \
# -allowProvisioningUpdates \
# -authenticationKeyID ${{ secrets.ASC_KEY_ID }} \
# -authenticationKeyPath '/path/to/AuthKey.p8' \
# -authenticationKeyIssuerID ${{ secrets.ASC_ISSUER_ID }} \
# -derivedDataPath build \
# build
```

For manual signing or to specify a provisioning profile, several required and
optional flags can be passed to `xcodebuild`. See the
[Apple Code Signing Overview](apple_codesigning_overview.md) for detailed
configuration options including:

- Using `CODE_SIGN_STYLE=Manual` with `PROVISIONING_PROFILE_SPECIFIER`
- Installing provisioning profiles locally vs. fetching remotely
- Certificate and profile management considerations
Loading
Loading