Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,22 @@

All notable changes to the Native SDK (formerly zero-native) will be documented in this file.

## 0.10.0
## 0.10.1

<!-- release:start -->

### Bug Fixes

- **Complete macOS distribution signing**: Developer ID packages now require an explicit secure timestamp, can notarize through a `notarytool` Keychain profile, and staple and validate both the app and final signed DMG before updater archives are created.

### Contributors

- @ctate

<!-- release:end -->

## 0.10.0

### New Features

- **Native macOS app updates**: Apps can publish Ed25519-signed update feeds that verify downloads, atomically replace and relaunch the installed bundle, roll back safely on failure, and integrate with new manifest settings plus CLI key-generation, feed-signing, and updater-package commands (#398).
Expand All @@ -23,8 +35,6 @@ All notable changes to the Native SDK (formerly zero-native) will be documented
- @ctate
- @sepehr-safari

<!-- release:end -->

## 0.9.5

### New Features
Expand Down
26 changes: 22 additions & 4 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ pub fn build(b: *std.Build) void {
_ = b.option(bool, "js-bridge", "Enable optional JavaScript bridge stubs") orelse false;
const package_target = b.option(PackageTarget, "package-target", "Package target: macos, windows, linux, ios, android") orelse .macos;
const signing_mode = b.option(SigningMode, "signing", "Signing mode: none, adhoc, identity") orelse .none;
const signing_identity = b.option([]const u8, "identity", "Code signing identity for distribution packages");
const signing_entitlements = b.option([]const u8, "entitlements", "Entitlements plist for identity signing");
const notary_profile = b.option([]const u8, "notary-profile", "notarytool Keychain profile for notarization");
const package_version = packageVersion(b);
const optimize_name = @tagName(optimize);
// Resolve against THIS build's root: as a dependency of a user app the
Expand Down Expand Up @@ -2962,6 +2965,8 @@ pub fn build(b: *std.Build) void {
});
package_run.addFileArg(embed_lib.getEmittedBin());
package_run.addArgs(&.{ "--manifest", "app.zon", "--assets", "assets", "--optimize", optimize_name, "--signing", @tagName(signing_mode), "--web-engine", @tagName(web_engine), "--cef-dir", cef_dir });
if (signing_identity) |identity| package_run.addArgs(&.{ "--identity", identity });
if (signing_entitlements) |entitlements| package_run.addArgs(&.{ "--entitlements", entitlements });
if (cef_auto_install) package_run.addArg("--cef-auto-install");
package_run.step.dependOn(&embed_lib.step);
package_run.step.dependOn(&bundle_run.step);
Expand Down Expand Up @@ -2999,6 +3004,13 @@ pub fn build(b: *std.Build) void {
const package_cef_smoke_step = b.step("test-package-cef-layout", "Verify macOS Chromium package layout");
package_cef_smoke_step.dependOn(&package_cef_check.step);

// The repository's macOS distribution helpers need a real launchable
// executable inside the .app. Keep the notarization helper on the same
// emitted host CLI binary that the signed-package smoke executes below;
// the SDK embed artifact is a static `.a` library and is not a valid
// CFBundleExecutable even though codesign can seal a bundle around it.
const macos_distribution_executable = host_cli_exe.getEmittedBin();

// Signed-package seal pin: package an ad-hoc signed bundle and prove
// the signature survives packaging intact with codesign's own strict
// verifier. This is the regression gate for the ordering bug where a
Expand All @@ -3015,7 +3027,7 @@ pub fn build(b: *std.Build) void {
const package_signing_mode: []const u8 = if (b.graph.host.result.os.tag == .macos) "adhoc" else "none";
const package_signing_run = b.addRunArtifact(host_cli_exe);
package_signing_run.addArgs(&.{ "package", "--target", "macos", "--output", "zig-out/package/native-sdk-signing-verify.app", "--binary" });
package_signing_run.addFileArg(host_cli_exe.getEmittedBin());
package_signing_run.addFileArg(macos_distribution_executable);
package_signing_run.addArgs(&.{ "--manifest", "app.zon", "--assets", "assets", "--optimize", optimize_name, "--signing", package_signing_mode });
package_signing_run.has_side_effects = true;
const package_signing_check = b.addSystemCommand(&.{
Expand All @@ -3027,6 +3039,7 @@ pub fn build(b: *std.Build) void {
\\ exit 0
\\fi
\\codesign --verify --strict --deep "$app"
\\"$app/Contents/MacOS/native-sdk" version >/dev/null
\\grep -q "ad-hoc signed" "$app/Contents/Resources/signing-plan.txt"
\\echo "signed package verify ok"
,
Expand Down Expand Up @@ -3094,10 +3107,13 @@ pub fn build(b: *std.Build) void {
b.fmt("zig-out/package/native-sdk-{s}-macos-{s}.app", .{ package_version, optimize_name }),
"--binary",
});
notarize_run.addFileArg(embed_lib.getEmittedBin());
notarize_run.addArgs(&.{ "--manifest", "app.zon", "--assets", "assets", "--optimize", optimize_name, "--signing", "identity", "--web-engine", @tagName(web_engine), "--cef-dir", cef_dir });
notarize_run.addFileArg(macos_distribution_executable);
notarize_run.addArgs(&.{ "--manifest", "app.zon", "--assets", "assets", "--optimize", optimize_name, "--signing", "identity", "--web-engine", @tagName(web_engine), "--cef-dir", cef_dir, "--archive", "--notarize" });
if (signing_identity) |identity| notarize_run.addArgs(&.{ "--identity", identity });
if (signing_entitlements) |entitlements| notarize_run.addArgs(&.{ "--entitlements", entitlements });
if (notary_profile) |profile| notarize_run.addArgs(&.{ "--notary-profile", profile });
if (cef_auto_install) notarize_run.addArg("--cef-auto-install");
notarize_run.step.dependOn(&embed_lib.step);
notarize_run.step.dependOn(&host_cli_exe.step);
notarize_run.step.dependOn(&bundle_run.step);
const notarize_step = b.step("notarize", "Package, sign with identity, and notarize for macOS distribution");
notarize_step.dependOn(&notarize_run.step);
Expand All @@ -3118,6 +3134,8 @@ pub fn build(b: *std.Build) void {
});
dmg_run.addFileArg(embed_lib.getEmittedBin());
dmg_run.addArgs(&.{ "--manifest", "app.zon", "--assets", "assets", "--optimize", optimize_name, "--signing", @tagName(signing_mode), "--web-engine", @tagName(web_engine), "--cef-dir", cef_dir, "--archive" });
if (signing_identity) |identity| dmg_run.addArgs(&.{ "--identity", identity });
if (signing_entitlements) |entitlements| dmg_run.addArgs(&.{ "--entitlements", entitlements });
if (cef_auto_install) dmg_run.addArg("--cef-auto-install");
dmg_run.step.dependOn(&embed_lib.step);
dmg_run.step.dependOn(&bundle_run.step);
Expand Down
6 changes: 4 additions & 2 deletions docs/src/app/docs/cli/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,10 @@ Package the app for distribution. The manifest is picked up at `app.json` (falli
<dd>Code signing identity name.</dd>
<dt><code>--entitlements</code></dt>
<dd>Path to entitlements file.</dd>
<dt><code>--team-id</code></dt>
<dd>Apple Developer Team ID.</dd>
<dt><code>--notarize</code></dt>
<dd>Submit the final macOS artifact to Apple's notary service, then staple and validate the accepted ticket. Requires identity signing.</dd>
<dt><code>--notary-profile</code></dt>
<dd>Name of a <code>notarytool</code> Keychain profile created with <code>xcrun notarytool store-credentials</code>.</dd>
<dt><code>--archive</code></dt>
<dd>Create a distributable archive. On macOS this is a styled DMG with the app, an Applications alias, a generated or custom background, and the Finder layout declared by the app manifest.</dd>
</dl>
Expand Down
46 changes: 39 additions & 7 deletions docs/src/app/docs/packaging/signing/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Sign the bundle with a Developer ID:
native package --target macos --signing identity --identity "Developer ID Application: Your Name"
```

Identity signing enables the hardened runtime and explicitly requests Apple's secure timestamp. If the timestamp service cannot be reached, packaging fails instead of producing a distribution signature that notarization will reject.

Signing modes:

<table>
Expand Down Expand Up @@ -69,25 +71,55 @@ For distribution without any dialog, sign with a Developer ID (`--signing identi
<td>Path to entitlements file (e.g. <code>assets/native-sdk.entitlements</code>)</td>
</tr>
<tr>
<td><code>--team-id</code></td>
<td>Apple Developer Team ID</td>
<td><code>--notarize</code></td>
<td>Submit the final macOS artifact, then staple and validate the accepted ticket</td>
</tr>
<tr>
<td><code>--notary-profile</code></td>
<td>Name of a <code>notarytool</code> Keychain profile created with <code>store-credentials</code></td>
</tr>
</tbody>
</table>

## Notarization

The framework repository includes a `zig build notarize` helper for local release testing:
Store notarization credentials in Keychain once. A team App Store Connect API key is the normal CI choice:

```bash
xcrun notarytool store-credentials "my-app-release" \
--key /path/to/AuthKey_KEYID.p8 \
--key-id KEYID \
--issuer ISSUER_UUID
```

Apple ID credentials work too:

```bash
xcrun notarytool store-credentials "my-app-release" \
--apple-id "you@example.com" \
--team-id TEAMID \
--password APP_SPECIFIC_PASSWORD
```

Then package, timestamp-sign, notarize, staple, and validate in one command:

```bash
zig build notarize
native package --target macos \
--signing identity \
--identity "Developer ID Application: Your Name (TEAMID)" \
--archive \
--notarize \
--notary-profile "my-app-release"
```

Generated apps should use `native package --target macos --signing identity ... --archive` unless they add their own `notarize` build step. This helper does not invoke `xcrun notarytool` directly. After the signed DMG is created, submit and staple it manually (use the archive path printed by the package command):
With `--archive`, Native SDK first submits the signed app, staples and validates it, then builds and signs the DMG and submits, staples, and validates that final image. Without `--archive`, only the app submission runs. Any updater ZIP is created afterward from the stapled app.

The framework repository exposes the same path for local release testing:

```bash
xcrun notarytool submit zig-out/package/your-app-1.0.0-macos-ReleaseFast.dmg --apple-id "you@example.com" --team-id "TEAMID" --password "@keychain:AC_PASSWORD" --wait
xcrun stapler staple zig-out/package/your-app-1.0.0-macos-ReleaseFast.dmg
zig build notarize \
-Didentity="Developer ID Application: Your Name (TEAMID)" \
-Dnotary-profile="my-app-release"
```

## Chromium apps
Expand Down
2 changes: 1 addition & 1 deletion examples/gpu-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"private": true,
"description": "Editor surface for the TypeScript core; the native CLI builds without node_modules.",
"dependencies": {
"@native-sdk/core": "0.10.0"
"@native-sdk/core": "0.10.1"
}
}
2 changes: 1 addition & 1 deletion examples/kanban/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"private": true,
"description": "Editor surface for the TypeScript core; the native CLI builds without node_modules.",
"dependencies": {
"@native-sdk/core": "0.10.0"
"@native-sdk/core": "0.10.1"
}
}
2 changes: 1 addition & 1 deletion examples/menu-bar/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"private": true,
"description": "TypeScript + Native markup menu-bar lifecycle example.",
"dependencies": {
"@native-sdk/core": "0.10.0"
"@native-sdk/core": "0.10.1"
}
}
2 changes: 1 addition & 1 deletion examples/record-store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"private": true,
"description": "Editor surface for the TypeScript core; the native CLI builds without node_modules.",
"dependencies": {
"@native-sdk/core": "0.10.0"
"@native-sdk/core": "0.10.1"
}
}
2 changes: 1 addition & 1 deletion examples/relational-notes/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"private": true,
"description": "Editor and versioning surface only: stock TypeScript tooling resolves @native-sdk/core from here. The native CLI never reads it and builds with node_modules absent.",
"dependencies": {
"@native-sdk/core": "0.10.0"
"@native-sdk/core": "0.10.1"
}
}
2 changes: 1 addition & 1 deletion examples/soundboard-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"private": true,
"description": "Editor and versioning surface only: stock TypeScript tooling resolves @native-sdk/core from here. The native CLI never reads it and builds with node_modules absent.",
"dependencies": {
"@native-sdk/core": "0.10.0"
"@native-sdk/core": "0.10.1"
}
}
2 changes: 1 addition & 1 deletion examples/system-monitor-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"private": true,
"description": "Editor and versioning surface only: stock TypeScript tooling resolves @native-sdk/core from here. The native CLI never reads it and builds with node_modules absent.",
"dependencies": {
"@native-sdk/core": "0.10.0"
"@native-sdk/core": "0.10.1"
}
}
2 changes: 1 addition & 1 deletion examples/voice-memo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"private": true,
"description": "Editor and versioning surface only: stock TypeScript tooling resolves @native-sdk/core from here. The native CLI never reads it and builds with node_modules absent.",
"dependencies": {
"@native-sdk/core": "0.10.0"
"@native-sdk/core": "0.10.1"
}
}
4 changes: 2 additions & 2 deletions packages/core/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@native-sdk/core",
"version": "0.10.0",
"version": "0.10.1",
"description": "The TypeScript authoring tier: the app-core subset, its checker and contract frontend, the exact-pinned core compiler dependency, and the SDK module cores import",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/native-sdk/npm/darwin-arm64/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@native-sdk/cli-darwin-arm64",
"version": "0.10.0",
"version": "0.10.1",
"description": "The native CLI binary for macOS on Apple silicon (arm64)",
"os": [
"darwin"
Expand Down
2 changes: 1 addition & 1 deletion packages/native-sdk/npm/darwin-x64/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@native-sdk/cli-darwin-x64",
"version": "0.10.0",
"version": "0.10.1",
"description": "The native CLI binary for macOS on Intel (x64)",
"os": [
"darwin"
Expand Down
2 changes: 1 addition & 1 deletion packages/native-sdk/npm/linux-arm64-gnu/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@native-sdk/cli-linux-arm64-gnu",
"version": "0.10.0",
"version": "0.10.1",
"description": "The native CLI binary for Linux arm64 (glibc)",
"os": [
"linux"
Expand Down
2 changes: 1 addition & 1 deletion packages/native-sdk/npm/linux-arm64-musl/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@native-sdk/cli-linux-arm64-musl",
"version": "0.10.0",
"version": "0.10.1",
"description": "The native CLI binary for Linux arm64 (musl)",
"os": [
"linux"
Expand Down
2 changes: 1 addition & 1 deletion packages/native-sdk/npm/linux-x64-gnu/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@native-sdk/cli-linux-x64-gnu",
"version": "0.10.0",
"version": "0.10.1",
"description": "The native CLI binary for Linux x64 (glibc)",
"os": [
"linux"
Expand Down
2 changes: 1 addition & 1 deletion packages/native-sdk/npm/linux-x64-musl/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@native-sdk/cli-linux-x64-musl",
"version": "0.10.0",
"version": "0.10.1",
"description": "The native CLI binary for Linux x64 (musl)",
"os": [
"linux"
Expand Down
2 changes: 1 addition & 1 deletion packages/native-sdk/npm/win32-arm64/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@native-sdk/cli-win32-arm64",
"version": "0.10.0",
"version": "0.10.1",
"description": "The native CLI binary for Windows on ARM (arm64)",
"os": [
"win32"
Expand Down
2 changes: 1 addition & 1 deletion packages/native-sdk/npm/win32-x64/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@native-sdk/cli-win32-x64",
"version": "0.10.0",
"version": "0.10.1",
"description": "The native CLI binary for Windows x64",
"os": [
"win32"
Expand Down
18 changes: 9 additions & 9 deletions packages/native-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@native-sdk/cli",
"version": "0.10.0",
"version": "0.10.1",
"description": "The Native SDK: the complete toolkit for building native desktop applications — declarative markup, native rendering, WebView surfaces, and OS capabilities",
"type": "module",
"engines": {
Expand Down Expand Up @@ -39,14 +39,14 @@
"scriptc": "0.0.35"
},
"optionalDependencies": {
"@native-sdk/cli-darwin-arm64": "0.10.0",
"@native-sdk/cli-darwin-x64": "0.10.0",
"@native-sdk/cli-linux-arm64-gnu": "0.10.0",
"@native-sdk/cli-linux-arm64-musl": "0.10.0",
"@native-sdk/cli-linux-x64-gnu": "0.10.0",
"@native-sdk/cli-linux-x64-musl": "0.10.0",
"@native-sdk/cli-win32-arm64": "0.10.0",
"@native-sdk/cli-win32-x64": "0.10.0"
"@native-sdk/cli-darwin-arm64": "0.10.1",
"@native-sdk/cli-darwin-x64": "0.10.1",
"@native-sdk/cli-linux-arm64-gnu": "0.10.1",
"@native-sdk/cli-linux-arm64-musl": "0.10.1",
"@native-sdk/cli-linux-x64-gnu": "0.10.1",
"@native-sdk/cli-linux-x64-musl": "0.10.1",
"@native-sdk/cli-win32-arm64": "0.10.1",
"@native-sdk/cli-win32-x64": "0.10.1"
},
"repository": {
"type": "git",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,10 @@ Signing modes:
native package --target macos --signing none
native package --target macos --signing adhoc
native package --target macos --signing identity --identity "Developer ID Application: Your Name"
native package --target macos --signing identity --identity "Developer ID Application: Your Name" --archive --notarize --notary-profile "my-app-release"
```

For Chromium apps, verify the CEF framework and resources are included and signed before notarization.
Identity signing enables the hardened runtime and requires a secure timestamp. `--notarize` uses a `notarytool` Keychain profile, submits the final DMG when `--archive` is present, and staples and validates the accepted tickets. For Chromium apps, verify the CEF framework and resources are included and signed before notarization.

## Linux and Windows packages

Expand Down
Loading
Loading