Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
123 commits
Select commit Hold shift + click to select a range
aa25ddc
workflow: derive the export method from the provisioning profile
Interlap01 Sep 16, 2026
b587320
docs: explain that the export method follows the profile
Interlap01 Sep 16, 2026
c35ddfb
workflow: set manageAppVersionAndBuildNumber with plutil
Interlap01 Sep 16, 2026
94eda37
docs: correct the provider signing doc for the export method
Interlap01 Sep 16, 2026
3833980
config: add build profiles and their resolution
Interlap01 Sep 16, 2026
cd92fbb
build: select a profile with --profile on ios build and ios share
Interlap01 Sep 16, 2026
63da3b6
workflows: apply the profile on the runner and export its env
Interlap01 Sep 16, 2026
7ef5346
docs: describe build profiles
Interlap01 Sep 16, 2026
f5872af
config: reserve the runner's secrets, shell and CI namespaces in prof…
Interlap01 Sep 16, 2026
9644933
build: explain a dispatch rejected by a workflow without the profile …
Interlap01 Sep 16, 2026
3c06c58
workflows: random GITHUB_ENV heredoc delimiter and a type check on env
Interlap01 Sep 16, 2026
3c883da
docs: defaultProfile also needs refreshed workflows; list reserved en…
Interlap01 Sep 16, 2026
636fb52
workflow: tolerate CRLF checkouts in the export-method test
Interlap01 Sep 16, 2026
9880363
asc: add App Store Connect API client
Interlap01 Sep 16, 2026
6bf8b32
auth: store App Store Connect API keys
Interlap01 Sep 16, 2026
364e38f
ios: upload IPAs to App Store Connect
Interlap01 Sep 16, 2026
146e287
ios: submit builds to TestFlight and App Review
Interlap01 Sep 16, 2026
ef25c19
docs: describe the TestFlight and App Store commands
Interlap01 Sep 16, 2026
d617332
build: pass profile settings and build options by pointer
Interlap01 Sep 16, 2026
7845248
asc: make waits injectable, back off status polls, drop unused helpers
Interlap01 Sep 16, 2026
23b9fd7
ios: print no JSON on a nil result and name the ASC_* variables
Interlap01 Sep 16, 2026
7aa74d6
docs: Release configuration prerequisite, drop roadmap item numbers
Interlap01 Sep 16, 2026
35efab4
workflow: stamp a build number on the runner
Interlap01 Sep 16, 2026
f2cee95
build: pass a build number and version to the runner
Interlap01 Sep 16, 2026
5c43085
asc: bundle ID, certificate, device and profile endpoints
Interlap01 Sep 16, 2026
e15399f
signing: provision certificates, devices and profiles through the ASC…
Interlap01 Sep 16, 2026
1abbeb5
signing: make setup automatic with the App Store Connect API key
Interlap01 Sep 16, 2026
5326857
ios: release command and build --submit
Interlap01 Sep 16, 2026
ba407c7
docs: describe ios release and automatic build numbers
Interlap01 Sep 16, 2026
ab63330
docs: automatic signing setup is the primary path
Interlap01 Sep 16, 2026
620099e
signing: check devices and write the key before requesting a certificate
Interlap01 Sep 16, 2026
21ea5f8
signing: register only devices whose MobAI ID is a UDID
Interlap01 Sep 16, 2026
d4293b7
docs: export method must follow the profile type
Interlap01 Sep 16, 2026
6f89fea
workflow: keep Codemagic's BUILD_NUMBER off plain builds
Interlap01 Sep 16, 2026
f66dfdb
Merge branch 'feat/export-method' into feat/signing-sets
Interlap01 Sep 16, 2026
d9974f1
Merge branch 'feat/build-profiles' into feat/signing-sets
Interlap01 Sep 16, 2026
f86a7ab
config: map a profile's distribution to its signing set
Interlap01 Sep 16, 2026
022e1a9
signing: read the profile type locally and name files by type
Interlap01 Sep 16, 2026
f385fdd
signing: upload setup material to the set of its type
Interlap01 Sep 16, 2026
0f8a66c
workflow: select the signing set by distribution and check the profil…
Interlap01 Sep 16, 2026
ce30801
docs: signing sets
Interlap01 Sep 16, 2026
cc0c28d
workflow: require a complete signing set and check the profile before…
Interlap01 Sep 16, 2026
afd81cf
workflow: tolerate CRLF checkouts in the signing set test
Interlap01 Sep 16, 2026
aedb6c9
docs: explain signing sets in the provider secrets guide
Interlap01 Sep 16, 2026
b5ed28b
github: list repository secret names
Interlap01 Sep 16, 2026
be0c51d
config: distribution is the only signing field of a profile
Interlap01 Sep 16, 2026
61de30c
signing: name types after distributions and share the test portal
Interlap01 Sep 16, 2026
8d54df8
build: print the signing set on the signing line
Interlap01 Sep 16, 2026
f4f38bd
signing: setup writes a build profile and ios build provisions a miss…
Interlap01 Sep 16, 2026
a1932ee
workflow: derive signing from the profile's distribution
Interlap01 Sep 16, 2026
a08adcd
docs: signing profiles with distribution
Interlap01 Sep 16, 2026
340d98b
github: explain 403 and 404 when listing secrets
Interlap01 Sep 16, 2026
8743b35
config: point app-store at store
Interlap01 Sep 16, 2026
f70abcd
build: check the signing set of the provider that runs the job
Interlap01 Sep 16, 2026
56b89b1
signing: keep a profile's spelling and report a replaced distribution
Interlap01 Sep 16, 2026
2d62ef9
docs: keep README line endings, say what signing setup leaves alone
Interlap01 Sep 16, 2026
afd4ad2
signing: --provider on setup and a hint when a build's secrets cannot…
Interlap01 Sep 16, 2026
13715af
signing: always print the secret values and treat a failed GitHub upl…
Interlap01 Sep 16, 2026
98cbfad
asc: match MobAI's key check, error window and profile delete
Interlap01 Sep 16, 2026
4f769e4
signing: write the private key as PKCS#8, read PKCS#1 too
Interlap01 Sep 16, 2026
cdb8bb8
docs: walk through creating the App Store Connect API key; drop the c…
Interlap01 Sep 17, 2026
933a104
merge: integrate ios release onto build profiles and signing sets
Interlap01 Sep 17, 2026
a9cf8fc
release: require an App Store build profile and provision its set
Interlap01 Sep 17, 2026
c44fa52
auth: say what auth apple saved
Interlap01 Sep 17, 2026
410325a
Merge branch 'feat/asc-client' into feat/signing-sets
Interlap01 Sep 17, 2026
46996aa
Merge branch 'feat/asc-client' into test/integration
Interlap01 Sep 17, 2026
2e9b6af
workflow: sign the archive with the identity the profile type needs
Interlap01 Sep 17, 2026
be46cb5
workflow: accept legacy iPhone Distribution and Developer certificates
Interlap01 Sep 17, 2026
88846cd
Merge branch 'feat/signing-sets' into test/integration
Interlap01 Sep 17, 2026
152608a
docs: explain creating the App Store Connect app record
Interlap01 Sep 17, 2026
f10f08f
Merge branch 'feat/signing-sets' into test/integration
Interlap01 Sep 17, 2026
feb5efa
asc: beta group, tester, team user and build management endpoints
Interlap01 Sep 17, 2026
18e6d36
ios: create missing TestFlight groups on submit, skip automatic-distr…
Interlap01 Sep 17, 2026
854cafe
asc: builder asc command group for apps, builds, groups, testers and …
Interlap01 Sep 17, 2026
8cc9876
docs: describe the asc management commands and group auto-create
Interlap01 Sep 17, 2026
7a67982
asc: invite beta testers, lowercase the email filter, refuse ambiguou…
Interlap01 Sep 17, 2026
eb47d69
distribute: invite NOT_INVITED testers after a group add, share the g…
Interlap01 Sep 17, 2026
9da531e
asc: testers invite, confirmed deletes with previews, one app resolver
Interlap01 Sep 17, 2026
dac9150
docs: asc testers invite, NOT_INVITED testers, confirmed deletes and …
Interlap01 Sep 17, 2026
b752a56
Merge branch 'feat/asc-manage' into test/integration
Interlap01 Sep 17, 2026
87e5daa
share: drop --profile; the simulator build takes no profile
Interlap01 Sep 17, 2026
d3414b2
Merge branch 'feat/signing-sets' into test/integration
Interlap01 Sep 17, 2026
685d1d8
asc: gofmt
Interlap01 Sep 17, 2026
42948fb
upload: show fractional megabytes, or kilobytes, in the progress line
Interlap01 Sep 17, 2026
741a573
asc: explain a refused invitation when the group has no build
Interlap01 Sep 17, 2026
5534520
signing: reuse the key from signing setup's --out-dir when provisioni…
Interlap01 Sep 17, 2026
dd662e4
signing: satisfy errcheck and unparam in the setup tests
Interlap01 Sep 17, 2026
6919ef9
build: print the failed step and the runner's errors when a run fails
Interlap01 Sep 17, 2026
f4449e6
Merge branch 'feat/signing-sets' into test/integration
Interlap01 Sep 17, 2026
e8a4877
Merge branch 'feat/asc-manage' into test/integration
Interlap01 Sep 17, 2026
ac4dbe0
release: default to the only store profile
Interlap01 Sep 17, 2026
048316e
Merge remote-tracking branch 'origin/main' into test/integration
Interlap01 Sep 17, 2026
ac9aef5
workflow: set manual signing on the app target only, not on every Pod…
Interlap01 Sep 17, 2026
0c93e74
Merge branch 'feat/signing-sets' into test/integration
Interlap01 Sep 17, 2026
896f7eb
workflow: match the Flutter build line by prefix in the signing test
Interlap01 Sep 17, 2026
ede7b47
Merge branch 'feat/signing-sets' into test/integration
Interlap01 Sep 17, 2026
e921ce7
ci: say why Codemagic or Bitrise rejected a request
Interlap01 Sep 17, 2026
6181e0f
Merge branch 'feat/signing-sets' into test/integration
Interlap01 Sep 17, 2026
7092e0e
asc: stop claiming the Apple key went to the keychain
Interlap01 Sep 17, 2026
1f66ee8
asc: trim comments and review fixes
Interlap01 Sep 17, 2026
ef87e3e
distribute: name only the groups the build was really added to
Interlap01 Sep 17, 2026
fdf52c2
release: trim comments and review fixes
Interlap01 Sep 17, 2026
5b83a4a
asc: trim comments and review fixes
Interlap01 Sep 17, 2026
9cd22f3
auth: drop the unrelated tail from the Apple key message
Interlap01 Sep 17, 2026
bf4f7f5
release: report a build timeout as the build's, not App Store Connect's
Interlap01 Sep 17, 2026
c544c2b
Merge branch 'feat/asc-client' into feat/asc-manage
Interlap01 Sep 17, 2026
adf3655
docs: keep README CRLF line endings
Interlap01 Sep 17, 2026
90811ef
signing: trim comments and review fixes
Interlap01 Sep 17, 2026
b0372b2
Merge branch 'feat/asc-client' into feat/signing-sets
Interlap01 Sep 17, 2026
c13d9ab
Merge branch 'feat/signing-sets' into feat/release
Interlap01 Sep 17, 2026
5880aa7
Merge branch 'feat/asc-manage' into feat/release
Interlap01 Sep 17, 2026
e97f798
asc: page through the app lookup instead of trusting a two-item filter
Interlap01 Sep 17, 2026
555822e
docs: extension targets are not signed yet
Interlap01 Sep 17, 2026
9388923
Merge branch 'feat/asc-client' into feat/signing-sets
Interlap01 Sep 17, 2026
b5d1479
Merge branch 'feat/asc-client' into feat/asc-manage
Interlap01 Sep 17, 2026
54c61f9
Merge branch 'feat/signing-sets' into feat/release
Interlap01 Sep 17, 2026
8831eb2
Merge branch 'feat/asc-manage' into feat/release
Interlap01 Sep 17, 2026
3156445
signing: provision and upload profiles for extension targets
Interlap01 Sep 17, 2026
54eb503
workflow: sign extension targets with their own profiles
Interlap01 Sep 17, 2026
a944716
docs: extension targets are signed with their own profiles
Interlap01 Sep 17, 2026
599fd8f
Merge branch 'feat/signing-sets' into feat/release
Interlap01 Sep 17, 2026
d0c603d
Merge remote-tracking branch 'origin/main' into feat/release
Interlap01 Sep 17, 2026
be9db57
cmd: set USERPROFILE with HOME in tests so ~ expands on Windows
Interlap01 Sep 17, 2026
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
152 changes: 142 additions & 10 deletions CLAUDE.md

Large diffs are not rendered by default.

1,759 changes: 1,022 additions & 737 deletions README.md

Large diffs are not rendered by default.

171 changes: 171 additions & 0 deletions cmd/builder/release.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package main

import (
"context"
"errors"
"fmt"
"strings"
"time"

"github.com/MobAI-App/ios-builder/internal/build"
"github.com/MobAI-App/ios-builder/internal/config"
"github.com/MobAI-App/ios-builder/internal/release"
"github.com/spf13/cobra"
)

var iosReleaseCmd = &cobra.Command{
Use: "release",
Short: "Build, upload to App Store Connect and submit in one command",
Long: `Builds the working tree with the next build number, uploads the IPA to App
Store Connect, waits for processing and hands the build on:

--testflight (default) adds it to the named TestFlight groups (--group,
repeatable) with --notes as "What to Test"
--app-store attaches it to the App Store version (--version, or the one
in the project) and submits it for review (--release
manual|after-approval)

The build number is one above the highest CFBundleVersion App Store Connect
holds for the app, so every release uploads; --build-number overrides it and
--version sets the marketing version. The app is identified by --bundle-id,
ios.bundleId in builder.json, or the newest IPA in the output directory.

Needs an App Store Connect API key (builder auth apple) and a builder.json
profile with "distribution": "store" (--profile, or defaultProfile, or the
only App Store profile there is), built in Release; builder signing setup
--distribution store writes the "store" profile.
Missing STORE signing secrets on GitHub are provisioned first, as ios build
--profile does.`,
Args: cobra.NoArgs,
RunE: runIOSRelease,
}

func init() {
f := iosReleaseCmd.Flags()
f.Bool("testflight", false, "Distribute to TestFlight (default)")
f.Bool("app-store", false, "Submit an App Store version for review")
f.StringArray("group", nil, "TestFlight group name to add the build to (repeatable)")
f.String("notes", "", "What to Test notes for the build")
f.String("release", "", "App Store release: manual or after-approval")
f.String("version", "", "Marketing version to build and submit (default: the project's)")
f.String("build-number", "", "CFBundleVersion to build (default: one above the highest in App Store Connect)")
f.String("bundle-id", "", "App bundle ID (default: ios.bundleId in builder.json, then the newest IPA in the output directory)")
f.Bool("no-encryption", false, "Declare the app uses no non-exempt encryption (export compliance)")
f.StringP("output", "o", "dist", "Output directory for the IPA")
f.StringP("remote", "r", "origin", "Git remote to push the working-tree snapshot to")
f.String("provider", "", "Override CI provider (default github or builder.json provider)")
f.String("profile", "", "App Store build profile from builder.json, distribution store (default: defaultProfile)")
f.Duration("timeout", 30*time.Minute, "Time limit for the build, and again for App Store Connect processing")
f.Bool("json", false, "Print the result as JSON (progress goes to stderr)")
iosCmd.AddCommand(iosReleaseCmd)
}

func runIOSRelease(cmd *cobra.Command, _ []string) error {
testflight, _ := cmd.Flags().GetBool("testflight")
appStore, _ := cmd.Flags().GetBool("app-store")
if testflight && appStore {
return fmt.Errorf("pass only one of --testflight or --app-store")
}
releaseFlag, _ := cmd.Flags().GetString("release")
releaseType, err := parseReleaseType(releaseFlag)
if err != nil {
return err
}
cfg, err := loadConfig()
if err != nil {
return err
}
if err := cfg.Validate(); err != nil {
return fmt.Errorf("invalid configuration: %w", err)
}
bo, err := buildOptionsFromFlags(cmd, cfg)
if err != nil {
return err
}
opts := &release.Options{Build: bo, AppStore: appStore, ReleaseType: releaseType}
opts.BundleID, _ = cmd.Flags().GetString("bundle-id")
opts.BuildNumber, _ = cmd.Flags().GetString("build-number")
opts.Version, _ = cmd.Flags().GetString("version")
opts.Groups, _ = cmd.Flags().GetStringArray("group")
opts.Notes, _ = cmd.Flags().GetString("notes")
opts.NoEncryption, _ = cmd.Flags().GetBool("no-encryption")
return runRelease(cmd, cfg, opts)
}

// releaseError keeps a build deadline out of finish, which reads every
// DeadlineExceeded as the App Store Connect wait and claims processing continues.
func releaseError(res *release.Result, err error, timeout time.Duration) error {
if errors.Is(err, context.DeadlineExceeded) && (res == nil || res.IPAPath == "") {
return fmt.Errorf("the build did not finish within %s; raise --timeout (%v)", timeout, err)
}
return err
}

// buildOptionsFromFlags reads the flags `ios build` and `ios release` share;
// Provider is resolved as the coordinator will, so the GitHub client agrees.
func buildOptionsFromFlags(cmd *cobra.Command, cfg *config.Config) (build.BuildOptions, error) {
var opts build.BuildOptions
opts.OutputDir, _ = cmd.Flags().GetString("output")
opts.Timeout, _ = cmd.Flags().GetDuration("timeout")
opts.Remote, _ = cmd.Flags().GetString("remote")
opts.Profile, _ = cmd.Flags().GetString("profile")
providerFlag, _ := cmd.Flags().GetString("provider")
provider, err := effectiveProvider(cfg, opts.Profile, providerFlag)
if err != nil {
return opts, err
}
opts.Provider = provider
return opts, nil
}

// runRelease is the flow behind `ios release` and `ios build --submit`: API
// key, App Store profile and (on GitHub) the STORE signing set are settled
// before anything is pushed.
func runRelease(cmd *cobra.Command, cfg *config.Config, opts *release.Options) error {
client, err := getASCClient()
if err != nil {
return err
}
out := newOutput(cmd)
opts.Log = out.log
// Preflight may pick the only store profile; the build, the provider and
// the signing set below must all follow that choice.
opts.Build.Profile, err = release.Preflight(cfg, opts.Build.Profile, out.log)
if err != nil {
return err
}
providerFlag, _ := cmd.Flags().GetString("provider")
opts.Build.Provider, err = effectiveProvider(cfg, opts.Build.Profile, providerFlag)
if err != nil {
return err
}
ghClient, err := clientForProvider(cfg, opts.Build.Provider)
if err != nil {
return err
}
ctx, cancel := commandContext(cmd, false)
defer cancel()

if ghClient != nil {
if err := ensureSigningSecrets(ctx, cfg, ghClient, getASCClient, opts.Build.Profile, opts.Build.Provider, out.log); err != nil {
return err
}
}
coordinator := build.NewCoordinatorWithOutput(cfg, ghClient, out.log)
res, err := release.Run(ctx, cfg, coordinator, client, opts)
err = releaseError(res, err, opts.Build.Timeout)
return finish(out, cmd, res, err, func() {
fmt.Println()
fmt.Printf("IPA: %s\n", res.IPAPath)
fmt.Printf("Build number: %s (version %s)\n", res.BuildNumber, res.Version)
fmt.Printf("ASC build: %s\n", res.ASCBuildID)
if len(res.Groups) > 0 {
names := make([]string, 0, len(res.Groups))
for _, g := range res.Groups {
names = append(names, g.Name)
}
fmt.Printf("Groups: %s\n", strings.Join(names, ", "))
}
fmt.Printf("Link: %s\n", res.Link)
})
}
28 changes: 28 additions & 0 deletions cmd/builder/release_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

import (
"context"
"errors"
"fmt"
"strings"
"testing"
"time"

"github.com/MobAI-App/ios-builder/internal/release"
)

func TestReleaseErrorTellsBuildTimeoutFromASCWait(t *testing.T) {
deadline := fmt.Errorf("build failed: %w", context.DeadlineExceeded)
err := releaseError(&release.Result{BuildNumber: "3"}, deadline, 5*time.Minute)
if errors.Is(err, context.DeadlineExceeded) || !strings.Contains(err.Error(), "did not finish within 5m0s") {
t.Errorf("build deadline = %v", err)
}
// After the IPA exists the deadline is the App Store Connect wait; finish explains that one.
if err := releaseError(&release.Result{IPAPath: "dist/app.ipa"}, deadline, time.Minute); !errors.Is(err, context.DeadlineExceeded) {
t.Errorf("ASC deadline = %v", err)
}
other := errors.New("runner exploded")
if err := releaseError(nil, other, time.Minute); err != other {
t.Errorf("other error = %v", err)
}
}
99 changes: 84 additions & 15 deletions cmd/builder/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/MobAI-App/ios-builder/internal/build"
"github.com/MobAI-App/ios-builder/internal/config"
"github.com/MobAI-App/ios-builder/internal/github"
"github.com/MobAI-App/ios-builder/internal/release"
"github.com/MobAI-App/ios-builder/internal/update"
"github.com/MobAI-App/ios-builder/internal/workflow"
"github.com/manifoldco/promptui"
Expand Down Expand Up @@ -250,6 +251,44 @@ func detectIOSPath() (string, string) {
return "", ""
}

// bundleIDRe matches PRODUCT_BUNDLE_IDENTIFIER assignments in a project.pbxproj.
var bundleIDRe = regexp.MustCompile(`PRODUCT_BUNDLE_IDENTIFIER\s*=\s*"?([^";\s]+)"?\s*;`)

// detectBundleID reads the app's bundle identifier from the Xcode project
// under iosPath, skipping test targets and $(…) values. Anything still
// ambiguous yields "" so init leaves the field for `signing setup` to resolve.
func detectBundleID(iosPath string) string {
if iosPath == "" {
iosPath = "."
}
projects, _ := filepath.Glob(filepath.Join(iosPath, "*.xcodeproj", "project.pbxproj"))
var found []string
for _, path := range projects {
data, err := os.ReadFile(path)
if err != nil {
continue
}
found = append(found, bundleIDsFromPbxproj(string(data))...)
}
if len(found) == 1 {
return found[0]
}
return ""
}

// bundleIDsFromPbxproj returns the distinct app bundle identifiers in pbxproj text.
func bundleIDsFromPbxproj(text string) []string {
var ids []string
for _, m := range bundleIDRe.FindAllStringSubmatch(text, -1) {
id := m[1]
if strings.Contains(id, "$") || strings.HasSuffix(id, "Tests") || slices.Contains(ids, id) {
continue
}
ids = append(ids, id)
}
return ids
}

func detectGitHubRepo(remoteName string) (owner, repo string, err error) {
// Try to get GitHub remote URL from git
cmd := exec.Command("git", "remote", "get-url", remoteName)
Expand Down Expand Up @@ -432,6 +471,10 @@ func runInit(cmd *cobra.Command, args []string) error {
cfg.Project, cfg.Platform = projectName, "ios"
cfg.GitHub = config.GitHubConfig{Owner: githubOwner, Repo: repoName}
cfg.IOS.Path, cfg.IOS.Scheme = iosPath, scheme
if cfg.IOS.BundleID == "" {
cfg.IOS.BundleID = detectBundleID(iosPath)
}
syncExtensions(cfg, os.Stdout)
if flutterVersion != "" {
cfg.Flutter.Version = flutterVersion
}
Expand Down Expand Up @@ -506,7 +549,7 @@ func runInit(cmd *cobra.Command, args []string) error {

if buildErr == nil {
fmt.Println()
return runBuild(context.Background(), cfg, build.BuildOptions{
return runBuild(context.Background(), cfg, &build.BuildOptions{
OutputDir: "dist",
Timeout: build.DefaultTimeout,
Remote: remoteName,
Expand Down Expand Up @@ -587,6 +630,8 @@ func init() {
iosBuildCmd.Flags().Bool("unsigned", false, "Build unsigned IPA (skip code signing even if configured)")
iosBuildCmd.Flags().StringP("remote", "r", "origin", "Git remote to push the working-tree snapshot to")
iosBuildCmd.Flags().String("provider", "", "Override CI provider (default github or builder.json provider)")
iosBuildCmd.Flags().String("profile", "", "Build profile from builder.json (default: defaultProfile, else the top-level ios settings)")
iosBuildCmd.Flags().Bool("submit", false, "Also upload to App Store Connect and process for TestFlight (short for: ios release)")
iosCmd.AddCommand(iosBuildCmd)

// iOS share command flags
Expand All @@ -596,6 +641,20 @@ func init() {
iosCmd.AddCommand(iosShareCmd)
}

// effectiveProvider is the --provider flag, else the selected profile's
// provider, else builder.json's. The coordinator resolves the same chain; this
// exists so the GitHub client and signal handling agree with it.
func effectiveProvider(cfg *config.Config, profile, flag string) (string, error) {
if flag != "" {
return flag, nil
}
s, err := cfg.ResolveProfile(profile)
if err != nil {
return "", err
}
return s.Provider, nil
}

func runIOSBuild(cmd *cobra.Command, args []string) error {
cfg, err := loadConfig()
if err != nil {
Expand All @@ -606,18 +665,25 @@ func runIOSBuild(cmd *cobra.Command, args []string) error {
return fmt.Errorf("invalid configuration: %w", err)
}

outputDir, _ := cmd.Flags().GetString("output")
timeout, _ := cmd.Flags().GetDuration("timeout")
unsigned, _ := cmd.Flags().GetBool("unsigned")
remote, _ := cmd.Flags().GetString("remote")
provider, _ := cmd.Flags().GetString("provider")
opts, err := buildOptionsFromFlags(cmd, cfg)
if err != nil {
return err
}
opts.Unsigned, _ = cmd.Flags().GetBool("unsigned")
submit, _ := cmd.Flags().GetBool("submit")
if submit {
if opts.Unsigned {
return fmt.Errorf("--submit uploads to App Store Connect, which needs a signed build; drop --unsigned")
}
return runRelease(cmd, cfg, &release.Options{Build: opts})
}

ctx := cmd.Context()
if ctx == nil {
ctx = context.Background()
}

name, err := cfg.ProviderName(provider)
name, err := cfg.ProviderName(opts.Provider)
if err != nil {
return err
}
Expand All @@ -626,13 +692,7 @@ func runIOSBuild(cmd *cobra.Command, args []string) error {
ctx, stop = signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
defer stop()
}
return runBuild(ctx, cfg, build.BuildOptions{
Provider: provider,
OutputDir: outputDir,
Timeout: timeout,
Unsigned: unsigned,
Remote: remote,
})
return runBuild(ctx, cfg, &opts)
}

func runIOSShare(cmd *cobra.Command, args []string) error {
Expand All @@ -646,6 +706,8 @@ func runIOSShare(cmd *cobra.Command, args []string) error {

duration, _ := cmd.Flags().GetDuration("duration")
remote, _ := cmd.Flags().GetString("remote")
// A simulator build takes no profile, so the provider is the flag, else
// builder.json's.
provider, _ := cmd.Flags().GetString("provider")

ctx := cmd.Context()
Expand Down Expand Up @@ -688,11 +750,18 @@ func runIOSShare(cmd *cobra.Command, args []string) error {
return nil
}

func runBuild(ctx context.Context, cfg *config.Config, opts build.BuildOptions) error {
func runBuild(ctx context.Context, cfg *config.Config, opts *build.BuildOptions) error {
ghClient, err := clientForProvider(cfg, opts.Provider)
if err != nil {
return err
}
// A GitHub build with a distribution needs its signing set in the
// repository; ensureSigningSecrets leaves Codemagic and Bitrise alone.
if ghClient != nil && !opts.Unsigned {
if err := ensureSigningSecrets(ctx, cfg, ghClient, getASCClient, opts.Profile, opts.Provider, os.Stdout); err != nil {
return err
}
}

coordinator := build.NewCoordinator(cfg, ghClient)

Expand Down
Loading
Loading