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
35 changes: 35 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copy to .env for local-only App Store Connect settings.

# Optional asc profile created with `asc auth login --name ...`
ASC_PROFILE=

# Required. App Store Connect app ID for in.fourplex.Inspect.
ASC_APP_ID=

# Required for xcodebuild signing and asc uploads.
APP_STORE_CONNECT_KEY_NAME=Inspect
APP_STORE_CONNECT_KEY_ID=
APP_STORE_CONNECT_ISSUER_ID=
APP_STORE_CONNECT_KEY_PATH=

# Optional. When set, the uploaded build is also distributed to this beta group name or ID.
TESTFLIGHT_GROUP=

# Optional release notes / What to Test text.
TESTFLIGHT_NOTES=
TESTFLIGHT_LOCALE=en-US

# Wait for App Store Connect build processing before finishing.
TESTFLIGHT_WAIT=true

# Notify testers when TESTFLIGHT_GROUP is set.
TESTFLIGHT_NOTIFY=false

# xcodebuild archive/export settings.
TESTFLIGHT_ALLOW_PROVISIONING_UPDATES=true
TESTFLIGHT_TIMEOUT=30m
TESTFLIGHT_BUILD_NUMBER=

# Override only if you need a different scheme, version, or destination.
# TESTFLIGHT_SCHEME=Inspect
# TESTFLIGHT_VERSION=2.1
7 changes: 2 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,8 @@ jobs:
- name: Generate Xcode project
run: xcodegen generate

- name: Verify generated project is up to date
run: git diff --exit-code -- Inspect.xcodeproj

- name: Build InspectCore package for iOS Simulator
run: xcodebuild -scheme InspectCore -project Inspect.xcodeproj -destination 'generic/platform=iOS Simulator' build -quiet
run: set -o pipefail && xcodebuild -scheme InspectCore -project Inspect.xcodeproj -destination 'generic/platform=iOS Simulator' build | xcbeautify

- name: Build iOS app for Simulator
run: xcodebuild -scheme Inspect -project Inspect.xcodeproj -destination 'generic/platform=iOS Simulator' build -quiet
run: set -o pipefail && xcodebuild -scheme Inspect -project Inspect.xcodeproj -destination 'generic/platform=iOS Simulator' build | xcbeautify
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Xcode
.DS_Store
Inspect.xcodeproj/
build/
.build/
Packages/*/.build/
Expand All @@ -21,6 +22,7 @@ DerivedData
Carthage/
*~
*swp
.env
xcuserdata/
*.xcuserdatad/
output/
Expand All @@ -29,3 +31,4 @@ Pods/
fastlane/README.md
fastlane/report.xml
Configs/LocalOverrides.xcconfig
.asc/
9 changes: 9 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Inspect Agent Notes

- `project.yml` is the source of truth for the Xcode project. Regenerate `Inspect.xcodeproj` with `xcodegen generate`; do not commit the generated project.
- Use [`justfile`](/Users/hewig/workspace/h/Inspect/justfile) for common local tasks. The main release entry point is `just testflight`.
- Prefer `xcbeautify` for `xcodebuild` output. The repo scripts and CI are expected to use `xcodebuild ... | xcbeautify`.
- TestFlight uploads go through [`scripts/testflight.sh`](/Users/hewig/workspace/h/Inspect/scripts/testflight.sh) and read settings from `.env`.
- App Store screenshots go through [`scripts/app_store_screenshots.sh`](/Users/hewig/workspace/h/Inspect/scripts/app_store_screenshots.sh). Use the built-in `INSPECT_SCREENSHOT_SCENARIO` launch mode rather than hand-editing UI state for storefront captures.
- Required TestFlight env vars: `ASC_APP_ID`, `APP_STORE_CONNECT_KEY_ID`, `APP_STORE_CONNECT_ISSUER_ID`, `APP_STORE_CONNECT_KEY_PATH`.
- If the current App Store Connect app already has the repo's `CURRENT_PROJECT_VERSION`, set `TESTFLIGHT_BUILD_NUMBER` in `.env` before uploading.
2 changes: 1 addition & 1 deletion App/Sources/InspectApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import SwiftUI
struct InspectApp: App {
var body: some Scene {
WindowGroup {
InspectionRootView()
InspectionRootView(screenshotScenario: .current)
.tint(.inspectAccent)
}
}
Expand Down
16 changes: 16 additions & 0 deletions Configs/TestFlightExportOptions.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?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>destination</key>
<string>export</string>
<key>method</key>
<string>app-store-connect</string>
<key>signingStyle</key>
<string>automatic</string>
<key>stripSwiftSymbols</key>
<true/>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>
8 changes: 4 additions & 4 deletions Inspect.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 70;
CURRENT_PROJECT_VERSION = 72;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
Expand All @@ -339,7 +339,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
MARKETING_VERSION = 2.1;
MARKETING_VERSION = 2.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -387,7 +387,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 70;
CURRENT_PROJECT_VERSION = 72;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
Expand All @@ -400,7 +400,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
MARKETING_VERSION = 2.1;
MARKETING_VERSION = 2.2;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ enum CertificateExportWriter {
}

enum InspectClipboard {
@MainActor
static func copy(_ value: String) {
UIPasteboard.general.string = value
UINotificationFeedbackGenerator().notificationOccurred(.success)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ struct InspectionPageHeader: View {
var body: some View {
VStack(spacing: 6) {
ZStack {
Text("Inspect")
.font(.inspectRootTitle)
.frame(maxWidth: .infinity)
if !ProcessInfo.processInfo.isiOSAppOnMac {
Text("Inspect")
.font(.inspectRootTitle)
.frame(maxWidth: .infinity)
}

if let closeAction {
HStack {
Expand Down
31 changes: 24 additions & 7 deletions Packages/InspectCore/Sources/Feature/InspectionRootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,36 @@ public struct InspectionRootView: View {
private let initialURL: URL?
private let closeAction: (() -> Void)?
private let presentation: InspectionPresentation
private let screenshotScenario: InspectionScreenshotScenario?

public init(
initialURL: URL? = nil,
closeAction: (() -> Void)? = nil,
presentation: InspectionPresentation = .app
presentation: InspectionPresentation = .app,
screenshotScenario: InspectionScreenshotScenario? = nil
) {
self.initialURL = initialURL
self.closeAction = closeAction
self.presentation = presentation
self.screenshotScenario = screenshotScenario
}

public var body: some View {
NavigationStack {
content
}
.tint(.inspectAccent)
.task(id: initialURL?.absoluteString) {
store.bootstrap(initialURL: initialURL)
.task(id: bootstrapURL?.absoluteString) {
store.bootstrap(initialURL: bootstrapURL)
}
}

@ViewBuilder
private var content: some View {
if presentation == .actionExtension, let report = store.report {
if presentation == .app, screenshotScenario?.showsCertificateDetail == true, let report = store.report {
CertificateDetailView(report: report, initialSelectionIndex: 0)
.ensureNavigationBarVisible()
} else if presentation == .actionExtension, let report = store.report {
CertificateDetailView(report: report, initialSelectionIndex: 0)
.toolbar {
if let closeAction {
Expand All @@ -56,7 +62,9 @@ public struct InspectionRootView: View {

private var rootContent: some View {
let report = store.report
let recentItems = store.recentInputs.map(RecentLookupItem.init)
let recentItems = screenshotScenario?.showsRecents == false
? []
: store.recentInputs.map(RecentLookupItem.init)

return ZStack {
Color.clear
Expand Down Expand Up @@ -92,8 +100,10 @@ public struct InspectionRootView: View {
isInputFocused: $isInputFocused
)

InspectionAppLinksCard(appVersionText: InspectionAppMetadata.versionText)
.id("about")
if screenshotScenario?.showsAboutCard != false {
InspectionAppLinksCard(appVersionText: InspectionAppMetadata.versionText)
.id("about")
}
}
.padding(.horizontal, 20)
.padding(.top, presentation.topPadding)
Expand All @@ -107,6 +117,13 @@ public struct InspectionRootView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.onAppear {
isInputFocused = false
if let screenshotScenario {
dismissDemoTarget = screenshotScenario.showsDemoTargets == false
}
}
}

private var bootstrapURL: URL? {
screenshotScenario?.initialURL ?? initialURL
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Foundation

public enum InspectionScreenshotScenario: String {
case home
case summary
case risk
case detail

public static var current: InspectionScreenshotScenario? {
ProcessInfo.processInfo.environment["INSPECT_SCREENSHOT_SCENARIO"]
.flatMap(InspectionScreenshotScenario.init(rawValue:))
}

var initialURL: URL? {
switch self {
case .home:
return nil
case .summary, .detail:
return URL(string: "https://hewig.dev")
case .risk:
return URL(string: "https://untrusted-root.badssl.com")
}
}

var showsAboutCard: Bool {
false
}

var showsCertificateDetail: Bool {
self == .detail
}

var showsDemoTargets: Bool {
self == .home
}

var showsRecents: Bool {
false
}
}
68 changes: 68 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ This repository reflects a full rewrite around current Apple platform APIs and m
- Xcode 16 or newer
- iOS 18 SDK
- XcodeGen
- xcbeautify

## Getting Started

Expand All @@ -49,6 +50,8 @@ Generate the Xcode project:
xcodegen generate
```

`Inspect.xcodeproj` is generated from `project.yml` and is not intended to be committed.

Open the project:

```bash
Expand All @@ -70,6 +73,71 @@ Build the app from the command line:
xcodebuild -scheme Inspect -project Inspect.xcodeproj -destination 'generic/platform=iOS Simulator' build
```

For readable `xcodebuild` output in this repo, prefer piping through `xcbeautify`.

## TestFlight Uploads

This repo includes a `just testflight` flow that archives the app, exports an IPA, and uploads it with [`asc`](https://github.com/rudrankriyam/App-Store-Connect-CLI).

One-time setup:

```bash
cp Configs/LocalOverrides.xcconfig.example Configs/LocalOverrides.xcconfig
cp .env.example .env
asc auth login \
--name "Inspect" \
--key-id "ABC123XYZ" \
--issuer-id "00000000-0000-0000-0000-000000000000" \
--private-key /path/to/AuthKey_ABC123XYZ.p8
```

Then update `.env`. `ASC_APP_ID`, `APP_STORE_CONNECT_KEY_ID`, `APP_STORE_CONNECT_ISSUER_ID`, and `APP_STORE_CONNECT_KEY_PATH` are required. If you also set `TESTFLIGHT_GROUP`, the command will distribute the processed build to that group after upload.

Run the full flow:

```bash
just testflight
```

Useful variants:

```bash
just testflight-build
just testflight-dry-run
just app-store-screenshots
```

Notes:

- `just testflight` regenerates `Inspect.xcodeproj` with XcodeGen before archiving.
- The archive/export flow uses automatic signing and `-allowProvisioningUpdates` by default. Set `TESTFLIGHT_ALLOW_PROVISIONING_UPDATES=false` in `.env` if you do not want that behavior.
- Set `TESTFLIGHT_BUILD_NUMBER` in `.env` when you need to override `CURRENT_PROJECT_VERSION` for an upload.
- `just testflight-build` stops after producing an IPA in `build/testflight/export/`.

## App Store Screenshots

This repo includes [`scripts/app_store_screenshots.sh`](/Users/hewig/workspace/h/Inspect/scripts/app_store_screenshots.sh) to capture current App Store screenshots from Simulator using a launch-only screenshot mode built into the app.

Capture fresh screenshots locally:

```bash
just app-store-screenshots
```

That writes iPhone and iPad assets under `build/app-store-screenshots/output/`.

To upload them to an App Store version localization:

```bash
./scripts/app_store_screenshots.sh upload <VERSION_LOCALIZATION_ID>
```

Or capture and upload in one pass:

```bash
./scripts/app_store_screenshots.sh capture-upload <VERSION_LOCALIZATION_ID>
```

## Status

Inspect is actively maintained in its rewritten SwiftUI/SPM form. If you are looking at older screenshots, implementation notes, or legacy code references, they likely predate this rewrite.
19 changes: 19 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
set shell := ["zsh", "-eu", "-o", "pipefail", "-c"]

default:
@just --list

generate:
xcodegen generate

testflight:
./scripts/testflight.sh upload

testflight-build:
./scripts/testflight.sh build

testflight-dry-run:
./scripts/testflight.sh dry-run

app-store-screenshots:
./scripts/app_store_screenshots.sh capture
Loading