diff --git a/.github/actions/validate-version/action.yml b/.github/actions/validate-version/action.yml new file mode 100644 index 0000000..4200b0e --- /dev/null +++ b/.github/actions/validate-version/action.yml @@ -0,0 +1,72 @@ +name: Validate Version +description: Extract and validate version from VERSION file against latest git tag + +inputs: + check-tag-exists: + description: Fail if the tag already exists + required: false + default: 'false' + +outputs: + version: + description: The extracted version from VERSION file + value: ${{ steps.extract.outputs.version }} + +runs: + using: composite + steps: + - name: Extract version from VERSION file + id: extract + shell: bash + run: | + VERSION=$(cat VERSION | tr -d '\n') + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Extracted version: $VERSION" + + - name: Get latest tag + id: latest_tag + shell: bash + run: | + LATEST_TAG=$(git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+' | head -n1 || echo "") + if [ -z "$LATEST_TAG" ]; then + echo "tag=" >> $GITHUB_OUTPUT + echo "No existing tags found" + else + echo "tag=${LATEST_TAG#v}" >> $GITHUB_OUTPUT + echo "Latest tag: $LATEST_TAG" + fi + + - name: Check if tag exists + if: inputs.check-tag-exists == 'true' + shell: bash + run: | + VERSION="${{ steps.extract.outputs.version }}" + if git rev-parse "v$VERSION" >/dev/null 2>&1; then + echo "Error: Tag v$VERSION already exists" + exit 1 + fi + + - name: Validate version bump + shell: bash + run: | + VERSION="${{ steps.extract.outputs.version }}" + LATEST="${{ steps.latest_tag.outputs.tag }}" + + echo "Current version: $VERSION" + echo "Latest tag: $LATEST" + + if [ -z "$LATEST" ]; then + echo "No existing tags. Version $VERSION is valid." + exit 0 + fi + + # Check if version is greater than latest tag using sort -V + HIGHEST=$(printf '%s\n%s' "$VERSION" "$LATEST" | sort -V | tail -n1) + + if [ "$HIGHEST" = "$VERSION" ] && [ "$VERSION" != "$LATEST" ]; then + echo "Version $VERSION is greater than $LATEST" + exit 0 + else + echo "Error: Version $VERSION must be greater than latest tag $LATEST" + exit 1 + fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4c7ed16 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,41 @@ +name: CI + +on: + pull_request: + branches: + - dev + - main + +jobs: + build-and-test: + name: Build and Test + runs-on: macos-26 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build + run: swift build + + - name: Run tests + run: swift test + + - name: Validate version (main) + if: github.base_ref == 'main' + uses: ./.github/actions/validate-version + with: + check-tag-exists: 'true' + + - name: Validate version (dev) + id: version-check + if: github.base_ref == 'dev' + uses: ./.github/actions/validate-version + with: + check-tag-exists: 'true' + continue-on-error: true + + - name: Version validation warning + if: github.base_ref == 'dev' && steps.version-check.outcome == 'failure' + run: echo "::warning::Version validation failed. Remember to bump the version before merging to main." diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..20c07e5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,33 @@ +name: Release + +on: + push: + branches: + - main + +jobs: + release: + name: Build and Release + runs-on: macos-26 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Validate version + id: version + uses: ./.github/actions/validate-version + with: + check-tag-exists: 'true' + + - name: Build release binary + run: swift build -c release + + - name: Create release + uses: diogot/gh-actions-workflows/actions/create-release@main + with: + tag: v${{ steps.version.outputs.version }} + title: v${{ steps.version.outputs.version }} + files: .build/release/swift-outdated + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/Package.swift b/Package.swift index 61c9cf8..51aa15d 100644 --- a/Package.swift +++ b/Package.swift @@ -15,11 +15,18 @@ let package = Package( .package(url: "https://github.com/tuist/XcodeProj.git", from: "8.0.0") ], targets: [ + .plugin( + name: "BuildVersionPlugin", + capability: .buildTool() + ), .executableTarget( name: "swift-outdated", dependencies: [ "SwiftOutdatedCore", .product(name: "ArgumentParser", package: "swift-argument-parser") + ], + plugins: [ + .plugin(name: "BuildVersionPlugin") ] ), .target( diff --git a/Plugins/BuildVersionPlugin/plugin.swift b/Plugins/BuildVersionPlugin/plugin.swift new file mode 100644 index 0000000..c995912 --- /dev/null +++ b/Plugins/BuildVersionPlugin/plugin.swift @@ -0,0 +1,30 @@ +import PackagePlugin +import Foundation + +@main +struct BuildVersionPlugin: BuildToolPlugin { + func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { + let versionFile = context.package.directoryURL.appending(path: "VERSION") + let outputFile = context.pluginWorkDirectoryURL.appending(path: "GeneratedVersion.swift") + + return [ + .prebuildCommand( + displayName: "Generate version from VERSION file", + executable: URL(fileURLWithPath: "/bin/bash"), + arguments: [ + "-c", + """ + VERSION=$(cat "\(versionFile.path)" | tr -d '\\n') + cat > "\(outputFile.path)" << EOF + // Auto-generated by BuildVersionPlugin - do not edit + enum GeneratedVersion { + static let version = "$VERSION" + } + EOF + """ + ], + outputFilesDirectory: context.pluginWorkDirectoryURL + ) + ] + } +} diff --git a/Sources/swift-outdated/SwiftOutdated.swift b/Sources/swift-outdated/SwiftOutdated.swift index 475e038..1d710de 100644 --- a/Sources/swift-outdated/SwiftOutdated.swift +++ b/Sources/swift-outdated/SwiftOutdated.swift @@ -13,7 +13,7 @@ struct SwiftOutdated: AsyncParsableCommand { By default, searches for Package.resolved in the current directory or within Xcode project/workspace directories. """, - version: "1.0.0" + version: GeneratedVersion.version ) @Flag(name: .long, help: "Output results in JSON format") diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.0