feat: support package name filtering and refactor cleanup flow

This commit is contained in:
2026-05-15 02:50:38 +00:00
parent 3305b57ac0
commit 528f8c75b3
5 changed files with 403 additions and 107 deletions
+17
View File
@@ -0,0 +1,17 @@
name: CI
on:
push:
branches:
- feat/解決問題
pull_request:
jobs:
test:
runs-on: ubuntu
steps:
- uses: actions/checkout@v4
- name: Install jq
run: sudo apt-get update && sudo apt-get install -y jq
- name: Run shell tests
run: bash tests/entrypoint.sh
+1 -1
View File
@@ -1,6 +1,6 @@
FROM alpine:latest FROM alpine:latest
RUN apk add --no-cache --no-check-certificate bash curl jq ca-certificates RUN apk add --no-cache bash curl jq ca-certificates
COPY entrypoint.sh /entrypoint.sh COPY entrypoint.sh /entrypoint.sh
+2
View File
@@ -59,3 +59,5 @@ jobs:
- Action 定義: [`action.yaml`](action.yaml) - Action 定義: [`action.yaml`](action.yaml)
- 執行腳本: [`entrypoint.sh`](entrypoint.sh) - 執行腳本: [`entrypoint.sh`](entrypoint.sh)
- Shell 測試: [`tests/entrypoint.sh`](tests/entrypoint.sh)
- Gitea workflow: [`.gitea/workflows/ci.yaml`](.gitea/workflows/ci.yaml)
+159 -105
View File
@@ -10,7 +10,12 @@ fail() {
exit 1 exit 1
} }
declare -A TARGET_PACKAGES=() trim() {
local value="$1"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
printf '%s' "$value"
}
resolve_token() { resolve_token() {
log "Trying token from RUNNER_TOKEN" log "Trying token from RUNNER_TOKEN"
@@ -24,7 +29,7 @@ resolve_token() {
return 1 return 1
} }
resolve_keep_versions() { resolve_keep_count() {
local raw_value="${INPUT_KEEP_COUNT:-2}" local raw_value="${INPUT_KEEP_COUNT:-2}"
if [[ -z "${raw_value}" ]]; then if [[ -z "${raw_value}" ]]; then
@@ -39,50 +44,68 @@ resolve_keep_versions() {
} }
resolve_package_names() { resolve_package_names() {
# Normalize PACKAGE_NAMES into a unique, newline-separated list.
local raw_value="${INPUT_PACKAGE_NAMES:-}" local raw_value="${INPUT_PACKAGE_NAMES:-}"
local normalized package_name local normalized token
local -a package_name_list local -A seen=()
local -a package_names=()
if [[ -z "${raw_value}" ]]; then if [[ -z "$(trim "${raw_value}")" ]]; then
fail "Missing PACKAGE_NAMES" fail "Missing PACKAGE_NAMES"
fi fi
normalized="${raw_value//$'\n'/,}" normalized="${raw_value//$'\n'/,}"
IFS=',' read -r -a tokens <<< "${normalized}"
IFS=',' read -r -a package_name_list <<< "${normalized}" for token in "${tokens[@]}"; do
for package_name in "${package_name_list[@]}"; do token="$(trim "${token}")"
package_name="${package_name#"${package_name%%[![:space:]]*}"}" [[ -n "${token}" ]] || continue
package_name="${package_name%"${package_name##*[![:space:]]}"}"
[[ -n "${package_name}" ]] || continue if [[ -z "${seen["${token}"]+x}" ]]; then
TARGET_PACKAGES["${package_name}"]=1 seen["${token}"]=1
package_names+=("${token}")
fi
done done
if (( ${#TARGET_PACKAGES[@]} == 0 )); then if (( ${#package_names[@]} == 0 )); then
fail "Missing PACKAGE_NAMES" fail "Missing PACKAGE_NAMES"
fi fi
printf '%s\n' "${package_names[@]}"
} }
init_repo_context() { parse_repo_context() {
local repository="${GITEA_REPOSITORY:-}" # Convert the repository slug into owner/repo parts.
local repository="$1"
local owner repo
repository="$(trim "${repository}")"
if [[ -z "${repository}" || "${repository}" != */* ]]; then if [[ -z "${repository}" || "${repository}" != */* ]]; then
fail "Invalid GITEA_REPOSITORY: ${repository:-<empty>}" fail "Invalid GITEA_REPOSITORY: ${repository:-<empty>}"
fi fi
REPO_OWNER="${repository%%/*}" owner="${repository%%/*}"
REPO_NAME="${repository#*/}" repo="${repository#*/}"
if [[ -z "${owner}" || -z "${repo}" || "${repo}" == */* ]]; then
fail "Invalid GITEA_REPOSITORY: ${repository}"
fi
printf '%s\t%s\n' "${owner}" "${repo}"
} }
api_request() { api_request() {
# Perform one HTTP request and return status metadata as TSV.
# stdout format: http_code<TAB>status_text<TAB>request_id
local method="$1" local method="$1"
local path="$2" local path="$2"
local url body_file headers_file local body_file="$3"
local headers_file="$4"
local url http_code status_text request_id status_line
url="${GITEA_SERVER_URL%/}${path}" url="${GITEA_SERVER_URL%/}${path}"
body_file="$(mktemp)"
headers_file="$(mktemp)"
if ! API_HTTP_CODE="$( if ! http_code="$(
curl -sS \ curl -sS \
-H "Accept: application/json" \ -H "Accept: application/json" \
-H "Authorization: token ${RESOLVED_GITEA_TOKEN}" \ -H "Authorization: token ${RESOLVED_GITEA_TOKEN}" \
@@ -92,50 +115,55 @@ api_request() {
-w '%{http_code}' \ -w '%{http_code}' \
"${url}" "${url}"
)"; then )"; then
rm -f "${body_file}" "${headers_file}"
fail "Request failed: ${method} ${path}" fail "Request failed: ${method} ${path}"
fi fi
API_RESPONSE_BODY="$(cat "${body_file}")" status_line="$(head -n 1 "${headers_file}" | tr -d '\r')"
API_RESPONSE_HEADERS="$(cat "${headers_file}")" status_text="$(printf '%s' "${status_line}" | cut -d' ' -f2-)"
API_STATUS_TEXT="$(head -n 1 "${headers_file}" | tr -d '\r' | cut -d' ' -f2-)" request_id="$(
API_REQUEST_ID="$( awk -F': *' 'tolower($1)=="x-gitea-request-id" || tolower($1)=="x-request-id" {value=$2} END {print value}' "${headers_file}" | tr -d '\r'
grep -iE '^(x-gitea-request-id|x-request-id):' "${headers_file}" | tail -n 1 | cut -d':' -f2- | tr -d '\r' | sed 's/^ *//' )"
)" || true
rm -f "${body_file}" "${headers_file}"
if [[ -n "${API_REQUEST_ID}" ]]; then if [[ -n "${request_id}" ]]; then
log "${method} ${path} -> ${API_STATUS_TEXT} request_id=${API_REQUEST_ID}" log "${method} ${path} -> ${status_text} request_id=${request_id}"
else else
log "${method} ${path} -> ${API_STATUS_TEXT}" log "${method} ${path} -> ${status_text}"
fi fi
[[ "${API_HTTP_CODE}" =~ ^2 ]]
printf '%s\t%s\t%s\n' "${http_code}" "${status_text}" "${request_id}"
} }
fetch_all_pages() { fetch_package_versions() {
local base_path="$1" # Fetch and aggregate all package versions for a single package name.
# stdout: JSON array of version objects sorted by page order, later sorted by created_at by the caller.
local owner="$1"
local package_name="$2"
local page=1 local page=1
local limit=100 local limit=100
local aggregate_file page_file tmp_file page_path page_length local aggregate_file page_file headers_file meta http_code status_text request_id page_length path
aggregate_file="$(mktemp)" aggregate_file="$(mktemp)"
printf '[]' > "${aggregate_file}" printf '[]' > "${aggregate_file}"
while :; do while :; do
page_path="${base_path}" path="/api/v1/packages/${owner}/nuget/${package_name}?page=${page}&limit=${limit}"
if [[ "${page_path}" == *\?* ]]; then
page_path="${page_path}&page=${page}&limit=${limit}"
else
page_path="${page_path}?page=${page}&limit=${limit}"
fi
if ! api_request GET "${page_path}"; then
rm -f "${aggregate_file}"
fail "Unexpected response for ${page_path}"
fi
page_file="$(mktemp)" page_file="$(mktemp)"
printf '%s' "${API_RESPONSE_BODY}" > "${page_file}" headers_file="$(mktemp)"
meta="$(api_request GET "${path}" "${page_file}" "${headers_file}")"
IFS=$'\t' read -r http_code status_text request_id <<< "${meta}"
rm -f "${headers_file}"
if [[ "${http_code}" == "404" ]]; then
rm -f "${page_file}" "${aggregate_file}"
printf '[]'
return 0
fi
if [[ ! "${http_code}" =~ ^2 ]]; then
rm -f "${page_file}" "${aggregate_file}"
fail "Unexpected response for package ${package_name}: ${status_text}"
fi
page_length="$(jq 'length' "${page_file}")" page_length="$(jq 'length' "${page_file}")"
tmp_file="$(mktemp)" tmp_file="$(mktemp)"
@@ -155,89 +183,110 @@ fetch_all_pages() {
} }
collect_package_candidates() { collect_package_candidates() {
local packages_json group_json package_name total_versions candidates_json # Build the delete candidate file for the requested package names.
# stdout: package_count<TAB>total_version_count<TAB>kept_count<TAB>candidate_count
local owner="$1"
local keep_count="$2"
local candidate_file="$3"
shift 3
local -a package_names=("$@")
local package_name versions_json total_versions candidates_json
local package_count=0
local total_version_count=0
local kept_count=0
local candidate_count=0
packages_json="$( : > "${candidate_file}"
fetch_all_pages "/api/v1/packages/${REPO_OWNER}?type=nuget"
)"
if [[ "$(jq 'length' <<<"${packages_json}")" -eq 0 ]]; then for package_name in "${package_names[@]}"; do
log "No nuget packages found for owner ${REPO_OWNER}" versions_json="$(fetch_package_versions "${owner}" "${package_name}")"
return 0
fi
while IFS= read -r group_json; do if [[ "$(jq 'length' <<<"${versions_json}")" -eq 0 ]]; then
package_name="$(jq -r '.[0].name' <<<"${group_json}")" log "No versions found for package ${package_name}"
if [[ -z "${TARGET_PACKAGES["$package_name"]+x}" ]]; then
continue continue
fi fi
total_versions="$(jq 'length' <<<"${group_json}")" package_count=$((package_count + 1))
total_versions="$(jq 'length' <<<"${versions_json}")"
total_version_count=$((total_version_count + total_versions))
PACKAGE_COUNT=$((PACKAGE_COUNT + 1)) log "Package ${package_name}: total_versions=${total_versions} keep_count=${keep_count}"
TOTAL_VERSION_COUNT=$((TOTAL_VERSION_COUNT + total_versions))
log "Package ${package_name}: total_versions=${total_versions} keep_count=${keep_versions}"
log "Package ${package_name} versions (oldest -> newest):" log "Package ${package_name} versions (oldest -> newest):"
while IFS=$'\t' read -r version created_at; do while IFS=$'\t' read -r version created_at; do
[[ -z "${version}" ]] && continue [[ -z "${version}" ]] && continue
log " - ${version} (${created_at})" log " - ${version} (${created_at})"
done < <(jq -r 'sort_by(.created_at)[] | [.version, .created_at] | @tsv' <<<"${group_json}") done < <(jq -r 'sort_by(.created_at)[] | [.version, .created_at] | @tsv' <<<"${versions_json}")
if (( total_versions <= keep_versions )); then if (( total_versions <= keep_count )); then
log " keep all ${total_versions} versions" log " keep all ${total_versions} versions"
KEPT_COUNT=$((KEPT_COUNT + total_versions)) kept_count=$((kept_count + total_versions))
continue continue
fi fi
KEPT_COUNT=$((KEPT_COUNT + keep_versions)) kept_count=$((kept_count + keep_count))
candidates_json="$( candidates_json="$(
jq -c --argjson keep "${keep_versions}" \ jq -c --argjson keep "${keep_count}" \
'sort_by(.created_at) | .[0:(length - $keep)]' <<<"${group_json}" 'sort_by(.created_at) | .[0:(length - $keep)]' <<<"${versions_json}"
)" )"
while IFS=$'\t' read -r name version created_at; do while IFS=$'\t' read -r package version created_at; do
[[ -z "${name}" ]] && continue [[ -z "${package}" ]] && continue
log "Candidate to delete: package ${name} version ${version} (created: ${created_at})" log "Candidate to delete: package ${package} version ${version} (created: ${created_at})"
printf '%s\t%s\t%s\n' "${name}" "${version}" "${created_at}" >> "${CANDIDATES_FILE}" printf '%s\t%s\t%s\n' "${package}" "${version}" "${created_at}" >> "${candidate_file}"
CANDIDATE_COUNT=$((CANDIDATE_COUNT + 1)) candidate_count=$((candidate_count + 1))
done < <(jq -r '.[] | [.name, .version, .created_at] | @tsv' <<<"${candidates_json}") done < <(jq -r '.[] | [.name, .version, .created_at] | @tsv' <<<"${candidates_json}")
done < <(jq -c 'group_by(.name)[]' <<<"${packages_json}") done
printf '%s\t%s\t%s\t%s\n' "${package_count}" "${total_version_count}" "${kept_count}" "${candidate_count}"
} }
process_candidates() { process_candidates() {
local name version created_at # Delete each queued version and summarize the result.
local owner="$1"
local candidate_file="$2"
local package_count="$3"
local total_version_count="$4"
local kept_count="$5"
local candidate_count="$6"
local deleted_count=0 local deleted_count=0
local error_count=0 local error_count=0
local package_name version created_at
local body_file headers_file meta http_code status_text request_id
if [[ ! -s "${CANDIDATES_FILE}" ]]; then if [[ ! -s "${candidate_file}" ]]; then
log "No delete candidates found" log "No delete candidates found"
log "Summary: packages=${PACKAGE_COUNT} versions=${TOTAL_VERSION_COUNT} kept=${KEPT_COUNT} candidates=0 deleted=0 errors=0" log "Summary: packages=${package_count} versions=${total_version_count} kept=${kept_count} candidates=0 deleted=0 errors=0"
return 0 return 0
fi fi
while IFS=$'\t' read -r name version created_at; do while IFS=$'\t' read -r package_name version created_at; do
[[ -z "${name}" ]] && continue [[ -z "${package_name}" ]] && continue
if api_request DELETE "/api/v1/packages/${REPO_OWNER}/nuget/${name}/${version}"; then body_file="$(mktemp)"
log "Deleted package ${name} version ${version} -> ${API_STATUS_TEXT}" headers_file="$(mktemp)"
meta="$(api_request DELETE "/api/v1/packages/${owner}/nuget/${package_name}/${version}" "${body_file}" "${headers_file}")"
IFS=$'\t' read -r http_code status_text request_id <<< "${meta}"
rm -f "${body_file}" "${headers_file}"
if [[ "${http_code}" =~ ^2 ]]; then
log "Deleted package ${package_name} version ${version} -> ${status_text}"
deleted_count=$((deleted_count + 1)) deleted_count=$((deleted_count + 1))
else else
if [[ -n "${API_REQUEST_ID}" ]]; then if [[ -n "${request_id}" ]]; then
log "ERROR: DELETE package ${name} version ${version} -> ${API_STATUS_TEXT} request_id=${API_REQUEST_ID}" log "ERROR: DELETE package ${package_name} version ${version} -> ${status_text} request_id=${request_id}"
else else
log "ERROR: DELETE package ${name} version ${version} -> ${API_STATUS_TEXT}" log "ERROR: DELETE package ${package_name} version ${version} -> ${status_text}"
fi fi
error_count=$((error_count + 1)) error_count=$((error_count + 1))
fi fi
done < "${CANDIDATES_FILE}" done < "${candidate_file}"
log "Summary: packages=${PACKAGE_COUNT} versions=${TOTAL_VERSION_COUNT} kept=${KEPT_COUNT} candidates=${CANDIDATE_COUNT} deleted=${deleted_count} errors=${error_count}" log "Summary: packages=${package_count} versions=${total_version_count} kept=${kept_count} candidates=${candidate_count} deleted=${deleted_count} errors=${error_count}"
} }
main() { main() {
local token keep_versions local token keep_count repository owner repo package_names_csv
local candidate_file summary package_count total_version_count kept_count candidate_count
log "Gitea Server Url: ${GITEA_SERVER_URL:-}" log "Gitea Server Url: ${GITEA_SERVER_URL:-}"
log "Gitea Repository: ${GITEA_REPOSITORY:-}" log "Gitea Repository: ${GITEA_REPOSITORY:-}"
@@ -247,27 +296,32 @@ main() {
fi fi
export RESOLVED_GITEA_TOKEN="$token" export RESOLVED_GITEA_TOKEN="$token"
init_repo_context
keep_versions="$(resolve_keep_versions)" repository="${GITEA_REPOSITORY:-}"
log "keep_count=${keep_versions}" IFS=$'\t' read -r owner repo <<< "$(parse_repo_context "${repository}")"
resolve_package_names keep_count="$(resolve_keep_count)"
log "package_names=${INPUT_PACKAGE_NAMES}"
mapfile -t package_names < <(resolve_package_names)
package_names_csv="$(IFS=,; echo "${package_names[*]}")"
log "keep_count=${keep_count}"
log "package_names=${package_names_csv}"
log "Token source resolved successfully" log "Token source resolved successfully"
CANDIDATES_FILE="$(mktemp)" candidate_file="$(mktemp)"
export CANDIDATES_FILE trap "rm -f -- '${candidate_file}'" EXIT
PACKAGE_COUNT=0
TOTAL_VERSION_COUNT=0
KEPT_COUNT=0
CANDIDATE_COUNT=0
trap 'rm -f "${CANDIDATES_FILE}"' EXIT
collect_package_candidates summary="$(collect_package_candidates "${owner}" "${keep_count}" "${candidate_file}" "${package_names[@]}")"
if (( PACKAGE_COUNT == 0 )); then IFS=$'\t' read -r package_count total_version_count kept_count candidate_count <<< "${summary}"
if (( package_count == 0 )); then
log "No matching packages found for requested package_names" log "No matching packages found for requested package_names"
fi fi
process_candidates
process_candidates "${owner}" "${candidate_file}" "${package_count}" "${total_version_count}" "${kept_count}" "${candidate_count}"
log "Stage 4 complete" log "Stage 4 complete"
} }
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
main "$@" main "$@"
fi
+223
View File
@@ -0,0 +1,223 @@
#!/bin/bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "${TMP_DIR}"' EXIT
assert_eq() {
local expected="$1"
local actual="$2"
if [[ "${expected}" != "${actual}" ]]; then
printf 'Expected:\n%s\nActual:\n%s\n' "${expected}" "${actual}" >&2
exit 1
fi
}
expect_fail() {
if ( "$@" ) >/dev/null 2>&1; then
printf 'Expected failure: %s\n' "$*" >&2
exit 1
fi
}
write_mock_curl_pagination() {
cat > "${TMP_DIR}/curl" <<'EOF'
#!/bin/sh
make_versions_json() {
prefix="$1"
start="$2"
count="$3"
i=0
printf '['
while [ "$i" -lt "$count" ]; do
idx=$((start + i))
[ "$i" -gt 0 ] && printf ','
printf '{"name":"%s","version":"%s.%s.0","created_at":"2023-01-%02dT00:00:00Z"}' \
"$prefix" "$idx" "$idx" "$((idx + 1))"
i=$((i + 1))
done
printf ']'
}
method=GET
out_file=
headers_file=
url=
while [ "$#" -gt 0 ]; do
case "$1" in
-X) method=$2; shift 2 ;;
-D) headers_file=$2; shift 2 ;;
-o) out_file=$2; shift 2 ;;
-w) shift 2 ;;
-H|-sS) shift 1 ;;
*) url=$1; shift ;;
esac
done
body='[]'
status='HTTP/1.1 200 OK'
case "$url" in
*'/api/v1/packages/test-owner/nuget/pkg-a?page=1&limit=100')
body="$(make_versions_json pkg-a 0 100)"
;;
*'/api/v1/packages/test-owner/nuget/pkg-a?page=2&limit=100')
body="$(make_versions_json pkg-a 100 1)"
;;
esac
printf '%s' "$body" > "$out_file"
{
printf '%s\n' "$status"
printf 'X-Request-Id: req-page\n'
} > "$headers_file"
printf '%s' 200
EOF
chmod +x "${TMP_DIR}/curl"
}
write_mock_curl_collect() {
cat > "${TMP_DIR}/curl" <<'EOF'
#!/bin/sh
method=GET
out_file=
headers_file=
url=
while [ "$#" -gt 0 ]; do
case "$1" in
-X) method=$2; shift 2 ;;
-D) headers_file=$2; shift 2 ;;
-o) out_file=$2; shift 2 ;;
-w) shift 2 ;;
-H|-sS) shift 1 ;;
*) url=$1; shift ;;
esac
done
body='[]'
status='HTTP/1.1 200 OK'
code='200'
case "$url" in
*'/api/v1/packages/test-owner/nuget/pkg-a?page=1&limit=100')
body='[{"name":"pkg-a","version":"1.0.0","created_at":"2023-01-01T00:00:00Z"},{"name":"pkg-a","version":"1.1.0","created_at":"2023-01-02T00:00:00Z"},{"name":"pkg-a","version":"1.2.0","created_at":"2023-01-03T00:00:00Z"}]'
;;
*'/api/v1/packages/test-owner/nuget/pkg-b?page=1&limit=100')
code='404'
status='HTTP/1.1 404 Not Found'
body=''
;;
esac
printf '%s' "$body" > "$out_file"
{
printf '%s\n' "$status"
printf 'X-Request-Id: req-collect\n'
} > "$headers_file"
printf '%s' "$code"
EOF
chmod +x "${TMP_DIR}/curl"
}
write_mock_curl_delete() {
cat > "${TMP_DIR}/curl" <<'EOF'
#!/bin/sh
method=GET
out_file=
headers_file=
url=
while [ "$#" -gt 0 ]; do
case "$1" in
-X) method=$2; shift 2 ;;
-D) headers_file=$2; shift 2 ;;
-o) out_file=$2; shift 2 ;;
-w) shift 2 ;;
-H|-sS) shift 1 ;;
*) url=$1; shift ;;
esac
done
code='200'
status='HTTP/1.1 200 OK'
body='[]'
case "$url" in
*'/api/v1/packages/test-owner/nuget/pkg-a?page=1&limit=100')
body='[{"name":"pkg-a","version":"1.0.0","created_at":"2023-01-01T00:00:00Z"},{"name":"pkg-a","version":"0.9.0","created_at":"2022-12-31T00:00:00Z"}]'
;;
*'/api/v1/packages/test-owner/nuget/pkg-a/1.0.0')
if [ "$method" = "DELETE" ]; then
code='204'
status='HTTP/1.1 204 No Content'
body=''
fi
;;
*'/api/v1/packages/test-owner/nuget/pkg-a/0.9.0')
if [ "$method" = "DELETE" ]; then
code='404'
status='HTTP/1.1 404 Not Found'
body=''
fi
;;
esac
printf '%s' "$body" > "$out_file"
{
printf '%s\n' "$status"
printf 'X-Request-Id: req-delete\n'
} > "$headers_file"
if [ "$method" = "DELETE" ] && [ -n "${MOCK_DELETE_LOG:-}" ]; then
printf '%s %s\n' "$method" "$url" >> "$MOCK_DELETE_LOG"
fi
printf '%s' "$code"
EOF
chmod +x "${TMP_DIR}/curl"
}
source "${ROOT_DIR}/entrypoint.sh"
assert_eq "abc" "$(RUNNER_TOKEN=abc resolve_token)"
expect_fail env -u RUNNER_TOKEN bash -lc "source '${ROOT_DIR}/entrypoint.sh'; resolve_token"
assert_eq "5" "$(INPUT_KEEP_COUNT=5 resolve_keep_count)"
expect_fail env INPUT_KEEP_COUNT=abc bash -lc "source '${ROOT_DIR}/entrypoint.sh'; resolve_keep_count"
output="$(INPUT_PACKAGE_NAMES=$' pkg-a, pkg-b\npkg-a , pkg-c ' resolve_package_names)"
assert_eq $'pkg-a\npkg-b\npkg-c' "${output}"
expect_fail env INPUT_PACKAGE_NAMES=' , ' bash -lc "source '${ROOT_DIR}/entrypoint.sh'; resolve_package_names"
IFS=$'\t' read -r owner repo <<< "$(parse_repo_context "owner/repo")"
assert_eq "owner" "${owner}"
assert_eq "repo" "${repo}"
expect_fail bash -lc "source '${ROOT_DIR}/entrypoint.sh'; parse_repo_context 'repo'"
write_mock_curl_pagination
output="$(PATH="${TMP_DIR}:$PATH" GITEA_SERVER_URL="https://gitea.example.com" RESOLVED_GITEA_TOKEN="token" \
fetch_package_versions "test-owner" "pkg-a")"
assert_eq "101" "$(jq 'length' <<<"${output}")"
assert_eq "0.0.0" "$(jq -r '.[0].version' <<<"${output}")"
assert_eq "100.100.0" "$(jq -r '.[100].version' <<<"${output}")"
write_mock_curl_collect
summary="$(PATH="${TMP_DIR}:$PATH" GITEA_SERVER_URL="https://gitea.example.com" RESOLVED_GITEA_TOKEN="token" \
collect_package_candidates "test-owner" "1" "${TMP_DIR}/candidates.tsv" "pkg-a" "pkg-b")"
IFS=$'\t' read -r package_count total_version_count kept_count candidate_count <<< "${summary}"
assert_eq "1" "${package_count}"
assert_eq "3" "${total_version_count}"
assert_eq "1" "${kept_count}"
assert_eq "2" "${candidate_count}"
assert_eq "2" "$(wc -l < "${TMP_DIR}/candidates.tsv" | tr -d ' ')"
write_mock_curl_delete
candidate_file="${TMP_DIR}/delete.tsv"
cat > "${candidate_file}" <<'EOF'
pkg-a 1.0.0 2023-01-01T00:00:00Z
pkg-a 0.9.0 2022-12-31T00:00:00Z
EOF
output="$(MOCK_DELETE_LOG="${TMP_DIR}/delete.log" PATH="${TMP_DIR}:$PATH" GITEA_SERVER_URL="https://gitea.example.com" RESOLVED_GITEA_TOKEN="token" \
process_candidates "test-owner" "${candidate_file}" "1" "2" "1" "2" 2>&1)"
assert_eq "1" "$(grep -c '^DELETE ' "${TMP_DIR}/delete.log")"
assert_eq "Summary: packages=1 versions=2 kept=1 candidates=2 deleted=1 errors=1" "$(printf '%s' "${output}" | tail -n 1)"