Packaging
nio build produces a self-contained native binary: the runtime is compiled
in, and the only dynamic dependencies are system frameworks every machine of
the platform already has. nio package wraps that binary in what the
platform calls an installable application. On macOS that is a signed .app
bundle and, on request, a drag-to-Applications disk image; Linux and Windows
packaging are planned.
nio package app.nio # app.app, beside the working directory
nio package --dmg app.nio -o dist # dist/app.app and dist/app.dmgThe manifest
Packaging is described by a small JSON file. It is found automatically as
<file>.pkg.json beside the source (app.nio → app.pkg.json), or named
explicitly with --manifest. Every key is optional — with no manifest at
all, the app is named after the file.
{
"name": "Demo App",
"identifier": "com.example.demo",
"version": "1.2.3",
"icon": "icon-1024.png",
"resources": ["ui.html", "theme.css", "components"],
"dmg": true,
"macos": {
"sign": "auto",
"notarize": "nio-notary",
"entitlements": "app.entitlements",
"provisioningProfile": "app.provisionprofile",
"minimumOS": "11.0"
}
}name— the app’s display name; also the bundle’s and image’s file name. Defaults to the source file’s stem.identifier— the bundle identifier. Defaults tocom.nio.<name>.version—CFBundleShortVersionString. Defaults to0.1.0.icon— one 1024×1024 PNG, relative to the manifest.sipsandiconutil(both ship with macOS) turn it into the full icon set. The source is converted to sRGB first, so a wide-gamut export straight out of a design tool costs nothing: its embedded color profile would otherwise be copied into every size of the set.resources— files and directories copied into the bundle’sContents/Resources, relative to the manifest. Trees are copied whole, modes and symlinks preserved.dmg— also produce<name>.dmg: the app beside an/Applicationssymlink, so opening the image shows the classic drag-to-install window, with the app’s icon as the volume’s. The image is lzma-compressed (mounts on macOS 10.15+, within the 11.0minimumOSfloor).--dmgon the command line does the same for one run.macos.sign— a codesign identity: the full certificate name,"auto"to use the machine’s only Developer ID Application identity, or"-"to force the ad-hoc signature even where a real identity is configured.macos.notarize— a notarytool keychain profile name; see below.macos.entitlements— an entitlements plist for apps that need one.macos.provisioningProfile— a.provisionprofilefrom the Apple Developer portal, copied into the bundle asContents/embedded.provisionprofilebefore the signature seals it. Only apps with a restricted entitlement need one: macOS honors those keys only where a profile authorizes them for that exact bundle identifier and team. The profile pairs with the identity inmacos.sign, so naming one with the ad-hoc signature is refused.macos.minimumOS—LSMinimumSystemVersion. Defaults to11.0.macos.plistExtra— raw plist XML spliced into the generated Info.plist’s top-level dict, for keys the tool has no business enumerating. A browser, for instance, wants<key>NSAppTransportSecurity</key><dict><key>NSAllowsArbitraryLoadsInWebContent</key><true/></dict>so its pages may loadhttp://sites; a menu-bar app wants<key>LSUIElement</key><true/>.
Unknown keys are ignored, so one manifest can carry sections for platforms this version does not read yet.
Signing
Every bundle is signed — on Apple Silicon an unsigned binary will not launch at all. With no identity configured the signature is ad-hoc, which is fine for an app used on the machine that built it. A downloaded copy of an ad-hoc-signed app is quarantined, and macOS makes the user right-click → Open it the first time; making that dialog disappear requires a paid Apple Developer ID and notarization.
With one, run the one-time setup:
nio package --setupIt lists the Developer ID Application identities in your Keychain and picks
one (a menu when there are several), then offers to set up notarization:
notarytool store-credentials prompts for your Apple ID, team, and an
app-specific password from appleid.apple.com, stores them in the Keychain
under a profile name, and the wizard validates the profile before saving.
What is saved — under your user, never in the project — is just the two
names: which identity, which profile.
After that, a plain nio package --dmg app.nio signs with the hardened
runtime, submits the DMG to Apple (--wait, typically a minute or two), and
staples the ticket to it, so Gatekeeper accepts the download even offline.
A manifest’s explicit macos.sign / macos.notarize override the per-user
config, which is what CI or a team-pinned project wants.
A resource that is itself code — a dylib a library ships (see Native interop), a helper
binary — is signed too, with the same identity, before the bundle’s own
signature seals it in. Notarization requires every Mach-O in the archive
to carry a Developer ID signature with a secure timestamp, and the outer
signature treats Resources/ as data, so whatever signature the file’s
build left it (typically the linker’s ad-hoc one) would otherwise go in
unchanged and fail the submission. Nothing to configure: Mach-O files are
recognized by their contents, everything else is copied untouched.
What lands where
Demo App.app/
Contents/
Info.plist generated from the manifest
MacOS/Demo App the binary, compiled straight into place
Resources/ the manifest's resources, plus app.icnsThe binary is built exactly as nio build builds it — same compiler, same
runtime, same clang -O2 -flto — and runs on any Mac of the build machine’s
architecture: the target CPU baseline is the toolchain’s default for the
platform (apple-m1 on Apple Silicon), not the build host’s own chip.
Universal binaries are not supported yet; an Intel DMG is built on (or for)
an Intel host.