npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@daemux/swift-app-ci

v0.0.28

Published

Zero-config TestFlight CI for native Swift/iOS apps — just drop a p8 in creds/ and push.

Downloads

3,448

Readme

@daemux/swift-app-ci

Zero-config TestFlight CI for native Swift / SwiftUI iOS apps. Drop a p8 in creds/, push to main, and your app ships to TestFlight on GitHub Actions. No fastlane to configure, no Apple Developer certificates to juggle, no provisioning profiles to manage by hand.

::warning:: SECURITY ADVISORY — rotate your ASC API key if you ran 0.0.1–0.0.11

Versions 0.0.1 through 0.0.11 of this package exported the raw contents of your App Store Connect .p8 private key as a GitHub Actions environment variable (ASC_KEY_P8). GitHub Actions prints env: blocks when step groups expand, so the full private key (between -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY-----) may appear in plaintext in your workflow logs.

Even in private repos, log retention, repo admins, and fork permissions can expose these logs. If you ran any build on 0.0.1–0.0.11, treat the key as compromised and rotate it now:

  1. Go to App Store Connect → Users and Access → Integrations → Keys.
  2. Revoke the old key.
  3. Generate a new key with the App Manager role.
  4. Replace the .p8 under creds/ (the filename encodes the new KEY_ID and ISSUER_UUID: AuthKey_<KEY_ID>_Issuer_<UUID>.p8).
  5. Re-run npx --yes @daemux/swift-app-ci to pick up the fixed action (0.0.12+), which uses the on-disk key path only and registers the key body with ::add-mask:: as a belt-and-braces redaction.

No action is required if you never ran a build on 0.0.1–0.0.11.

Install

From the root of your iOS project repo:

npx --yes @daemux/swift-app-ci

Writes two things into your repo:

  • .github/actions/swift-app/ — the vendored composite action (action.yml + scripts)
  • .github/workflows/deploy.yml — an 18-line workflow that invokes it on push to main

Re-run the same command anytime to pull the latest version.

Setup

  1. Create an App Store Connect API key with the App Manager role (App Store Connect → Users and Access → Keys → generate).
  2. Download the .p8 and save it into your repo at:
    creds/AuthKey_<KEY_ID>_Issuer_<ISSUER_UUID>.p8
    Example: creds/AuthKey_ABC123DEFG_Issuer_69a6de70-xxxx-47e3-e053-5b8c7c11a4d1.p8 The filename encodes both the KEY_ID and the ISSUER_ID — the composite action parses them from the filename.
  3. The repo must be private. The p8 is a long-lived credential; never commit it to a public repo.
  4. (First time only, per app) Create the app record in App Store Connect. See First-time app setup below.
  5. Commit and push to main. CI triggers automatically.

How it works

On each push, the composite action runs on macos-15 and:

  1. Auto-detects your .xcodeproj / .xcworkspace, scheme, bundle ID, and team_id (from the ASC API key). No ci.config.yaml required — override via action inputs only if auto-detection fails.
  2. Reads the ASC key from creds/AuthKey_*.p8 and uses it to authenticate to App Store Connect via JWT.
  3. Decides the marketing version: either reuses the current PREPARE_FOR_SUBMISSION version on App Store Connect, or creates a new version if the highest declared version is already live.
  4. Computes the next build number by querying ASC for the latest uploaded build and incrementing.
  5. Provisions signing at runtime: generates a throwaway Apple Distribution cert + a per-target App Store provisioning profile named CI-<bundle_id>. Patches the .pbxproj to use Manual signing against those profiles.
  6. Archives with xcodebuild archive, exports the IPA, and uploads via xcrun altool.
  7. Sets "What's New" on every declared localization (reads fastlane/metadata/ios/<locale>/release_notes.txt if present, or from the app-store-whats-new input).
  8. Auto-fills empty App Store metadata (name, subtitle, keywords, description, promotional text, what's new) via GitHub Models AI, on every locale that has gaps. See AI metadata auto-fill below.

The only secret required is the p8. Everything else is derived.

AI metadata auto-fill

On every run, after the TestFlight upload succeeds, the action:

  1. Queries App Store Connect for every appInfoLocalization and appStoreVersionLocalization on the editable version.
  2. Computes the set of empty fields per locale (URL fields are always skipped — you must set those manually in ASC).
  3. Scans your repo for context (README, Info.plist, dependency files, top Swift files) and feeds it to openai/gpt-4o via GitHub Models with a strict JSON schema.
  4. PATCHes only the fields that were empty — never overwrites existing content.

Fully idempotent: a second run with no empty fields skips the AI step entirely (zero requests, zero PATCHes).

Requirement: permissions: models: read

actions/ai-inference needs the models: read permission. The template workflow written by npx @daemux/swift-app-ci already includes it:

permissions:
  contents: read
  models: read

Existing consumers must add this block to their deploy.yml at the workflow or job level. If it's missing, the AI step fails open with a ::warning:: and the rest of the workflow continues unaffected.

Rate limits

GitHub Models free tier allows 50 gpt-4o requests per day (10 per minute). One workflow run = one request. If you run many apps from the same GitHub account, or trigger several builds per day, switch to the cheaper mini model:

- uses: ./.github/actions/swift-app
  with:
    ai-metadata-model: openai/gpt-4o-mini

gpt-4o-mini has a much higher free-tier quota.

Disabling AI metadata

Pass ai-metadata: 'false' to skip the AI steps entirely:

- uses: ./.github/actions/swift-app
  with:
    ai-metadata: 'false'

Update

npx --yes @daemux/swift-app-ci

Overwrites .github/actions/swift-app/ and .github/workflows/deploy.yml with the latest versions. Because the action is vendored locally, your workflow never auto-updates against daemux-plugins's @main — you control updates via this npm package.

Override config (rare)

Most projects never need this. If auto-detection fails or you have multiple schemes, pass inputs in .github/workflows/deploy.yml:

- uses: ./.github/actions/swift-app
  with:
    scheme: MyAppRelease
    bundle-id: com.example.myapp
    run-tests: 'false'
    uses-non-exempt-encryption: 'false'

All inputs are declared in .github/actions/swift-app/action.yml. The common ones:

| Input | Purpose | |-------|---------| | project / workspace | Path to .xcodeproj or .xcworkspace | | scheme | Xcode scheme to archive | | configuration | Release (default) or custom | | bundle-id | Override the auto-detected bundle identifier | | team-id | Override the auto-detected team ID | | app-store-apple-id | Numeric ASC app ID (override auto-lookup) | | run-tests | false to skip the simulator test stage | | uses-non-exempt-encryption | Value for ITSAppUsesNonExemptEncryption | | archive | false to build-only (PR runs without secrets) | | upload | false to archive but not upload to TestFlight | | app-store-whats-new | Inline "What's New" text (overrides files) | | ai-metadata | false to disable AI auto-fill of empty ASC metadata | | ai-metadata-model | GitHub Models model id (default openai/gpt-4o) |

First-time app setup

The app record must exist in App Store Connect before the first CI upload. This is a one-time step per app that requires Apple ID + 2FA, so it can't run in CI. Use fastlane locally:

# From your iOS repo (after installing fastlane: gem install fastlane)
APPLE_ID="[email protected]" \
BUNDLE_ID="com.example.myapp" \
APP_NAME="My App" \
  fastlane produce

Once the app exists, all subsequent builds and uploads are fully automated via the ASC API key.

Troubleshooting

"No app found for bundle ID" — the app record doesn't exist yet. Run the first-time setup above.

"MARKETING_VERSION is not set" — the action requires MARKETING_VERSION to be declared in your target's build settings. Open the target in Xcode → Build Settings → Versioning → set MARKETING_VERSION (and CURRENT_PROJECT_VERSION) to $(MARKETING_VERSION) / $(CURRENT_PROJECT_VERSION) respectively.

"You must accept the latest Program License Agreement" — go to developer.apple.com and App Store Connect as the account holder, accept any pending agreements, retry.

Upload fails with provisioning errors — delete any stale profiles named CI-<bundle_id> on developer.apple.com and re-run; the action will regenerate.

Auto-bumping MARKETING_VERSION

When the ASC combined floor (max of pending review, preReleaseVersions, or builds-via-preReleaseVersion) exceeds your project's MARKETING_VERSION, the action auto-bumps and commits the new value as part of the same bot commit that handles cert refresh / autoupdate.

Default policy is rollover — patch with carry: at .9 it rolls into the next minor (1.0.91.1.0), and at minor=9 it cascades into the next major (1.9.92.0.0). Major has no upper limit (9.9.910.0.0). This produces the more natural human progression most projects want — patch numbers never silently grow past 9.

Four policies are supported:

| Policy | Example bump | When to use | |--------|--------------|-------------| | rollover (default) | 1.0.51.0.6; 1.0.91.1.0 | Natural progression, carry at .9. | | patch | 1.0.51.0.6; 1.0.91.0.10 | Legacy unbounded patch — pinned for backward compat. | | minor | 1.0.51.1.0; 1.0.91.1.0 | Projects that ship every release as a minor. | | none | (fails the build) | Explicit semver control via human bump. |

Full rollover behaviour: 1.0.91.1.0 (patch overflow), 1.9.92.0.0 (minor cascade), 9.9.910.0.0 (major no upper limit).

Backward compat: existing consumers on 0.0.27 that explicitly pin marketing-version-auto-bump: 'patch' keep their current unbounded behavior — the 'patch' policy is unchanged. The default change from 'patch''rollover' only affects new installs and consumers that do not override the input.

Opt out via the action input:

- uses: ./.github/actions/swift-app
  with:
    marketing-version-auto-bump: 'none'

In 'none' mode, the floor check fails the build and you must bump MARKETING_VERSION manually before retrying.

Side effect: the bot commit subject reflects what was changed, e.g. ci: refresh signing identity + bump MARKETING_VERSION [skip ci].

Source-of-truth resolution. The auto-bump writes the new value into the file your project actually reads from, in this order:

  1. xcodegen project.yml (preferred when present): regex-rewrite of the MARKETING_VERSION: key, preserving formatting. The generated *.xcodeproj is regenerated on every build, so editing it directly would lose the bump.
  2. *.xcconfig sitting alongside the project: handles non-xcodegen projects that hoist MARKETING_VERSION into xcconfig.
  3. *.xcodeproj/project.pbxproj: only when no xcodegen spec is present.
  4. Info.plist CFBundleShortVersionString: last-ditch fallback.

If your project uses xcodegen but MARKETING_VERSION lives somewhere not in project.yml or .xcconfig, the action emits a ::warning:: and falls back to fail-on-floor (refusing to silently edit the generated pbxproj). Either move MARKETING_VERSION under settings.base in project.yml, or pin marketing-version-auto-bump: 'none' and bump manually.

Auto-updates

The vendored action ships with a per-run autoupdate check. On every push to your default branch, the action queries npm for the latest @daemux/swift-app-ci, compares against the local marker at .github/actions/swift-app/.daemux-version, and if newer, re-vendors the package via npx --yes and commits the refreshed action files back (under .github/actions/swift-app/ only). .github/workflows/deploy.yml is NEVER auto-committed — see "deploy.yml is not auto-updated" below.

| Aspect | Behaviour | |--------|-----------| | Trigger | Push to default branch only (PR / branch runs do nothing) | | Lag | One run — the next push after a new release picks up the update | | Suppression | [skip ci] in the commit subject + paths-ignore for .github/actions/swift-app/** | | Combined commit | Cert refresh + autoupdate share a single commit when both fire in the same run | | Failure mode | Non-fatal: a failed npm view or npx emits ::warning:: and the build continues |

The shipped deploy.yml template already includes the required paths-ignore entry for .github/actions/swift-app/**. If you have an older deploy.yml checked in, add that line to prevent the autoupdate commit-back from re-triggering the workflow.

deploy.yml is not auto-updated

deploy.yml is NOT auto-updated. GitHub's GITHUB_TOKEN cannot push changes to workflow files (.github/workflows/*.yml) regardless of contents: write — this is a built-in safeguard against CI self-modification. When a new version of @daemux/swift-app-ci requires deploy.yml schema changes (e.g., new permissions, new paths-ignore entries), the action's release notes will call this out and you must run npx --yes @daemux/swift-app-ci manually once to sync your deploy.yml. Existing deploy.yml stays untouched on every auto-update cycle until you do.

Opt out

Pin the vendored copy by passing auto-update: 'false' to the action:

- uses: ./.github/actions/swift-app
  with:
    auto-update: 'false'

First-run bootstrap

The marker is written by npx @daemux/swift-app-ci itself. A repo without a marker (e.g. an old hand-vendored copy) will be treated as out-of-date on its first run, after which updates land incrementally. Run npx --yes @daemux/swift-app-ci once locally if you want to skip even that first auto-bootstrap.

License

MIT