diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e3f6a94 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,174 @@ +name: Release + +on: + workflow_dispatch: + +jobs: + get-plugins: + runs-on: ubuntu-latest + outputs: + plugins: ${{ steps.set-matrix.outputs.plugins_json }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Get changed plugins + id: set-matrix + env: + DIFF_BASE: ${{ github.base_ref }} + run: | + plugins=$(jq -c '[.plugins[].name]' plugins.json) + echo "Changed plugins: $plugins" + echo "plugins_json=$plugins" >> $GITHUB_OUTPUT + + create-plugin-release: + needs: get-plugins + runs-on: ubuntu-latest + outputs: + plugin_name: ${{ matrix.plugin }} + upload_url: ${{ steps.create_release.outputs.upload_url }} + permissions: + contents: write + pull-requests: write + strategy: + matrix: + plugin: ${{ fromJSON(needs.get-plugins.outputs.plugins) }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Fetch last tag + run: | + git fetch --tags + + LATEST_TAG=$(git describe --abbrev=0 --tags --match "${{ matrix.plugin }}/*" 2>/dev/null || true) + if [ -z "$LATEST_TAG" ]; then + LATEST_TAG=$(git rev-list --max-parents=0 HEAD) + LATEST_VERSION="0.0.0" + echo "Latest tag not found. The first commit of the repository will be used to search for plugin changelogs: $LATEST_TAG" + + echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV + echo "LATEST_VERSION=$LATEST_VERSION" >> $GITHUB_ENV + exit 0 + fi + echo $LATEST_TAG + + LATEST_VERSION=$(echo "$LATEST_TAG" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') + if [ -n "$LATEST_VERSION" ]; then + LATEST_VERSION="${LATEST_VERSION}" + else + echo "Failed to extract version from tag: $LATEST_TAG" >&2; exit 1; + fi + + echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV + echo "LATEST_VERSION=$LATEST_VERSION" >> $GITHUB_ENV + - name: Calculate new version + id: new-version + if: env.LATEST_TAG != '' + run: | + LATEST_TAG="${{ env.LATEST_TAG }}" + LATEST_VERSION="${{ env.LATEST_VERSION }}" + + set +e + ALL_MESSAGES=$(git log "$LATEST_TAG"..HEAD --format=%B -- ${{ matrix.plugin }} | grep -E "\[(patch|minor|major)\] (feat|fix|refactor): .*") + set -e + + if [ -z "$ALL_MESSAGES" ]; then + echo "ALL_MESSAGES is empty. The plugin has not had any changes." >&2; + exit 0 + fi + + echo "ALL_MESSAGES=$ALL_MESSAGES" + + chmod +x ./scripts/helpers/incr_semver.bash + + new_patch="" + if [[ "$ALL_MESSAGES" == *"[major]"* ]]; then + new_patch=$(./scripts/helpers/incr_semver.bash "$LATEST_VERSION" major) + echo "NEW_BUMP_TYPE=major" >> $GITHUB_ENV + elif [[ "$ALL_MESSAGES" == *"[minor]"* ]]; then + new_patch=$(./scripts/helpers/incr_semver.bash "$LATEST_VERSION" minor) + echo "NEW_BUMP_TYPE=minor" >> $GITHUB_ENV + else + new_patch=$(./scripts/helpers/incr_semver.bash "$LATEST_VERSION" patch) + echo "NEW_BUMP_TYPE=patch" >> $GITHUB_ENV + fi + + echo "NEW_VERSION=$new_patch" >> $GITHUB_ENV + + echo "CHANGELOG<> $GITHUB_ENV + echo "$ALL_MESSAGES" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + - name: Create Release + if: env.NEW_VERSION != '' + id: create_release + uses: actions/create-release@v1.1.4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ matrix.plugin }}/${{ env.NEW_VERSION }} + release_name: ${{ matrix.plugin }}/${{ env.NEW_VERSION }} + body: ${{ env.CHANGELOG }} + draft: true + prerelease: false + - name: Write upload_url + env: + plugin_upload_url: ${{ steps.create_release.outputs.upload_url }} + run: echo $plugin_upload_url > plugin_upload_url + - name: Produce Artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.plugin }} + path: plugin_upload_url + + archive-plugin: + needs: + - get-plugins + - create-plugin-release + runs-on: ${{ matrix.os }} + permissions: + contents: write + pull-requests: write + strategy: + matrix: + os: [macos-26, ubuntu-latest] + plugin: ${{ fromJSON(needs.get-plugins.outputs.plugins) }} + steps: + - name: Retrieve Upload Url + uses: actions/download-artifact@v4 + with: + name: ${{ matrix.plugin }} + - name: Get plugin_upload_url + run: | + echo $(cat plugin_upload_url) + echo "PLUGIN_UPLOAD_URL=$(cat plugin_upload_url)" >> $GITHUB_ENV + - uses: actions/checkout@v6 + if: env.PLUGIN_UPLOAD_URL != '' + - uses: jdx/mise-action@v2 + if: env.PLUGIN_UPLOAD_URL != '' + - name: Archive plugin ${{ matrix.plugin }} + if: env.PLUGIN_UPLOAD_URL != '' + run: | + PLUGIN_NAME=${{ matrix.plugin }} + geko plugin archive --path $PLUGIN_NAME + if [[ ${{ matrix.os }} == "ubuntu-latest" ]]; then + ARCH="x86_64" + find "$PLUGIN_NAME" -type f -iname "$PLUGIN_NAME.linux.$ARCH.geko-plugin.zip" -exec cp -v {} "$ARCH.linux-plugin.zip" \; + echo "ARCHIVE_PATH=$ARCH.linux-plugin.zip" >> $GITHUB_ENV + echo "ASSET_NAME=$PLUGIN_NAME.linux.$ARCH.geko-plugin.zip" >> $GITHUB_ENV + else + echo "ARCHIVE_PATH=$PLUGIN_NAME/$PLUGIN_NAME.macos.geko-plugin.zip" >> $GITHUB_ENV + echo "ASSET_NAME=$PLUGIN_NAME.macos.geko-plugin.zip" >> $GITHUB_ENV + fi + - name: Upload release binary + if: env.PLUGIN_UPLOAD_URL != '' + uses: actions/upload-release-asset@v1.0.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ env.PLUGIN_UPLOAD_URL }} + asset_path: ${{ env.ARCHIVE_PATH }} + asset_name: ${{ env.ASSET_NAME }} + asset_content_type: application/json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..52dc3bb --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,45 @@ +name: Test + +on: + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + get-changed-plugins: + runs-on: ubuntu-latest + outputs: + plugins: ${{ steps.set-matrix.outputs.plugins_json }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Get changed plugins + id: set-matrix + env: + DIFF_BASE: ${{ github.base_ref }} + run: | + git fetch origin $DIFF_BASE + chmod +x ./scripts/helpers/changed_plugins.bash + changed=$(./scripts/helpers/changed_plugins.bash) + echo "Changed plugins: $changed" + echo "plugins_json=$changed" >> $GITHUB_OUTPUT + + test-plugins: + needs: get-changed-plugins + runs-on: ${{ matrix.os }} + strategy: + matrix: + plugin: ${{ fromJSON(needs.get-changed-plugins.outputs.plugins) }} + os: [macos-26, ubuntu-latest] + steps: + - uses: actions/checkout@v6 + - uses: jdx/mise-action@v2 + - name: Run tests for plugin + run: | + echo "Testing plugin ${{ matrix.plugin }}" + geko plugin test --path ${{ matrix.plugin }} diff --git a/ExecutablePluginExample/Package.swift b/ExecutablePluginExample/Package.swift index 53b6f31..2e7ff1a 100644 --- a/ExecutablePluginExample/Package.swift +++ b/ExecutablePluginExample/Package.swift @@ -18,5 +18,11 @@ let package = Package( name: "ExampleTarget", dependencies: [] ), + .testTarget( + name: "ExecutablePluginExampleTests", + dependencies: [ + "ExampleTarget" + ] + ) ] -) \ No newline at end of file +) diff --git a/ExecutablePluginExample/Plugin.swift b/ExecutablePluginExample/Plugin.swift index 9ae8054..87ae6ec 100644 --- a/ExecutablePluginExample/Plugin.swift +++ b/ExecutablePluginExample/Plugin.swift @@ -1,7 +1,7 @@ import ProjectDescription let plugin = Plugin( - name: "GekoPlugin", + name: "ExecutablePluginExample", executables: [ ExecutablePlugin(name: "ExampleGekoExecutable") ] diff --git a/ExecutablePluginExample/Tests/ExecutablePluginExampleTests/Tests.swift b/ExecutablePluginExample/Tests/ExecutablePluginExampleTests/Tests.swift new file mode 100644 index 0000000..dcf4494 --- /dev/null +++ b/ExecutablePluginExample/Tests/ExecutablePluginExampleTests/Tests.swift @@ -0,0 +1,9 @@ +import Foundation +import XCTest +@testable import ExampleTarget + +final class ExampleTargetTests: XCTestCase { + func test_example() async throws { + XCTAssert(true) + } +} diff --git a/WorkspaceMapperPluginExample/Package.resolved b/WorkspaceMapperExample/Package.resolved similarity index 100% rename from WorkspaceMapperPluginExample/Package.resolved rename to WorkspaceMapperExample/Package.resolved diff --git a/WorkspaceMapperPluginExample/Package.swift b/WorkspaceMapperExample/Package.swift similarity index 86% rename from WorkspaceMapperPluginExample/Package.swift rename to WorkspaceMapperExample/Package.swift index b9e3e7d..580c7d8 100644 --- a/WorkspaceMapperPluginExample/Package.swift +++ b/WorkspaceMapperExample/Package.swift @@ -30,6 +30,12 @@ let package = Package( .product(name: "ProjectDescription", package: "project-description"), ], path: "ProjectDescriptionHelpers" + ), + .testTarget( + name: "WorkspaceMapperExampleTests", + dependencies: [ + "WorkspaceMapperExample" + ] ) ] ) diff --git a/WorkspaceMapperPluginExample/Plugin.swift b/WorkspaceMapperExample/Plugin.swift similarity index 100% rename from WorkspaceMapperPluginExample/Plugin.swift rename to WorkspaceMapperExample/Plugin.swift diff --git a/WorkspaceMapperPluginExample/ProjectDescriptionHelpers/SomeStruct.swift b/WorkspaceMapperExample/ProjectDescriptionHelpers/SomeStruct.swift similarity index 100% rename from WorkspaceMapperPluginExample/ProjectDescriptionHelpers/SomeStruct.swift rename to WorkspaceMapperExample/ProjectDescriptionHelpers/SomeStruct.swift diff --git a/WorkspaceMapperPluginExample/Sources/WorkspaceMapperExample/WorkspaceMapperExample.swift b/WorkspaceMapperExample/Sources/WorkspaceMapperExample/WorkspaceMapperExample.swift similarity index 100% rename from WorkspaceMapperPluginExample/Sources/WorkspaceMapperExample/WorkspaceMapperExample.swift rename to WorkspaceMapperExample/Sources/WorkspaceMapperExample/WorkspaceMapperExample.swift diff --git a/WorkspaceMapperExample/Tests/WorkspaceMapperExampleTests/Tests.swift b/WorkspaceMapperExample/Tests/WorkspaceMapperExampleTests/Tests.swift new file mode 100644 index 0000000..28ffd1a --- /dev/null +++ b/WorkspaceMapperExample/Tests/WorkspaceMapperExampleTests/Tests.swift @@ -0,0 +1,9 @@ +import Foundation +import XCTest +@testable import WorkspaceMapperExample + +final class WorkspaceMapperExampleTests: XCTestCase { + func test_example() async throws { + XCTAssert(true) + } +} diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..e986984 --- /dev/null +++ b/mise.toml @@ -0,0 +1,2 @@ +[tools] +"github:geko-tech/geko" = { version = "1.0.0", version_prefix = "Geko@" } diff --git a/plugins.json b/plugins.json new file mode 100644 index 0000000..a0c1f42 --- /dev/null +++ b/plugins.json @@ -0,0 +1,7 @@ +{ + "plugins": [ + { "name": "ExecutablePluginExample" }, + { "name": "WorkspaceMapperExample" }, + { "name": "ImpactAnalysis"} + ] +} diff --git a/scripts/helpers/changed_plugins.bash b/scripts/helpers/changed_plugins.bash new file mode 100644 index 0000000..1cb394f --- /dev/null +++ b/scripts/helpers/changed_plugins.bash @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +PLUGINS_FILE="plugins.json" + +changed_files=$(git --no-pager diff --name-only --merge-base FETCH_HEAD) + +if [[ -z "$changed_files" ]]; then + exit 0 +fi + +plugins=$(jq -r '.plugins[].name' "$PLUGINS_FILE") + +changed_plugins=() + +for plugin in $plugins; do + if echo "$changed_files" | grep -q "^${plugin}/"; then + changed_plugins+=("$plugin") + fi +done + +jq -nc '$ARGS.positional' --args "${changed_plugins[@]}" diff --git a/scripts/helpers/incr_semver.bash b/scripts/helpers/incr_semver.bash new file mode 100644 index 0000000..5bfe79d --- /dev/null +++ b/scripts/helpers/incr_semver.bash @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +# Function to increment a semantic version string +# Usage: incr_semver [major|minor|patch] +IFS=. +parts=($1) +level=$2 + +# Default to patch if no level specified +[[ -z "$level" ]] && level="patch" + +case "$level" in + major) + ((parts[0]++)) + parts[1]=0 + parts[2]=0 + ;; + minor) + ((parts[1]++)) + parts[2]=0 + ;; + patch) + ((parts[2]++)) + ;; + *) + echo "Invalid level specified: $level. Use major, minor, or patch." >&2 + return 1 + ;; +esac + +echo "${parts[0]}.${parts[1]}.${parts[2]}"