Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions .codacy/cli.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#!/usr/bin/env bash


set -e +o pipefail

# Set up paths first
bin_name="codacy-cli-v2"

# Determine OS-specific paths
os_name=$(uname)
arch=$(uname -m)

case "$arch" in
"x86_64")
arch="amd64"
;;
"x86")
arch="386"
;;
"aarch64"|"arm64")
arch="arm64"
;;
esac

if [ -z "$CODACY_CLI_V2_TMP_FOLDER" ]; then
if [ "$(uname)" = "Linux" ]; then
CODACY_CLI_V2_TMP_FOLDER="$HOME/.cache/codacy/codacy-cli-v2"
elif [ "$(uname)" = "Darwin" ]; then
CODACY_CLI_V2_TMP_FOLDER="$HOME/Library/Caches/Codacy/codacy-cli-v2"
else
CODACY_CLI_V2_TMP_FOLDER=".codacy-cli-v2"
fi
fi

version_file="$CODACY_CLI_V2_TMP_FOLDER/version.yaml"


get_version_from_yaml() {
if [ -f "$version_file" ]; then
local version=$(grep -o 'version: *"[^"]*"' "$version_file" | cut -d'"' -f2)
if [ -n "$version" ]; then
echo "$version"
return 0
fi
fi
return 1
}

get_latest_version() {
local response
if [ -n "$GH_TOKEN" ]; then
response=$(curl -Lq --header "Authorization: Bearer $GH_TOKEN" "https://api.github.com/repos/codacy/codacy-cli-v2/releases/latest" 2>/dev/null)
else
response=$(curl -Lq "https://api.github.com/repos/codacy/codacy-cli-v2/releases/latest" 2>/dev/null)
fi

handle_rate_limit "$response"
local version=$(echo "$response" | grep -m 1 tag_name | cut -d'"' -f4)
echo "$version"
}

handle_rate_limit() {
local response="$1"
if echo "$response" | grep -q "API rate limit exceeded"; then
fatal "Error: GitHub API rate limit exceeded. Please try again later"
fi
}

download_file() {
local url="$1"

echo "Downloading from URL: ${url}"
if command -v curl > /dev/null 2>&1; then
curl -# -LS "$url" -O
elif command -v wget > /dev/null 2>&1; then
wget "$url"
else
fatal "Error: Could not find curl or wget, please install one."
fi
}

download() {
local url="$1"
local output_folder="$2"

( cd "$output_folder" && download_file "$url" )
}

download_cli() {
# OS name lower case
suffix=$(echo "$os_name" | tr '[:upper:]' '[:lower:]')

local bin_folder="$1"
local bin_path="$2"
local version="$3"

if [ ! -f "$bin_path" ]; then
echo "📥 Downloading CLI version $version..."

remote_file="codacy-cli-v2_${version}_${suffix}_${arch}.tar.gz"
url="https://github.com/codacy/codacy-cli-v2/releases/download/${version}/${remote_file}"

download "$url" "$bin_folder"
tar xzfv "${bin_folder}/${remote_file}" -C "${bin_folder}"
fi
}

# Warn if CODACY_CLI_V2_VERSION is set and update is requested
if [ -n "$CODACY_CLI_V2_VERSION" ] && [ "$1" = "update" ]; then
echo "⚠️ Warning: Performing update with forced version $CODACY_CLI_V2_VERSION"
echo " Unset CODACY_CLI_V2_VERSION to use the latest version"
fi

# Ensure version.yaml exists and is up to date
if [ ! -f "$version_file" ] || [ "$1" = "update" ]; then
echo "ℹ️ Fetching latest version..."
version=$(get_latest_version)
mkdir -p "$CODACY_CLI_V2_TMP_FOLDER"
echo "version: \"$version\"" > "$version_file"
fi

# Set the version to use
if [ -n "$CODACY_CLI_V2_VERSION" ]; then
version="$CODACY_CLI_V2_VERSION"
else
version=$(get_version_from_yaml)
fi


# Set up version-specific paths
bin_folder="${CODACY_CLI_V2_TMP_FOLDER}/${version}"

mkdir -p "$bin_folder"
bin_path="$bin_folder"/"$bin_name"

# Download the tool if not already installed
download_cli "$bin_folder" "$bin_path" "$version"
chmod +x "$bin_path"

run_command="$bin_path"
if [ -z "$run_command" ]; then
fatal "Codacy cli v2 binary could not be found."
fi

if [ "$#" -eq 1 ] && [ "$1" = "download" ]; then
echo "Codacy cli v2 download succeeded"
else
eval "$run_command $*"
fi
4 changes: 3 additions & 1 deletion .github/workflows/self-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Self Test Codacy Label Action

on:
pull_request:
types: [opened, reopened, synchronize, ready_for_review, edited]
types: [opened, reopened, synchronize, ready_for_review, edited, unlabeled]

jobs:
label-pr:
Expand All @@ -14,6 +14,7 @@ jobs:
- uses: actions/checkout@v4

- name: Run action from repository
if: ${{ !contains(github.event.pull_request.labels.*.name, 'codacy-review') }}
uses: ./
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -22,6 +23,7 @@ jobs:
bots: true
required-title-keywords: "^feat:, test"
label-color: "FF5733"
label-removal: true

- name: Verify label applied
if: always()
Expand Down
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ name: Codacy Review Labeler

on:
pull_request:
types: [opened, ready_for_review, edited, reopened]
types: [opened, ready_for_review, edited, reopened, unlabeled]

jobs:
label-pr:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'codacy-review') }}
runs-on: ubuntu-latest
permissions:
contents: read
Expand All @@ -33,20 +34,21 @@ jobs:
exclude-title-keywords: "revert, ^draft, ^wip" # optional
create-label: true # optional
label-color: "050B1A" # optional
label-removal: true # optional
```

## Controls

* **`include-drafts`** (optional, default `false`)
* **`include-drafts`** (optional, default `false`) (recommended: `ready-for-review` event)
Labels draft pull requests when enabled.

* **`bots`** (optional, default: `false`)
Labels pull requests opened by bot accounts (or accounts with [bot] in their name).

* **`required-title-keywords`** (optional, default: `""`)
* **`required-title-keywords`** (optional, default: `""`) (recommended: `edited` event)
Comma-separated list. Labels pull requests only if its title contains at least one of these keywords. Use `^` as start anchor. Matching is case-insensitive. Empty disables it.

* **`exclude-title-keywords`** (optional, default: `""`)
* **`exclude-title-keywords`** (optional, default: `""`) (recommended: `edited` event)
Comma-separated list. Excludes labeling from pull requests that contain at least one of these keywords. Use `^` as start anchor. Matching is case-insensitive. Empty disables it.

* **`create-label`** (optional, default: `true`)
Expand All @@ -55,6 +57,9 @@ jobs:
* **`label-color`** (optional, default: `050B1A`)
In case the label is created, you can set a Hex color override for the auto-created label (no `#` needed).

* **`label-removal`** (optional, default: `true`) (required: `unlabeled` event)
Store when a label is removed from a PR to prevent it from being added again. When enabled, the action checks `unlabeled` events, and records the removal in a hidden comment on the PR (e.g. `<!-- codacy-review label removed -->).


## Troubleshooting

Expand Down
5 changes: 5 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ inputs:
required: false
default: "050B1A"

label-removal:
description: "Add a hidden marker to the PR description when the label is removed and skip re-adding while it is present."
required: false
default: "true"

github-token:
description: "GitHub token. If not set, GITHUB_TOKEN env will be used."
required: false
Expand Down
39 changes: 34 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
const { parseKeywords, parseBool, parseHexColor } = require("./helpers/parsers");
const { githubRequest } = require("./helpers/github");
const { getInput } = require("./helpers/input")

const fs = require("fs");

// action contiguration variables
const labelName = "codacy-review";
const removalMarker = "<!-- codacy-review label removed : by codacy-review-label-action -->";

function log(msg) {
process.stdout.write(msg + "\n");
}
Expand All @@ -26,7 +29,8 @@
const excludeKeywords = parseKeywords(getInput("exclude-title-keywords", ""));
const autoCreate = parseBool(getInput("create-label", "true"), true);
const labelColor = parseHexColor(getInput("label-color", "050B1A"), "050B1A");
const githubToken = process.env.GITHUB_TOKEN;
const labelRemoval = parseBool(getInput("label-removal", "true"), true);
const githubToken = process.env.GITHUB_TOKEN;

// if token is not set, exit with error
if (!githubToken || githubToken === "" || githubToken === undefined) {
Expand All @@ -42,11 +46,37 @@
const pr = eventData.pull_request;
if (!pr) return log("Not a PR event");

// get pr details
const owner = process.env.GITHUB_REPOSITORY.split("/")[0];
const repo = process.env.GITHUB_REPOSITORY.split("/")[1];
const prNumber = pr.number;
const prBody = pr.body || "";
const eventAction = eventData.action || "";
const eventLabelName = eventData.label?.name || "";
const labelRemovalTriggered = eventAction === "unlabeled" && eventLabelName === labelName;

// check if the label has been removed from the PR at some point
if (labelRemoval && prBody.includes(removalMarker)) {
return log("Skipping PR because the label removal marker is present in the description");
}

// handle removal of label
if (labelRemoval && labelRemovalTriggered) {
if (!prBody.includes(removalMarker)) {
// handle empty descriptions
const trimmedBody = prBody.trimEnd();
const updatedBody = trimmedBody ? `${trimmedBody}\n${removalMarker}` : removalMarker;
await githubRequest(
githubToken,
"PATCH",
`https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}`,
{ body: updatedBody }

Check notice on line 73 in src/index.js

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/index.js#L73

Unnecessary block.
);
log("Appended label removal marker to PR description");
}
return log("Detected codacy-review label removal event. Skipping relabeling.");
}

// check PR data --
// - exclude drafts
if (!includeDrafts) {
const isDraft = pr.draft === true;
Expand Down Expand Up @@ -74,7 +104,6 @@
}

// Ensure label exists
const labelName = "codacy-review";
const labelUrl = `https://api.github.com/repos/${owner}/${repo}/labels/${labelName}`;

let labelExists = true;
Expand Down Expand Up @@ -114,4 +143,4 @@
}
}

run();
run();