Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Provides reusable helpers sourced by scripts:
| `validate_github_token [bearer]` | Verify GITHUB_TOKEN via /user endpoint |
| `validate_slug <value> <label>` | Reject values with non-alphanumeric/hyphen/underscore chars |
| `is_bsd_date` | Returns 0 if `date` is BSD-style (macOS), 1 for GNU (Linux); branch between `date -v` and `date -d` syntax |
| `csv_escape <value>` | Doubles embedded double-quotes for a quoted CSV field |
| `gh_api <path> [--api-version V] [curl args...]` | Bearer-auth REST helper with 5-retry rate-limit handling; optional `--api-version` overrides the default `2022-11-28` header; returns literal `__404__` or `__422__` for those HTTP statuses — callers must check for these sentinels |
| `gh_api_paginate <path> [filter] [version]` | Paginated REST helper, follows Link headers, streams items; returns silently with empty output on 404/422 |
| `get_enterprise_orgs` | Three-tier enterprise org resolver (REST → GraphQL → /user/orgs) |
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ bats tests/test_common.bats

| File | What it covers |
|------|----------------|
| `tests/test_common.bats` | `lib/github-common.sh` — pure-logic functions (`validate_slug`, `require_env_var`, `require_command`, `err`, `configure_gh_auth`, `validate_token`, `get_repo_page_count`, `is_bsd_date`) and API helpers (`gh_api` sentinels, `gh_api_paginate`) |
| `tests/test_common.bats` | `lib/github-common.sh` — pure-logic functions (`validate_slug`, `require_env_var`, `require_command`, `err`, `configure_gh_auth`, `validate_token`, `get_repo_page_count`, `is_bsd_date`, `csv_escape`) and API helpers (`gh_api` sentinels, `gh_api_paginate`) |
| `tests/test_script_validation.bats` | Every script — missing required env vars exit 1, invalid CLI args exit 1, `--help` exits 0, script-specific enum/allowlist validation |
| `tests/mock_curl.sh` | Universal drop-in curl mock (used by both test files); response data via env vars `MOCK_CURL_CODE`, `MOCK_CURL_BODY`, `MOCK_CURL_LINK` |

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,7 @@ All scripts can leverage a shared utility library for common operations like val
- `validate_github_token [bearer]` — Convenience wrapper for `GITHUB_TOKEN` validation
- `validate_slug <value> <label>` — Exit if value contains characters other than alphanumeric, hyphen, or underscore
- `is_bsd_date` — Return 0 if the system `date` is BSD-style (macOS), 1 for GNU (Linux); use to branch between `date -v` and `date -d` syntax
- `csv_escape <value>` — Double embedded double-quotes so a value is safe to wrap in a quoted CSV field

**Auth helpers:**
- `configure_gh_auth [scope_hint]` — Bridge `GITHUB_TOKEN→GH_TOKEN` for scripts that use the `gh` CLI, or verify an active `gh` auth session if no token is set
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,13 @@ parse_dockerfile_content() {

# Sanitize fields for CSV (escape double-quotes, wrap in quotes)
local safe_path safe_url
safe_path=$(echo "${dockerfile_path}" | sed 's/"/""/g')
safe_url=$(echo "${html_url}" | sed 's/"/""/g')
safe_path=$(csv_escape "${dockerfile_path}")
safe_url=$(csv_escape "${html_url}")
local safe_image safe_tag safe_digest safe_base
safe_image=$(echo "${image}" | sed 's/"/""/g')
safe_tag=$(echo "${tag}" | sed 's/"/""/g')
safe_digest=$(echo "${digest}" | sed 's/"/""/g')
safe_base=$(echo "${base_image}" | sed 's/"/""/g')
safe_image=$(csv_escape "${image}")
safe_tag=$(csv_escape "${tag}")
safe_digest=$(csv_escape "${digest}")
safe_base=$(csv_escape "${base_image}")
local repo_name
repo_name=$(echo "${repo_full_name}" | cut -d/ -f2)

Expand Down
10 changes: 10 additions & 0 deletions lib/github-common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
# validate_token <VAR_NAME> — verify a secondary token variable
# validate_slug <value> [label] — exit if value contains unsafe chars
# is_bsd_date — 0 if `date` is BSD-style (macOS), 1 for GNU (Linux)
# csv_escape <value> — doubles embedded double-quotes for a quoted CSV field
# gh_api <path|url> [--api-version V] [curl args…] — Bearer-auth REST helper with retry;
# returns "__404__"/"__422__" (exit 0) for those codes
# gh_api_paginate <path> [filter] [version] — paginated REST, follows Link headers;
Expand Down Expand Up @@ -163,6 +164,15 @@ is_bsd_date() {
date -v-1d > /dev/null 2>&1
}

###
## csv_escape <value>
## Doubles embedded double-quotes so a value is safe to wrap in a quoted
## CSV field (e.g. printf '"%s"' "$(csv_escape "$value")").
###
csv_escape() {
echo "$1" | sed 's/"/""/g'
}

###
## validate_slug <value> <label>
## Exits with status 1 if the value contains characters other than
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ fetch_old_repos() {
REPO_HTMLURL=$(echo "$REPO_PAYLOAD" | jq -r .html_url)
REPO_DESCRIPTION=$(echo "$REPO_PAYLOAD" | jq -r .description)
REPO_FORK=$(echo "$REPO_PAYLOAD" | jq -r .fork)
ESCAPED_DESC=$(echo "$REPO_DESCRIPTION" | sed 's/"/""/g')
ESCAPED_DESC=$(csv_escape "$REPO_DESCRIPTION")
echo "${REPO_NAME},${REPO_FULLNAME},${REPO_PRIVATE},${REPO_ARCHIVED},${REPO_HTMLURL},\"${ESCAPED_DESC}\",${REPO_FORK},${REPO_UPDATEDAT},${DAYS_SINCE}" >> "$REPORT_FILE"

total_old_repos=$((total_old_repos + 1))
Expand Down
2 changes: 1 addition & 1 deletion org-admin/github-get-repo-list/github-get-repo-list.sh
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ process_repos () {
REPO_PUSHEDAT=$(echo "${REPO_PAYLOAD}" | jq -r .pushed_at)
REPO_CREATEDAT=$(echo "${REPO_PAYLOAD}" | jq -r .created_at)
REPO_UPDATEDAT=$(echo "${REPO_PAYLOAD}" | jq -r .updated_at)
ESCAPED_DESCRIPTION=$(echo "${REPO_DESCRIPTION}" | sed 's/"/""/g')
ESCAPED_DESCRIPTION=$(csv_escape "${REPO_DESCRIPTION}")

printf '%s,%s,%s,%s,%s,"%s",%s,%s,%s,%s\n' \
"${i}" "${REPO_FULLNAME}" "${REPO_OWNER}" "${REPO_PRIVATE}" "${REPO_HTMLURL}" \
Expand Down
14 changes: 14 additions & 0 deletions tests/test_common.bats
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,20 @@ _mock_curl() {
fi
}

# ─── csv_escape ───────────────────────────────────────────────────────────────

@test "csv_escape: leaves plain values unchanged" {
run bash -c "GITHUB_TOKEN=x source '${LIB_PATH}' 2>/dev/null; csv_escape 'plain value'"
[ "$status" -eq 0 ]
[ "$output" = "plain value" ]
}

@test "csv_escape: doubles embedded double-quotes" {
run bash -c "GITHUB_TOKEN=x source '${LIB_PATH}' 2>/dev/null; csv_escape 'she said \"hi\"'"
[ "$status" -eq 0 ]
[ "$output" = 'she said ""hi""' ]
}

# ─── require_env_var ──────────────────────────────────────────────────────────

@test "require_env_var: exits 1 when variable is unset" {
Expand Down