From 0d7096f06d84c872a9faa36db1799df7902aceea Mon Sep 17 00:00:00 2001 From: Nicholas Hart Date: Fri, 26 Sep 2025 12:36:22 -0700 Subject: [PATCH] initial project setup improvements create a Localizable.xcstrings file, and configure its language (default: en) create an Asset catalog, including placeholders for AppIcon and AccentColor detect if project exists in a git repository and offer to initialize it (default branch: main) create an initial commit for the generated project include .gitignore in the dev-sync script updated GitHub PR workflow to skip the CI if the project hasn't been initialized --- .github/workflows/pr-validation.yml | 70 ++++++-- scripts/dev-sync.sh | 4 +- scripts/setup.sh | 252 +++++++++++++++++++++++++++- 3 files changed, 304 insertions(+), 22 deletions(-) diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index b536d80..dcdb1a5 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -34,12 +34,39 @@ jobs: echo "๐Ÿ“ฑ Detected generated project repository" fi + - name: Check if project is configured + id: check_configured + run: | + if [[ ! -f "project.yml" ]]; then + # Count commits (if this is a fresh template with minimal history, skip CI) + COMMIT_COUNT=$(git rev-list --count HEAD 2>/dev/null || echo "0") + echo "Commit count: $COMMIT_COUNT" + + if [[ $COMMIT_COUNT -le 2 ]]; then + echo "โญ๏ธ Skipping CI - this appears to be an unconfigured template repository" + echo "๐Ÿ‘‰ Run './scripts/setup.sh' to configure your project, then commit to enable CI" + echo "configured=false" >> $GITHUB_OUTPUT + echo "should_skip=true" >> $GITHUB_OUTPUT + else + echo "โŒ Missing project.yml but git history exists - please run './scripts/setup.sh'" + echo "configured=false" >> $GITHUB_OUTPUT + echo "should_skip=false" >> $GITHUB_OUTPUT + exit 1 + fi + else + echo "โœ… Project appears to be configured (project.yml found)" + echo "configured=true" >> $GITHUB_OUTPUT + echo "should_skip=false" >> $GITHUB_OUTPUT + fi + - name: Set up Xcode + if: steps.check_configured.outputs.should_skip != 'true' uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: "16.4" - name: Cache Homebrew packages + if: steps.check_configured.outputs.should_skip != 'true' uses: actions/cache@v4 with: path: | @@ -50,6 +77,7 @@ jobs: ${{ runner.os }}-homebrew- - name: Install dependencies + if: steps.check_configured.outputs.should_skip != 'true' run: | # Install Homebrew dependencies brew bundle install --file=./Brewfile @@ -63,7 +91,7 @@ jobs: # Template Repository: Generate test project for E2E validation - name: Generate test project - if: steps.detect.outputs.repo_type == 'template' + if: steps.detect.outputs.repo_type == 'template' && steps.check_configured.outputs.should_skip != 'true' run: | echo "๐Ÿ—๏ธ Generating test project for E2E validation..." echo "Working directory: $(pwd)" @@ -105,7 +133,7 @@ jobs: # Template Repository: Validate generated project - name: Run E2E validation on generated project - if: steps.detect.outputs.repo_type == 'template' + if: steps.detect.outputs.repo_type == 'template' && steps.check_configured.outputs.should_skip != 'true' run: | echo "๐Ÿ” Running end-to-end validation on generated project..." cd ci-test-project @@ -118,7 +146,7 @@ jobs: # Generated Project: Normal validation continues below - name: Validate project configuration - if: steps.detect.outputs.repo_type == 'generated' + if: steps.detect.outputs.repo_type == 'generated' && steps.check_configured.outputs.should_skip != 'true' run: | # Check if project.yml exists and is valid if [ -f "project.yml" ]; then @@ -139,7 +167,7 @@ jobs: fi - name: Generate Xcode project - if: steps.detect.outputs.repo_type == 'generated' + if: steps.detect.outputs.repo_type == 'generated' && steps.check_configured.outputs.should_skip != 'true' run: | echo "Generating Xcode project..." xcodegen generate @@ -153,7 +181,7 @@ jobs: fi - name: SwiftLint - if: steps.detect.outputs.repo_type == 'generated' + if: steps.detect.outputs.repo_type == 'generated' && steps.check_configured.outputs.should_skip != 'true' run: | echo "Running SwiftLint..." @@ -168,7 +196,7 @@ jobs: fi - name: SwiftFormat check - if: steps.detect.outputs.repo_type == 'generated' + if: steps.detect.outputs.repo_type == 'generated' && steps.check_configured.outputs.should_skip != 'true' run: | echo "Checking SwiftFormat..." @@ -183,7 +211,7 @@ jobs: fi - name: Build project - if: steps.detect.outputs.repo_type == 'generated' + if: steps.detect.outputs.repo_type == 'generated' && steps.check_configured.outputs.should_skip != 'true' run: | echo "Building project..." @@ -216,7 +244,7 @@ jobs: echo "โœ“ Build completed successfully" - name: Run unit tests - if: steps.detect.outputs.repo_type == 'generated' + if: steps.detect.outputs.repo_type == 'generated' && steps.check_configured.outputs.should_skip != 'true' run: | PROJECT_NAME=$(yq eval '.name' project.yml) @@ -256,7 +284,7 @@ jobs: # UI Tests (Optional - disabled by default) # To enable UI tests in CI, set RUN_UI_TESTS: true in the env section above - name: Run UI tests - if: steps.detect.outputs.repo_type == 'generated' && env.RUN_UI_TESTS == 'true' + if: steps.detect.outputs.repo_type == 'generated' && env.RUN_UI_TESTS == 'true' && steps.check_configured.outputs.should_skip != 'true' run: | PROJECT_NAME=$(yq eval '.name' project.yml) @@ -298,7 +326,7 @@ jobs: fi - name: Validate scripts - if: steps.detect.outputs.repo_type == 'generated' + if: steps.detect.outputs.repo_type == 'generated' && steps.check_configured.outputs.should_skip != 'true' run: | echo "Validating helper scripts..." @@ -325,7 +353,7 @@ jobs: echo "โœ“ All scripts validated" - name: Check configuration files - if: steps.detect.outputs.repo_type == 'generated' + if: steps.detect.outputs.repo_type == 'generated' && steps.check_configured.outputs.should_skip != 'true' run: | echo "Validating configuration files..." @@ -354,7 +382,7 @@ jobs: echo "โœ“ Configuration files validated" - name: Summary - if: steps.detect.outputs.repo_type == 'generated' + if: steps.detect.outputs.repo_type == 'generated' && steps.check_configured.outputs.should_skip != 'true' run: | echo "๐ŸŽ‰ PR Validation Complete!" echo "" @@ -367,8 +395,24 @@ jobs: # Template Repository: Cleanup - name: Cleanup test project - if: always() && steps.detect.outputs.repo_type == 'template' + if: always() && steps.detect.outputs.repo_type == 'template' && steps.check_configured.outputs.should_skip != 'true' run: | echo "๐Ÿงน Cleaning up test project..." rm -rf ci-test-project echo "โœ“ Cleanup completed" + + # Show helpful message when CI is skipped + - name: Configuration needed + if: steps.check_configured.outputs.should_skip == 'true' + run: | + echo "๐Ÿš€ Welcome to your new iOS project!" + echo "" + echo "This appears to be a fresh project created from the SwiftProjectTemplate." + echo "To enable CI and configure your project, please run:" + echo "" + echo " ./scripts/setup.sh" + echo "" + echo "After running setup and committing your changes, CI will automatically" + echo "run on future pushes and pull requests." + echo "" + echo "๐Ÿ“– Check the README.md for detailed setup instructions!" diff --git a/scripts/dev-sync.sh b/scripts/dev-sync.sh index 856b249..bfb9e98 100755 --- a/scripts/dev-sync.sh +++ b/scripts/dev-sync.sh @@ -141,7 +141,7 @@ sync_to_project() { done # Sync individual files - for file in Brewfile .swift-version .markdownlint.json; do + for file in Brewfile .gitignore .swift-version .markdownlint.json; do if [[ -f "$file" ]]; then "${rsync_cmd[@]}" "$file" "$TARGET_PROJECT/" fi @@ -164,7 +164,7 @@ sync_from_project() { done # Sync individual files (reverse direction) - for file in Brewfile .swift-version .markdownlint.json; do + for file in Brewfile .gitignore .swift-version .markdownlint.json; do if [[ -f "$TARGET_PROJECT/$file" ]]; then "${rsync_cmd[@]}" "$TARGET_PROJECT/$file" ./ fi diff --git a/scripts/setup.sh b/scripts/setup.sh index 52a01e3..cc52cf5 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -17,7 +17,10 @@ SWIFT_VERSION="6.2" PROJECT_TYPE="private" # private or public BUNDLE_ID_ROOT="com.yourcompany" TEST_FRAMEWORK="swift-testing" # swift-testing or xctest +SOURCE_LANGUAGE="en" USE_GIT_HOOKS=true +CREATE_INITIAL_COMMIT=true +GIT_REPOSITORY_AVAILABLE=false FORCE_OVERWRITE=false SKIP_BREW=false @@ -28,7 +31,10 @@ CLI_SWIFT_VERSION_SET=false CLI_VISIBILITY_SET=false CLI_BUNDLE_ID_ROOT_SET=false CLI_TEST_FRAMEWORK_SET=false +CLI_SOURCE_LANGUAGE_SET=false CLI_GIT_HOOKS_SET=false +CLI_COMMIT_SET=false +CLI_GIT_INIT_SET=false show_help() { cat < Test framework (default: $TEST_FRAMEWORK) Options: swift-testing, xctest + --source-language Source language for localization (default: $SOURCE_LANGUAGE) + Format: ISO 639-1 code (e.g., en, es, fr, de) --git-hooks Enable git pre-commit hooks (default) --no-git-hooks Disable git pre-commit hooks + --commit Create initial git commit after setup (default) + --no-commit Skip initial git commit after setup --public Make this a public project (includes LICENSE in README) --private Make this a private project (default) --force Overwrite existing files without prompting @@ -66,8 +76,10 @@ EXAMPLES: $0 --project-name "MyApp" --public # Mostly CLI, minimal prompts $0 --project-name "MyApp" \\ --deployment-target "26.0" \\ - --swift-version "5.9" \\ + --swift-version "6.2" \\ + --source-language "en" \\ --public --force # Full CLI mode + $0 --project-name "MyApp" --no-commit # Skip initial git commit VALIDATION: - Project name must be a valid Swift identifier (alphanumeric, starts with letter) @@ -80,9 +92,13 @@ WORKFLOW: 3. Install Homebrew dependencies (unless --skip-brew) 4. Generate all configuration files from templates 5. Create MVVM folder structure - 6. Generate Xcode project with XcodeGen - 7. Set up git pre-commit hooks - 8. Display next steps + 6. Create Resources with localization and asset catalogs + 7. Generate Xcode project with XcodeGen + 8. Configure simulators with intelligent defaults + 9. Verify or create git repository (offers to run 'git init' if not in a repository) + 10. Set up git pre-commit hooks (if git repository available) + 11. Create initial git commit with project details (unless --no-commit) + 12. Display next steps EOF } @@ -139,6 +155,15 @@ parse_arguments() { CLI_TEST_FRAMEWORK_SET=true shift 2 ;; + --source-language) + if [[ -z "${2:-}" ]]; then + log_error "Option --source-language requires a value" + exit 1 + fi + SOURCE_LANGUAGE="$2" + CLI_SOURCE_LANGUAGE_SET=true + shift 2 + ;; --git-hooks) USE_GIT_HOOKS=true CLI_GIT_HOOKS_SET=true @@ -149,6 +174,16 @@ parse_arguments() { CLI_GIT_HOOKS_SET=true shift ;; + --commit) + CREATE_INITIAL_COMMIT=true + CLI_COMMIT_SET=true + shift + ;; + --no-commit) + CREATE_INITIAL_COMMIT=false + CLI_COMMIT_SET=true + shift + ;; --public) if [[ "$PROJECT_TYPE" == "private" ]]; then PROJECT_TYPE="public" @@ -313,6 +348,15 @@ prompt_for_missing_info() { done fi + # Only prompt for source language if not provided via CLI + if [[ "$CLI_SOURCE_LANGUAGE_SET" == false ]]; then + echo + read -p "Source language for localization (default: $SOURCE_LANGUAGE): " input_source_language + if [[ -n "$input_source_language" ]]; then + SOURCE_LANGUAGE="$input_source_language" + fi + fi + # Only prompt for git hooks if not provided via CLI if [[ "$CLI_GIT_HOOKS_SET" == false ]]; then echo @@ -338,6 +382,33 @@ prompt_for_missing_info() { esac done fi + + # Handle git repository initialization if not already in one and git features are needed + if ! is_git_repo && ($USE_GIT_HOOKS || $CREATE_INITIAL_COMMIT) && [[ "$CLI_GIT_INIT_SET" == false ]]; then + echo + log_warning "Not in a git repository" + while true; do + read -p "Would you like to initialize a git repository? (Y/n): " yn + case $yn in + [Yy]*|"") + log_info "Will initialize git repository during setup" + GIT_REPOSITORY_AVAILABLE=true + break + ;; + [Nn]*) + log_info "Git repository will not be initialized" + log_info "Git hooks and initial commit will be skipped" + GIT_REPOSITORY_AVAILABLE=false + break + ;; + *) echo "Please answer yes or no.";; + esac + done + elif is_git_repo; then + GIT_REPOSITORY_AVAILABLE=true + else + GIT_REPOSITORY_AVAILABLE=false + fi } detect_architecture() { @@ -480,7 +551,6 @@ create_project_structure() { "$PROJECT_NAME/Services" "$PROJECT_NAME/Extensions" "$PROJECT_NAME/Helpers" - "$PROJECT_NAME/Resources" ) # Create test directories @@ -784,6 +854,101 @@ EOF fi } +create_resources_structure() { + log_info "Creating Resources with localization and asset catalogs..." + + # Create Resources directory if it doesn't exist + mkdir -p "$PROJECT_NAME/Resources" + + # Create Localizable.xcstrings + local localizable_file="$PROJECT_NAME/Resources/Localizable.xcstrings" + if [[ ! -f "$localizable_file" || "$FORCE_OVERWRITE" == true ]]; then + cat > "$localizable_file" < "$assets_dir/Contents.json" < "$appicon_dir/Contents.json" < "$accentcolor_dir/Contents.json" </dev/null; then + log_success "Git repository initialized with 'main' branch" + elif git init 2>/dev/null; then + # Older git version - rename default branch to main + git checkout -b main 2>/dev/null || true + log_success "Git repository initialized and switched to 'main' branch" + else + log_error "Failed to initialize git repository" + GIT_REPOSITORY_AVAILABLE=false + fi + fi +} + setup_git_hooks() { if ! $USE_GIT_HOOKS; then log_info "Git hooks disabled, skipping git hook setup" return fi - if ! is_git_repo; then - log_warning "Not in a git repository, skipping git hook setup" + if ! $GIT_REPOSITORY_AVAILABLE; then + log_info "Git repository not available, skipping git hook setup" return fi @@ -910,12 +1099,57 @@ EOF log_info "Hook will run formatting, linting, and build checks before commits" } +create_initial_commit() { + if ! $CREATE_INITIAL_COMMIT; then + log_info "Initial commit disabled, skipping git commit" + return + fi + + if ! $GIT_REPOSITORY_AVAILABLE; then + log_info "Git repository not available, skipping initial commit" + return + fi + + log_info "Creating initial git commit..." + + # Add all generated files + git add . 2>/dev/null + + # Check if there are files to commit + if git diff --cached --quiet; then + log_warning "No changes to commit" + return + fi + + # Create initial commit with descriptive message + local commit_message="Initial project setup + +Generated iOS project using SwiftProjectTemplate with: +- Project: $PROJECT_NAME +- Bundle ID: ${BUNDLE_ID_ROOT}.${PROJECT_NAME} +- iOS Deployment Target: $DEPLOYMENT_TARGET +- Swift Version: $SWIFT_VERSION +- Test Framework: $TEST_FRAMEWORK +- Source Language: $SOURCE_LANGUAGE" + + if git commit -m "$commit_message" 2>/dev/null; then + log_success "Initial commit created successfully" + log_info "You can view the commit with: git log --oneline -1" + else + log_error "Failed to create initial commit" + log_info "You may need to configure git user settings:" + echo " git config --global user.name \"Your Name\"" + echo " git config --global user.email \"your.email@example.com\"" + fi +} + display_next_steps() { log_success "๐ŸŽ‰ Project setup complete!" echo echo "Project: $PROJECT_NAME" echo "iOS Deployment Target: $DEPLOYMENT_TARGET" echo "Swift Version: $SWIFT_VERSION" + echo "Source Language: $SOURCE_LANGUAGE" echo "Project Type: $PROJECT_TYPE" echo log_info "Next steps:" @@ -954,6 +1188,7 @@ main() { log_info " Deployment Target: iOS $DEPLOYMENT_TARGET" log_info " Swift Version: $SWIFT_VERSION" log_info " Test Framework: $TEST_FRAMEWORK" + log_info " Source Language: $SOURCE_LANGUAGE" log_info " Git Hooks: $(if $USE_GIT_HOOKS; then echo "enabled"; else echo "disabled"; fi)" log_info " Project Type: $PROJECT_TYPE" echo @@ -961,9 +1196,12 @@ main() { install_dependencies generate_template_files create_project_structure + create_resources_structure generate_xcode_project setup_default_simulators + verify_or_create_git_repository setup_git_hooks + create_initial_commit display_next_steps }