2026-05-02 00:18:46 +00:00
|
|
|
package notarize
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
|
2026-05-02 01:47:13 +00:00
|
|
|
"lmika.dev/actions/wails-release/internal/runner"
|
2026-05-02 00:18:46 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// APIKey is the App Store Connect API key credential set.
|
|
|
|
|
type APIKey struct {
|
|
|
|
|
KeyPath string
|
|
|
|
|
KeyID string
|
|
|
|
|
IssuerID string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AppleID is the legacy Apple ID + app-specific password credential set.
|
|
|
|
|
type AppleID struct {
|
|
|
|
|
Username string
|
|
|
|
|
Password string
|
|
|
|
|
TeamID string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Opts configures a notarization submission. Exactly one of APIKey or
|
|
|
|
|
// AppleID must be set.
|
|
|
|
|
type Opts struct {
|
|
|
|
|
ZipPath string
|
|
|
|
|
APIKey *APIKey
|
|
|
|
|
AppleID *AppleID
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Submit ships ZipPath to Apple's notary service and waits for the result.
|
|
|
|
|
// Rejected submissions cause the per-submission log to be fetched and
|
|
|
|
|
// embedded in the returned error.
|
|
|
|
|
func Submit(ctx context.Context, r runner.Runner, opts Opts) error {
|
|
|
|
|
if (opts.APIKey == nil) == (opts.AppleID == nil) {
|
|
|
|
|
return fmt.Errorf("notarize: exactly one of APIKey or AppleID must be set")
|
|
|
|
|
}
|
|
|
|
|
credArgs := credentialArgs(opts)
|
|
|
|
|
|
|
|
|
|
args := append([]string{"notarytool", "submit", opts.ZipPath}, credArgs...)
|
|
|
|
|
args = append(args, "--wait", "--output-format", "json")
|
|
|
|
|
|
|
|
|
|
out, err := r.Run(ctx, runner.Spec{Name: "xcrun", Args: args})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("notarytool submit: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var result struct {
|
|
|
|
|
ID string `json:"id"`
|
|
|
|
|
Status string `json:"status"`
|
|
|
|
|
}
|
|
|
|
|
if jerr := json.Unmarshal(out, &result); jerr != nil {
|
|
|
|
|
return fmt.Errorf("notarytool submit: parse output: %w (raw: %s)", jerr, string(out))
|
|
|
|
|
}
|
|
|
|
|
if result.Status == "Accepted" {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logOut, _ := r.Run(ctx, runner.Spec{
|
|
|
|
|
Name: "xcrun",
|
|
|
|
|
Args: append([]string{"notarytool", "log", result.ID}, credArgs...),
|
|
|
|
|
})
|
|
|
|
|
return fmt.Errorf("notarization %s: %s\n%s", result.Status, result.ID, string(logOut))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Staple runs `xcrun stapler staple` against the bundle.
|
|
|
|
|
func Staple(ctx context.Context, r runner.Runner, appPath string) error {
|
|
|
|
|
if _, err := r.Run(ctx, runner.Spec{
|
|
|
|
|
Name: "xcrun",
|
|
|
|
|
Args: []string{"stapler", "staple", appPath},
|
|
|
|
|
}); err != nil {
|
|
|
|
|
return fmt.Errorf("stapler staple: %w", err)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func credentialArgs(opts Opts) []string {
|
|
|
|
|
if opts.APIKey != nil {
|
|
|
|
|
return []string{
|
|
|
|
|
"--key", opts.APIKey.KeyPath,
|
|
|
|
|
"--key-id", opts.APIKey.KeyID,
|
|
|
|
|
"--issuer", opts.APIKey.IssuerID,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return []string{
|
|
|
|
|
"--apple-id", opts.AppleID.Username,
|
|
|
|
|
"--password", opts.AppleID.Password,
|
|
|
|
|
"--team-id", opts.AppleID.TeamID,
|
|
|
|
|
}
|
|
|
|
|
}
|