diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8aa33c1..4b3a4cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: - name: Auto-configure if unconfigured run: | # Detect if this is an unconfigured template - if grep -q "{{PROJECT_NAME}}" project.yml 2>/dev/null; then + if grep -q "TestApp" project.yml 2>/dev/null; then echo "🔧 Unconfigured template detected - generating minimal test project" ./scripts/setup.sh \ --project-name "CITestApp" \ @@ -37,7 +37,6 @@ jobs: --generate-minimal \ --no-git-hooks \ --no-commit \ - --skip-brew \ --force echo "✅ Template auto-configured for CI testing" else diff --git a/.gitignore b/.gitignore index 9490c2b..d3c0129 100644 --- a/.gitignore +++ b/.gitignore @@ -102,9 +102,3 @@ iOSInjectionProject/ *.pyo *.rej *.swp - -# CI Test Projects (prevent accidental commits) -/ci-test-*/ -/ci-test-project/ -/CITest*/ -/TestApp*/ diff --git a/.swiftformat b/.swiftformat index 089877e..b92e95e 100644 --- a/.swiftformat +++ b/.swiftformat @@ -1,4 +1,4 @@ -# SwiftFormat configuration for {{PROJECT_NAME}} +# SwiftFormat configuration for TestApp # For more information: https://github.com/nicklockwood/SwiftFormat # Version diff --git a/.swiftlint.yml b/.swiftlint.yml index 462c212..0af1eef 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -39,9 +39,9 @@ opt_in_rules: - yoda_condition included: - - {{PROJECT_NAME}} - - {{PROJECT_NAME}}Tests - - {{PROJECT_NAME}}UITests + - TestApp + - TestAppTests + - TestAppUITests excluded: - Pods @@ -114,12 +114,6 @@ large_tuple: warning: 3 error: 4 -# Allow implicitly unwrapped optionals in UI tests (common pattern for XCUIApplication) -implicitly_unwrapped_optional: - excluded: - - "**/*UITests.swift" - - "**/*UITest.swift" - # Custom rules for better code quality custom_rules: no_objcMembers: diff --git a/CLAUDE.md b/CLAUDE.md index a584149..86d47b4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,7 +32,7 @@ This is a **Swift iOS project template** that uses: ### Core Components - **`scripts/` Directory**: Contains all development automation scripts with helper functions in `_helpers.sh` -- **`project.yml`**: XcodeGen configuration with placeholders like `{{PROJECT_NAME}}` +- **`project.yml`**: XcodeGen configuration with placeholders like `TestApp` - **`simulator.yml`**: Auto-generated simulator configuration for tests - **`.github/workflows/ci.yml`**: Unified CI workflow that auto-configures if needed - **Brewfile**: Manages all development tool dependencies (yq, jq, xcodegen, swiftlint, etc.) @@ -80,9 +80,9 @@ This is a **Swift iOS project template** that uses: ## Configuration Files ### Critical Files (Pre-configured with Placeholders) -- **`project.yml`** - XcodeGen project definition with `{{PROJECT_NAME}}` placeholders +- **`project.yml`** - XcodeGen project definition with `TestApp` placeholders - **`simulator.yml`** - Auto-generated during setup with sensible defaults -- **`.swiftlint.yml`** - SwiftLint rules with `{{PROJECT_NAME}}` in paths +- **`.swiftlint.yml`** - SwiftLint rules with `TestApp` in paths - **`.swiftformat`** - SwiftFormat configuration ready to use ### Development Dependencies @@ -151,7 +151,7 @@ All tools installed via Brewfile: yq, jq, xcodegen, swiftlint, swiftformat, xcbe ### CI Workflow 1. Checkout code -2. Detect if project is configured (check for `{{PROJECT_NAME}}` in project.yml) +2. Detect if project is configured (check for `TestApp` in project.yml) 3. If unconfigured: run `./scripts/setup.sh --generate-minimal` with test parameters 4. Install dependencies via Brewfile 5. Generate Xcode project with xcodegen (or use existing in adopt mode) diff --git a/TestApp/AppDelegate.swift b/TestApp/AppDelegate.swift new file mode 100644 index 0000000..40ef4c7 --- /dev/null +++ b/TestApp/AppDelegate.swift @@ -0,0 +1,27 @@ +import UIKit + +@main +final class AppDelegate: UIResponder, UIApplicationDelegate { + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + true + } + + func application( + _ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions + ) -> UISceneConfiguration { + UISceneConfiguration( + name: "Default Configuration", + sessionRole: connectingSceneSession.role + ) + } + + func application( + _ application: UIApplication, + didDiscardSceneSessions sceneSessions: Set + ) {} +} diff --git a/TestApp/Extensions/.gitkeep b/TestApp/Extensions/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/TestApp/Helpers/.gitkeep b/TestApp/Helpers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/TestApp/Info.plist b/TestApp/Info.plist new file mode 100644 index 0000000..fc831b0 --- /dev/null +++ b/TestApp/Info.plist @@ -0,0 +1,62 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + TestApp + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/TestApp/Models/.gitkeep b/TestApp/Models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/TestApp/SceneDelegate.swift b/TestApp/SceneDelegate.swift new file mode 100644 index 0000000..25c2063 --- /dev/null +++ b/TestApp/SceneDelegate.swift @@ -0,0 +1,21 @@ +import UIKit + +final class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? + + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + guard let windowScene = (scene as? UIWindowScene) else { + return + } + + let window = UIWindow(windowScene: windowScene) + let viewController = ViewController() + window.rootViewController = viewController + window.makeKeyAndVisible() + self.window = window + } +} diff --git a/TestApp/Services/.gitkeep b/TestApp/Services/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/TestApp/ViewModels/.gitkeep b/TestApp/ViewModels/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/TestApp/Views/ViewController.swift b/TestApp/Views/ViewController.swift new file mode 100644 index 0000000..231793c --- /dev/null +++ b/TestApp/Views/ViewController.swift @@ -0,0 +1,21 @@ +import UIKit + +final class ViewController: UIViewController { + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .systemBackground + + let label = UILabel() + label.text = "Hello, TestApp!" + label.font = .preferredFont(forTextStyle: .largeTitle) + label.textAlignment = .center + label.translatesAutoresizingMaskIntoConstraints = false + + view.addSubview(label) + + NSLayoutConstraint.activate([ + label.centerXAnchor.constraint(equalTo: view.centerXAnchor), + label.centerYAnchor.constraint(equalTo: view.centerYAnchor), + ]) + } +} diff --git a/TestAppTests/TestAppTests.swift b/TestAppTests/TestAppTests.swift new file mode 100644 index 0000000..d8b6d2b --- /dev/null +++ b/TestAppTests/TestAppTests.swift @@ -0,0 +1,9 @@ +@testable import TestApp +import Testing + +struct TestAppTests { + @Test + func exampleTest() { + #expect(true) + } +} diff --git a/TestAppUITests/TestAppUITests.swift b/TestAppUITests/TestAppUITests.swift new file mode 100644 index 0000000..b6c3160 --- /dev/null +++ b/TestAppUITests/TestAppUITests.swift @@ -0,0 +1,14 @@ +import XCTest + +@MainActor +final class TestAppUITests: XCTestCase { + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testExample() { + let app = XCUIApplication() + app.launch() + XCTAssertTrue(app.exists) + } +} diff --git a/project.yml b/project.yml index 4a90fe3..794f3ae 100644 --- a/project.yml +++ b/project.yml @@ -1,9 +1,9 @@ -name: {{PROJECT_NAME}} +name: TestApp options: - bundleIdPrefix: {{BUNDLE_ID_ROOT}} + bundleIdPrefix: com.yourcompany developmentLanguage: en deploymentTarget: - iOS: "{{DEPLOYMENT_TARGET}}" + iOS: "18.0" xcodeVersion: "15.0" generateEmptyDirectories: true @@ -17,7 +17,7 @@ options: settings: base: # Swift version - SWIFT_VERSION: "{{SWIFT_VERSION}}" + SWIFT_VERSION: "6.2" # Development settings DEVELOPMENT_TEAM: "" CODE_SIGN_IDENTITY: "Apple Development" @@ -37,18 +37,18 @@ settings: ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME: AccentColor targets: - {{PROJECT_NAME}}: + TestApp: type: application platform: iOS - deploymentTarget: "{{DEPLOYMENT_TARGET}}" + deploymentTarget: "18.0" sources: - - {{PROJECT_NAME}} + - TestApp resources: - - {{PROJECT_NAME}}/Resources + - TestApp/Resources info: - path: {{PROJECT_NAME}}/Info.plist + path: TestApp/Info.plist properties: - CFBundleDisplayName: {{PROJECT_NAME}} + CFBundleDisplayName: TestApp CFBundleVersion: "1" CFBundleShortVersionString: "1.0" UILaunchStoryboardName: LaunchScreen @@ -71,41 +71,41 @@ targets: - UISceneConfigurationName: Default Configuration UISceneDelegateClassName: $(PRODUCT_MODULE_NAME).SceneDelegate - {{PROJECT_NAME}}Tests: + TestAppTests: type: bundle.unit-test platform: iOS - deploymentTarget: "{{DEPLOYMENT_TARGET}}" + deploymentTarget: "18.0" sources: - - {{PROJECT_NAME}}Tests + - TestAppTests dependencies: - - target: {{PROJECT_NAME}} + - target: TestApp - {{PROJECT_NAME}}UITests: + TestAppUITests: type: bundle.ui-testing platform: iOS - deploymentTarget: "{{DEPLOYMENT_TARGET}}" + deploymentTarget: "18.0" sources: - - {{PROJECT_NAME}}UITests + - TestAppUITests dependencies: - - target: {{PROJECT_NAME}} + - target: TestApp schemes: - {{PROJECT_NAME}}: + TestApp: build: targets: - {{PROJECT_NAME}}: all - {{PROJECT_NAME}}Tests: [test] - {{PROJECT_NAME}}UITests: [test] + TestApp: all + TestAppTests: [test] + TestAppUITests: [test] run: config: Debug test: config: Debug gatherCoverageData: true coverageTargets: - - {{PROJECT_NAME}} + - TestApp targets: - - {{PROJECT_NAME}}Tests - - {{PROJECT_NAME}}UITests + - TestAppTests + - TestAppUITests profile: config: Release analyze: diff --git a/scripts/setup.sh b/scripts/setup.sh index f6de16b..b8d92c4 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -12,7 +12,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/_helpers.sh" # Default values PROJECT_NAME="" -DEPLOYMENT_TARGET="18.0" +DEPLOYMENT_TARGET="26.0" SWIFT_VERSION="6.2" PROJECT_TYPE="private" BUNDLE_ID_ROOT="com.yourcompany" @@ -417,7 +417,7 @@ class ViewController: UIViewController { NSLayoutConstraint.activate([ label.centerXAnchor.constraint(equalTo: view.centerXAnchor), - label.centerYAnchor.constraint(equalTo: view.centerYAnchor), + label.centerYAnchor.constraint(equalTo: view.centerYAnchor) ]) } } @@ -467,6 +467,7 @@ EOF cat > "${PROJECT_NAME}UITests/${PROJECT_NAME}UITests.swift" < simulator.yml <