feat(catalyst): run and build Mac Catalyst apps with ns run catalyst - #6
Merged
Conversation
Adds macOS as a supported platform. A Mac Catalyst app is the iOS app rebuilt against the macOS SDK, so it keeps every iOS convention -- App_Resources/iOS, the iOS runtime package, each plugin's platforms/ios folder, the iOS Podfile, the iOS bundle -- and diverges only in the directory it prepares into, platforms/macos, and the SDK it builds against. That is the whole design: iOSProjectService reports iOS as the platform name so every iOS convention falls out for free, and projectRoot is the single place macOS differs. Two call sites rebuilt the platform directory from the platform name rather than reading projectRoot, which would have made a macOS build probe and delete platforms/ios; both now use projectRoot, which already resolves hostProjectPath the same way. The Mac is modelled as a device so build, deploy and LiveSync drive it through the existing pipeline. Everything it does is local: the .app is a directory on this machine, so the file system is a plain copy, install only records the built bundle, launch is `open -n`, and the log stream is `log stream` narrowed to the app and the runtime. Watch-mode prepare events are stamped with the platform the caller asked for rather than the platform data's name. run-controller pairs an event with a device by comparing the two, so for Catalyst -- iOS platform data, macOS device -- a file change recompiled but reached no device. The non-watch path already reported the requested platform; watchers and bundler processes are now keyed by it too, since stopWatchers and stopBundlerCompiler are called with it. Nothing changes for ios/android/visionos, where both strings are identical. Replaces the `--catalyst` build flag, which this supersedes. Verified on a real app: `ns run macos` runs pod install, generates metadata for arm64-apple-ios<version>-macabi, links and signs the .app, launches it, streams its logs, and syncs a file change into the running app. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`ns run macos` is being taken by a separate effort that builds a native macOS app against a macOS runtime. That is a different product from a Mac Catalyst build -- the iOS app rebuilt against the macOS SDK -- so this one takes the name that says what it actually is: `ns build catalyst`, `ns run catalyst`, preparing into platforms/catalyst. Only the platform identifier changes; the build, device and LiveSync behaviour is untouched. The renamed code is byte-identical to the upstream PR so the two merge without conflict. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ns run macosns run catalyst
farfromrefug
marked this pull request as ready for review
August 11, 2026 15:58
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Checklist
What is the current behavior?
There is no way to run a NativeScript app on macOS. #5 added a
--catalystflag tons build ios, which produces a Mac Catalyst binary but has no run, deploy or LiveSync story.What is the new behavior?
catalystbecomes a supported platform:ns build catalystandns run catalyst, the latter with full LiveSync.A Catalyst app keeps every iOS convention —
App_Resources/iOS, the iOS runtime package, each plugin'splatforms/iosfolder, the iOS Podfile, the iOS bundle — and diverges only in the directory it prepares into (platforms/catalyst) and the SDK it builds against.iOSProjectServicereports iOS as the platform name so every iOS convention falls out for free, andprojectRootbecomes the single place Catalyst differs. Two call sites rebuilt the platform directory from the platform name instead of readingprojectRoot; both now useprojectRoot, which already resolveshostProjectPathidentically. Left as-is, a Catalyst build would have probed and deletedplatforms/ios..appis a directory on this machine, so the file system is a plain copy, install only records the built bundle, launch isopen -n, and the log stream islog streamnarrowed to the app and the runtime.run-controllerpairs an event with a device by comparing the two, so for Catalyst — iOS platform data, Catalyst device — a file change recompiled but reached no device. The non-watch path already reported the requested platform; watchers and bundler processes are keyed by it too, sincestopWatchersandstopBundlerCompilerare called with it. Nothing changes for ios/android/visionos, where both strings are identical.--catalyst, which this supersedes: the Catalyst build path is now selected by the platform rather than a flag onbuild ios.Relationship to the upstream PR
The same work is proposed upstream as NativeScript#6125, and the Catalyst code here is byte-identical to it — verified line by line, including the four new files. That is deliberate: when NativeScript#6125 lands and this fork merges
upstream/main, git sees the same additions on both sides and resolves them automatically.A trial merge of the two confirms it. The only conflict my work adds is in
getInstalledRuntimePackage, where the upstream patch inserts the Catalyst→iOS mapping directly above a block this fork already carries; resolution is to keep both sides. (That block —let packageName: string[]— is dead code: the.find()callback below declares its own localpackageName, so nothing reads it. Worth removing separately.) The other conflicts in that trial —lib/config.tsandpackage-lock.json— appear identically when mergingupstream/maininto forkmainwith none of this work applied.Testing
Verified end to end with OSS Weather on Apple Silicon, on this branch after the rename:
ns build catalystruns pod install, generates metadata forarm64-apple-ios<version>-macabiand links and signs the.app;ns run catalystlaunches it, streams its logs, and syncs a file change into the running app — an addedconsole.logcame back through the CLI after the automatic restart.No unit tests are included: the change is platform plumbing plus a device implementation that shells out to
open,pkillandlog, none of which the existing suite has a harness for.vitest runreports 189 failures out of 1648 on this branch — and the identical 189 onmain. Verified by running the suite on both and diffing the failing test names: zero new, zero fixed. The suite is already red onmain; this PR does not change it.Note for reviewers
Two runtime-side fixes are needed before a clean checkout can build a working Catalyst app, both unmerged:
iOSSupportframework search paths, or the app launches intoReferenceError: UIDevice is not defined;build_npm_ios.shneedscp -Rso the Catalyst xcframeworks keep their versioned-bundle symlinks, or codesign rejects the app.iOS never hits either, because it uses shallow framework bundles and its own SDK paths.