Skip to content
Open
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
110 changes: 110 additions & 0 deletions MAS_SUBMISSION_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Mac App Store Submission Guide

## Prerequisites

- Apple Developer account with App Store Connect access
- Xcode with "Apple Distribution" signing certificate installed
- Mac App Store provisioning profile for `com.macscp.macSCP`

## Build Configuration

### MAS Build Flag

This project uses a `MAS_BUILD` Swift compiler flag to conditionally exclude Sparkle (third-party update framework), which Apple does not allow on the App Store.

To build for the Mac App Store:

1. In Xcode, go to **Build Settings** → **Swift Compiler - Custom Flags** → **Active Compilation Conditions**
2. Add `MAS_BUILD` to the Release configuration
3. Alternatively, pass it via xcodebuild:

```bash
xcodebuild archive \
-project macSCP.xcodeproj \
-scheme macSCP \
-archivePath build/macSCP-MAS.xcarchive \
SWIFT_ACTIVE_COMPILATION_CONDITIONS='$(inherited) MAS_BUILD' \
CODE_SIGN_IDENTITY="Apple Distribution" \
CODE_SIGN_STYLE=Manual \
PROVISIONING_PROFILE_SPECIFIER="macSCP App Store"
```

### MAS Entitlements

Use `macSCP/macSCP_MAS.entitlements` for the App Store build. This file mirrors the standard entitlements but is suitable for MAS distribution.

When exporting the archive:

```bash
xcodebuild -exportArchive \
-archivePath build/macSCP-MAS.xcarchive \
-exportPath build/MAS \
-exportOptionsPlist ExportOptions-MAS.plist
```

### ExportOptions-MAS.plist (create this)

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store</string>
<key>teamID</key>
<string>NW7K6UFA6P</string>
<key>uploadSymbols</key>
<true/>
<key>signingStyle</key>
<string>manual</string>
<key>signingCertificate</key>
<string>Apple Distribution</string>
<key>provisioningProfiles</key>
<dict>
<key>com.macscp.macSCP</key>
<string>macSCP App Store</string>
</dict>
</dict>
</plist>
```

## App Store Connect Setup

1. **Create the app** in App Store Connect with bundle ID `com.macscp.macSCP`
2. **App Information**:
- Name: macSCP
- Subtitle: SFTP, S3 & SSH for Mac
- Category: Developer Tools
- Secondary Category: Utilities
3. **Description**:
> macSCP is a native macOS client for managing remote servers via SFTP, Amazon S3, and SSH terminal — all in one app built with SwiftUI.
>
> Features:
> • SFTP file browser with drag-and-drop transfers
> • Amazon S3 bucket management
> • Built-in SSH terminal
> • Secure credential storage in Keychain
> • Multiple simultaneous connections
> • Dark mode support
4. **Keywords**: sftp, s3, ssh, terminal, file transfer, remote, server, ftp, macos, developer
5. **Screenshots**: Required sizes — 1280x800 and 1440x900 (or retina equivalents)
6. **Privacy Policy URL**: Required — add to macscp.co
7. **Support URL**: https://github.com/macnev2013/macSCP/issues
8. **Marketing URL**: https://www.macscp.co

## Upload & Submit

1. Archive in Xcode (Product → Archive) with MAS build settings
2. Upload via Xcode Organizer or `xcrun altool --upload-app`
3. In App Store Connect, select the build and submit for review

## Review Notes

> macSCP requires network access (outgoing and incoming) to connect to SFTP servers, Amazon S3 buckets, and SSH terminals. The app stores connection credentials securely in the macOS Keychain.
>
> To test: Add an SFTP connection using any publicly accessible SFTP server, or configure an S3 bucket with valid AWS credentials.

## Timeline

- App Store review typically takes 1-3 days
- Submit by March 22 to be live for launch week (March 25+)
4 changes: 2 additions & 2 deletions macSCP.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 0.2.4;
MARKETING_VERSION = 0.2.7;
PRODUCT_BUNDLE_IDENTIFIER = com.macscp.macSCP;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
Expand Down Expand Up @@ -558,7 +558,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 0.2.4;
MARKETING_VERSION = 0.2.7;
PRODUCT_BUNDLE_IDENTIFIER = com.macscp.macSCP;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
Expand Down
8 changes: 8 additions & 0 deletions macSCP/App/MacSCPApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,24 @@

import SwiftUI
import SwiftData
#if !MAS_BUILD
import Sparkle
#endif

@main
struct MacSCPApp: App {
@StateObject private var container = DependencyContainer.shared

#if !MAS_BUILD
private let updaterController: SPUStandardUpdaterController
@StateObject private var checkForUpdatesViewModel: CheckForUpdatesViewModel
#endif

init() {
AnalyticsService.initialize()
AppLockManager.shared.lockIfNeeded()

#if !MAS_BUILD
let controller = SPUStandardUpdaterController(
startingUpdater: true,
updaterDelegate: nil,
Expand All @@ -29,6 +34,7 @@ struct MacSCPApp: App {
self._checkForUpdatesViewModel = StateObject(
wrappedValue: CheckForUpdatesViewModel(updater: controller.updater)
)
#endif
}

var body: some Scene {
Expand Down Expand Up @@ -94,9 +100,11 @@ struct MacSCPApp: App {
// MARK: - Commands
@CommandsBuilder
private var appCommands: some Commands {
#if !MAS_BUILD
CommandGroup(after: .appInfo) {
CheckForUpdatesView(viewModel: checkForUpdatesViewModel)
}
#endif

CommandGroup(replacing: .newItem) {
Button("New Connection") {
Expand Down
2 changes: 2 additions & 0 deletions macSCP/Features/Settings/CheckForUpdatesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// macSCP
//

#if !MAS_BUILD
import SwiftUI

struct CheckForUpdatesView: View {
Expand All @@ -13,3 +14,4 @@ struct CheckForUpdatesView: View {
.disabled(!viewModel.canCheckForUpdates)
}
}
#endif
2 changes: 2 additions & 0 deletions macSCP/Features/Settings/CheckForUpdatesViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// macSCP
//

#if !MAS_BUILD
import Combine
import Foundation
import Sparkle
Expand All @@ -26,3 +27,4 @@ final class CheckForUpdatesViewModel: ObservableObject {
logInfo("Manual update check triggered", category: .app)
}
}
#endif
20 changes: 20 additions & 0 deletions macSCP/macSCP_MAS.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.macSCP.keychain</string>
</array>
</dict>
</plist>