From b25410a204db94e1ccd2379e5ae768d257eda968 Mon Sep 17 00:00:00 2001 From: Jheison Martinez Bolivar Date: Fri, 10 Apr 2026 07:41:15 -0500 Subject: [PATCH 1/3] ci: centralize actions worflow --- .github/workflows/ci.yml | 101 +--------------- .github/workflows/release.yml | 220 ++-------------------------------- Cargo.toml | 2 +- README.md | 17 +-- 4 files changed, 19 insertions(+), 321 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cfbd48e..bb69e3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,103 +1,10 @@ name: CI on: + push: + branches: [main, develop] pull_request: - branches: - - develop - - main - -permissions: - contents: read - pull-requests: write - -env: - CARGO_TERM_COLOR: always jobs: - check: - name: Check, Test & Clippy - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo index - uses: actions/cache@v4 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo build - uses: actions/cache@v4 - with: - path: target - key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} - - - name: Cargo fmt - run: cargo fmt --check - - - name: Cargo build - run: cargo build --release - - - name: Cargo test - run: cargo test - - - name: Cargo clippy - run: cargo clippy --all-targets -- -D warnings - - publish-check: - name: Publish Check (dry-run) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo index - uses: actions/cache@v4 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - - - name: Cargo publish dry-run - run: cargo publish --dry-run - - main-pr-checks: - name: Main PR Requirements - if: github.base_ref == 'main' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Check version bump - run: | - CURRENT_VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/') - echo "Versión en Cargo.toml: $CURRENT_VERSION" - - LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") - if [ -z "$LAST_TAG" ]; then - echo "✅ Primer release (no hay tags previos)" - else - LAST_VERSION=${LAST_TAG#v} - echo "Última versión en tag: $LAST_VERSION" - if [ "$CURRENT_VERSION" = "$LAST_VERSION" ]; then - echo "❌ Error: La versión en Cargo.toml debe bumpearse en PR a main" - exit 1 - fi - echo "✅ Versión bumpeada correctamente" - fi + rust-ci: + uses: UniverLab/workflows/.github/workflows/rust-ci.yml@main \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4c9f8a5..c77b6c5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,219 +2,15 @@ name: Release on: pull_request: - branches: - - main - types: - - closed - paths: - - Cargo.toml + branches: [main] + types: [closed] + paths: [Cargo.toml] workflow_dispatch: -permissions: - contents: write - -env: - CARGO_TERM_COLOR: always - jobs: - create-tag: - name: Create Release Tag + release: if: github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - outputs: - tag: ${{ steps.version.outputs.tag }} - version: ${{ steps.version.outputs.version }} - created: ${{ steps.create.outputs.created }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Get version from Cargo.toml - id: version - run: | - VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/' | tr -d '\r' | xargs) - TAG="v${VERSION}" - echo "version=${VERSION}" >> $GITHUB_OUTPUT - echo "tag=${TAG}" >> $GITHUB_OUTPUT - - - name: Check if tag already exists - id: check_tag - run: | - TAG=${{ steps.version.outputs.tag }} - if git rev-parse "$TAG" >/dev/null 2>&1; then - echo "exists=true" >> $GITHUB_OUTPUT - echo "Tag $TAG already exists, skipping" - else - echo "exists=false" >> $GITHUB_OUTPUT - fi - - - name: Create and push tag - id: create - run: | - if [ "${{ steps.check_tag.outputs.exists }}" = "true" ]; then - echo "created=false" >> $GITHUB_OUTPUT - echo "Tag already exists — skipping release" - exit 0 - fi - TAG=${{ steps.version.outputs.tag }} - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag -a "$TAG" -m "Release $TAG" - git push origin "$TAG" - echo "created=true" >> $GITHUB_OUTPUT - echo "Tag $TAG created and pushed" - - build: - name: Build ${{ matrix.target }} - needs: create-tag - if: needs.create-tag.outputs.created == 'true' - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - target: x86_64-unknown-linux-musl - os: ubuntu-latest - archive: tar.gz - - target: aarch64-apple-darwin - os: macos-latest - archive: tar.gz - - target: x86_64-apple-darwin - os: macos-latest - archive: tar.gz - - target: x86_64-pc-windows-msvc - os: windows-latest - archive: zip - - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ needs.create-tag.outputs.tag }} - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.target }} - - - name: Install musl tools (Linux) - if: matrix.target == 'x86_64-unknown-linux-musl' - run: sudo apt-get update && sudo apt-get install -y musl-tools - - - name: Build release binary - run: cargo build --release --target ${{ matrix.target }} - - - name: Package (unix) - if: matrix.archive == 'tar.gz' - run: | - cd target/${{ matrix.target }}/release - tar czf "../../../ghscaff-${{ needs.create-tag.outputs.tag }}-${{ matrix.target }}.tar.gz" ghscaff - cd ../../.. - - - name: Package (windows) - if: matrix.archive == 'zip' - shell: pwsh - run: | - cd target/${{ matrix.target }}/release - Compress-Archive -Path ghscaff.exe -DestinationPath "../../../ghscaff-${{ needs.create-tag.outputs.tag }}-${{ matrix.target }}.zip" - cd ../../.. - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: ghscaff-${{ matrix.target }} - path: ghscaff-${{ needs.create-tag.outputs.tag }}-${{ matrix.target }}.* - - github-release: - name: Create GitHub Release - needs: [create-tag, build] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Download all artifacts - uses: actions/download-artifact@v4 - with: - path: artifacts - merge-multiple: true - - - name: Create release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ needs.create-tag.outputs.tag }} - generate_release_notes: true - files: artifacts/* - - approve: - name: Awaiting Manual Approval - needs: github-release - runs-on: ubuntu-latest - environment: - name: production - steps: - - name: Approval granted - run: echo "Release approved for publishing to crates.io" - - ensure-owners: - name: Ensure crates.io owners - needs: create-tag - if: needs.create-tag.outputs.created == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ needs.create-tag.outputs.tag }} - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - - - name: Add crates.io owners (best-effort) - env: - TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - OWNERS: github:univerlab:owners - run: | - set -eu - if [ -z "$TOKEN" ]; then - echo "No cargo token found in CARGO_REGISTRY_TOKEN; skipping owners update" - exit 0 - fi - CRATE_NAME=$(grep '^name = ' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/' | tr -d '\r') - echo "crate=$CRATE_NAME" - for owner in $(echo "$OWNERS" | tr ',' ' '); do - echo "Adding owner: $owner" - if command -v cargo >/dev/null 2>&1; then - cargo owner --token "$TOKEN" --add "$owner" || echo "cargo owner failed for $owner (continuing)" - else - curl -sSf -X PUT -H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \ - -d "{\"users\":[\"$owner\"]}" "https://crates.io/api/v1/crates/$CRATE_NAME/owners" || echo "API add failed for $owner (continuing)" - fi - done - - publish: - name: Publish to crates.io - needs: [create-tag, approve, ensure-owners] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ needs.create-tag.outputs.tag }} - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - - - name: Check if stable release - id: version_check - run: | - VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/' | xargs) - if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "is_stable=true" >> $GITHUB_OUTPUT - echo "Version $VERSION is stable — will publish" - else - echo "is_stable=false" >> $GITHUB_OUTPUT - echo "Version $VERSION is prerelease/dev — skipping publish" - fi - - - name: Cargo publish - if: steps.version_check.outputs.is_stable == 'true' - env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: cargo publish --allow-dirty + uses: UniverLab/workflows/.github/workflows/rust-release.yml@main + with: + binary-name: ghscaff + secrets: inherit diff --git a/Cargo.toml b/Cargo.toml index e74990b..e7c67ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ghscaff" -version = "0.3.1" +version = "0.3.2" edition = "2021" description = "Interactive CLI wizard for creating and configuring GitHub repositories" license = "MIT" diff --git a/README.md b/README.md index bd41b04..3c0061f 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,12 @@ ░░░░░░ ``` -[![Crate](https://img.shields.io/crates/v/ghscaff.svg)](https://crates.io/crates/ghscaff) -[![CI](https://github.com/UniverLab/ghscaff/actions/workflows/ci.yml/badge.svg)](https://github.com/UniverLab/ghscaff/actions/workflows/ci.yml) -[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +

+ CI + Crates.io + Status + License +

Interactive CLI wizard for creating and configuring GitHub repositories. One binary, zero runtime dependencies. @@ -257,12 +260,4 @@ MIT — see [LICENSE](LICENSE) for details. --- -## Support - -- 📖 [GitHub Issues](https://github.com/UniverLab/ghscaff/issues) — Report bugs or request features -- 💬 [Discussions](https://github.com/UniverLab/ghscaff/discussions) — Ask questions -- 🐦 Twitter: [@JheisonMB](https://twitter.com/JheisonMB) - ---- - Made with ❤️ by [JheisonMB](https://github.com/JheisonMB) and [UniverLab](https://github.com/UniverLab) From 1a645c76ffabc56a3a47c77fb05bf3fd868508a8 Mon Sep 17 00:00:00 2001 From: Jheison Martinez Bolivar Date: Fri, 10 Apr 2026 07:51:36 -0500 Subject: [PATCH 2/3] fix: adjust branch restrictions --- Cargo.lock | 2 +- src/github/branches.rs | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 296c3c1..820630e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -561,7 +561,7 @@ dependencies = [ [[package]] name = "ghscaff" -version = "0.3.1" +version = "0.3.2" dependencies = [ "anyhow", "base64", diff --git a/src/github/branches.rs b/src/github/branches.rs index 0725bfc..c02c256 100644 --- a/src/github/branches.rs +++ b/src/github/branches.rs @@ -57,7 +57,7 @@ pub fn apply_branch_protection( required_status_checks: RequiredChecks<'a>, enforce_admins: bool, required_pull_request_reviews: Reviews, - restrictions: Option<()>, + restrictions: Restrictions, allow_force_pushes: bool, } #[derive(Serialize)] @@ -70,6 +70,12 @@ pub fn apply_branch_protection( dismiss_stale_reviews: bool, required_approving_review_count: u8, } + #[derive(Serialize)] + struct Restrictions { + users: Vec, + teams: Vec, + apps: Vec, + } let body = Body { required_status_checks: RequiredChecks { @@ -81,7 +87,11 @@ pub fn apply_branch_protection( dismiss_stale_reviews: true, required_approving_review_count: 1, }, - restrictions: None, + restrictions: Restrictions { + users: vec![], + teams: vec![], + apps: vec![], + }, allow_force_pushes: false, }; let _: serde_json::Value = client.put( From 877df14d3f48cd729b785c54d7015d529304c728 Mon Sep 17 00:00:00 2001 From: Jheison Martinez Bolivar Date: Fri, 10 Apr 2026 08:20:13 -0500 Subject: [PATCH 3/3] feat: update branch protection with ghscaff apply --- src/apply.rs | 36 ++++++++++++++++++------------------ src/wizard.rs | 16 ++++++++++++++-- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/apply.rs b/src/apply.rs index 193e076..d8d23c9 100644 --- a/src/apply.rs +++ b/src/apply.rs @@ -326,26 +326,26 @@ pub fn run_apply(repo_arg: Option<&str>, dry_run: bool) -> Result<()> { sync_labels(&client, &owner, &repo_name, false)?; println!(" ✓ Labels synced"); - // 2. Branch protection (skip if already enabled, warn on failure) - if !ctx.branch_protection_enabled { - match crate::github::branches::apply_branch_protection( - &client, &owner, &repo_name, "main", "CI", - ) { - Ok(()) => println!(" ✓ Branch protection applied"), - Err(e) => { - let msg = format!("{e:#}"); - if msg.contains("403") { - println!(" ⚠ Branch protection skipped (403 Forbidden)"); - println!(" Possible causes:"); - println!(" • Private repo on a free org plan (requires GitHub Team)"); - println!(" • Token not authorized for this organization"); - } else { - println!(" ⚠ Branch protection failed: {msg}"); - } + // 2. Branch protection (always apply to ensure correct config) + match crate::github::branches::apply_branch_protection( + &client, + &owner, + &repo_name, + "main", + "rust-ci / Format, Lint & Test", + ) { + Ok(()) => println!(" ✓ Branch protection applied"), + Err(e) => { + let msg = format!("{e:#}"); + if msg.contains("403") { + println!(" ⚠ Branch protection skipped (403 Forbidden)"); + println!(" Possible causes:"); + println!(" • Private repo on a free org plan (requires GitHub Team)"); + println!(" • Token not authorized for this organization"); + } else { + println!(" ⚠ Branch protection failed: {msg}"); } } - } else { - println!(" ✓ Branch protection (already enabled)"); } // 3. Develop branch (if needed) diff --git a/src/wizard.rs b/src/wizard.rs index ce81d6c..e4644bf 100644 --- a/src/wizard.rs +++ b/src/wizard.rs @@ -315,13 +315,25 @@ fn execute(client: &GithubClient, c: &WizardConfig, dry_run: bool, token: &str) step!( &format!("apply branch protection ({})", c.default_branch), { - branches::apply_branch_protection(client, owner, name, &c.default_branch, "build")?; + branches::apply_branch_protection( + client, + owner, + name, + &c.default_branch, + "rust-ci / Format, Lint & Test", + )?; Ok::<(), anyhow::Error>(()) } ); if c.create_develop { step!("apply branch protection (develop)", { - branches::apply_branch_protection(client, owner, name, "develop", "build")?; + branches::apply_branch_protection( + client, + owner, + name, + "develop", + "rust-ci / Format, Lint & Test", + )?; Ok::<(), anyhow::Error>(()) }); }