"Finder shows you an icon. The system sees a directory with a contract inside."
Open One Up
Every .app is a directory the Finder draws as a single icon. On the Mac the layout is nested under Contents/: an Info.plist describing the app, a MacOS/ folder holding the executable, Resources/ for icons and assets, and _CodeSignature/ sealing all of it. Helpers and extensions nest further inside (Frameworks/, PlugIns/, Library/LaunchAgents/). An iOS app bundle is flat: the executable, Info.plist and Assets.car sit at the top, with a share extension under PlugIns/, a watch app under Watch/, and an embedded.mobileprovision on a development install. An archive exported for TestFlight carries a distribution profile as well.
You can open any bundle with ls, and you should — once, on a system app, before you write your own build script. Nothing in this quest will make more sense than seeing that the thing you ship is a folder with rules.
Info.plist Is the Contract
CFBundleIdentifier— the app's name to the system, in reverse-DNS form. TCC permissions, Keychain items, LaunchServices registrations, App Groups and the App Store Connect record all hang off it.CFBundleExecutable— which file inMacOS/actually runs. It does not have to match the bundle's display name, and scripts thatpkillby the wrong one match nothing.CFBundleShortVersionString— the marketing version people see (1.2.0).CFBundleVersion— the build number. TestFlight refuses to accept the same one twice.LSMinimumSystemVersion(macOS) /MinimumOSVersion(iOS) — the deployment target, written into the artifact.LSUIElement— true for a menu-bar utility with no Dock icon.
The Signature Seals the Folder
_CodeSignature/CodeResources lists a hash for every file in the bundle. Edit the Info.plist after signing, drop in an extra image, or let Finder attach extended attributes, and codesign --verify reports that a sealed resource is missing or invalid. That is why every build script in this family finishes the bundle completely — version stamped, icon copied, attributes cleared — and then signs, and then verifies.
A Bare Executable Is Not a Bundle
swift build produces a plain binary under .build/. It runs, and Bundle.main even answers — but it answers with the directory containing the binary. The family measured this after a code comment claimed Bundle.main.resourceURL is nil for a SwiftPM executable: it is not nil, it points at .build/arm64-apple-macosx/debug. So a non-nil resource URL proves nothing about whether you are packaged. Check for the file you actually need.