DEV Community

Cover image for Splitting a Swift monorepo shouldn't require git-subtree tricks (so I built a free registry)
Timophei Lemeshchenko
Timophei Lemeshchenko

Posted on

Splitting a Swift monorepo shouldn't require git-subtree tricks (so I built a free registry)

Disclosure: I built Perchly, the tool this post ends up recommending. It started as a fix for my own problem, not a product — and this is the honest version of how it happened.

If your team has more than one Swift package that always version together, you've probably hit this: either you split them into separate git repos and juggle a release per package on every single change, or you keep one repo and lean on git submodules / git-subtree hacks just to make each package independently resolvable by SwiftPM. Neither is great, and I kept seeing people run into it — including a Stack Overflow question from someone trying to split a "newskit"/"the-sun" style theming package into independently versioned pieces without blowing up their repo structure.

The root cause is that swift package-registry publish (and Xcode's dependency resolution) publishes whatever package lives at a given path — so each package you want to version independently needs its own Package.swift, not a shared target inside one giant manifest. That part is actually fine once you know it. The part that's genuinely annoying is that Apple's own answer to "how do I host that privately for my team" is SE-0292, a real open protocol — but there was no simple, free way to actually run one if you're not Artifactory/JFrog-scale.

So I built a private SPM registry that implements SE-0292 for real, not a wrapper around git.

Why not just use a private GitHub repo as a dependency

That works fine for exactly one package. The moment you have several related packages that need independent versions in one repo, you're stuck choosing between:

  • Splitting into N repos, so every cross-cutting change becomes N pull requests and N releases
  • Keeping one repo and hacking around it with git-subtree splits or submodules, which nobody enjoys maintaining

A real registry sidesteps this entirely. Each package resolves and versions independently regardless of how your source is actually organized on disk.

What "real SE-0292" gets you

Because Perchly speaks the actual SE-0292 registry protocol instead of a proxy or a plugin, the standard SwiftPM CLI just works, unmodified:

# once per machine/CI runner
swift package-registry set https://registry.perchly.dev

# from inside a package directory
swift package-registry publish my-org.network-kit 1.0.0
Enter fullscreen mode Exit fullscreen mode

Depending on it elsewhere is just:

.package(id: "my-org.network-kit", from: "1.0.0")
Enter fullscreen mode Exit fullscreen mode

No custom tooling, no forked SwiftPM, no post-install scripts. It even works through Xcode's own "Add Package" dependency resolution once you've run the set command once — Xcode doesn't need to know Perchly exists.

For the actual monorepo case, splitting into independently publishable packages just means giving each one its own Package.swift:

my-monorepo/
├── Sources/
│   ├── newskit/
│   │   ├── Package.swift          ← name: "newskit"
│   │   └── Sources/newskit/...
│   └── the-sun/
│       ├── Package.swift          ← name: "the-sun"
│       └── Sources/the-sun/...
Enter fullscreen mode Exit fullscreen mode

They still live in one git repo. Nothing about this requires separate repos, submodules, or subtree splits — you just publish each path with its own identifier and version:

swift package-registry publish --package-path Sources/newskit my-org.newskit 1.0.0
swift package-registry publish --package-path Sources/the-sun my-org.the-sun 1.0.0
Enter fullscreen mode Exit fullscreen mode

I also open-sourced a small CLI, perchly-cli, that diffs the last publish against HEAD, figures out which package directories actually changed, and bumps + publishes only those — so you're not doing that by hand every release once you have more than two or three packages.

Other things it does

  • GitHub and GitLab OAuth to sign in — no separate account/password to manage
  • Versions are immutable once published (no silently overwriting 1.0.0 out from under someone who's already pinned to it)
  • Free plan: 3 members, 5 packages, 2GB storage, no credit card
  • Paid tiers exist for bigger teams, but the whole point of the free tier is that a small team shouldn't need to talk to sales to stop fighting git-subtree

What it deliberately doesn't do (yet)

No package signing yet (SwiftPM prints an "is not signed" warning but still resolves fine — that's an SwiftPM-level thing, not specific to Perchly). No self-hosted GitLab instance support, no per-package ACLs. It's intentionally a small, focused tool, not an attempt to out-feature Artifactory.

If you want to poke at it: perchly.dev. Feedback, especially "this doesn't work for my setup because X," is genuinely useful to me right now.

Top comments (0)