Skip to content

Commit cb92c2a

Browse files
authored
sync(ci): restructure GitHub env config and workflows (#87)
1 parent 6597d63 commit cb92c2a

32 files changed

+1197
-798
lines changed

.github/.env.base

Lines changed: 0 additions & 549 deletions
This file was deleted.

.github/.yamlfmt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ exclude:
7474
- "**/*.swo"
7575
- "**/*~"
7676

77-
# Build configs
77+
# Environment files
78+
- "**/env/**"
7879
- "**/.env.base"
7980
- "**/.env.custom"

.github/CODEOWNERS

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
* @mrz1836
77

88
# GitHub Actions workflows
9+
.github/actions/* @mrz1836
10+
.github/scripts/* @mrz1836
911
.github/workflows/* @mrz1836
12+
.github/env/* @mrz1836
1013
.github/.env.base @mrz1836
1114
.github/.env.custom @mrz1836
1215

@@ -37,7 +40,6 @@ codecov.yml @mrz1836
3740
.github/AGENTS.md @mrz1836
3841
.cursorrules @mrz1836
3942
.github/CLAUDE.md @mrz1836
40-
sweep.yml @mrz1836
4143

4244
# Security and configuration files
4345
.github/SECURITY.md @mrz1836
@@ -46,3 +48,16 @@ sweep.yml @mrz1836
4648
# Repository configuration
4749
.github/labels.yml @mrz1836
4850
.github/dependabot.yml @mrz1836
51+
52+
# Tech Conventions
53+
.github/tech-conventions/* @mrz1836
54+
55+
# Cursor Rules
56+
.cursorrules @mrz1836
57+
58+
# Devcontainer configuration
59+
.devcontainer/* @mrz1836
60+
.devcontainer.json @mrz1836
61+
62+
# Gitpod configuration
63+
.gitpod.yml @mrz1836

.github/actions/download-artifact-resilient/action.yml

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,18 +101,29 @@ runs:
101101
# Check if artifacts exist first (avoid unnecessary retries)
102102
echo "🔍 Checking artifact availability..."
103103
104-
# Fetch artifacts list, handling API errors gracefully
105-
ARTIFACTS_JSON=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts 2>&1) || {
104+
# Fetch artifacts list (best-effort). In some workflow contexts GitHub will
105+
# block listing artifacts via API even though direct downloads work.
106+
# So: if listing fails, we fall back to attempting the download directly.
107+
ARTIFACTS_JSON=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --paginate 2>&1) || {
106108
API_ERROR=$?
107109
echo "⚠️ Failed to fetch artifacts list (exit code: $API_ERROR)"
108110
echo " Response: $ARTIFACTS_JSON"
111+
echo "↪ Falling back to direct download attempt (skipping preflight list)"
112+
113+
DOWNLOAD_CMD="gh run download ${{ github.run_id }} --pattern \"$ARTIFACT_PATTERN\" --dir \"$ARTIFACT_PATH\""
114+
if timeout "$DOWNLOAD_TIMEOUT" bash -c "$DOWNLOAD_CMD"; then
115+
echo "✅ Successfully downloaded artifacts via fallback"
116+
DOWNLOAD_SUCCESS=true
117+
ARTIFACTS_FOUND=1
118+
break
119+
fi
109120
110121
if [ "$CONTINUE_ON_ERROR" = "true" ]; then
111-
echo "::warning title=Artifact API Error::Failed to fetch artifacts list - API may be unavailable or credentials invalid"
122+
echo "::warning title=Artifact API Error::Failed to list/download artifacts - API may be unavailable or credentials invalid"
112123
DOWNLOAD_SUCCESS=false
113124
break
114125
else
115-
echo "::error title=Artifact API Error::Failed to fetch artifacts list - API may be unavailable or credentials invalid"
126+
echo "::error title=Artifact API Error::Failed to list/download artifacts - API may be unavailable or credentials invalid"
116127
exit 1
117128
fi
118129
}
@@ -121,6 +132,15 @@ runs:
121132
if ! echo "$ARTIFACTS_JSON" | jq -e '.artifacts' > /dev/null 2>&1; then
122133
echo "⚠️ Invalid API response (not valid artifacts JSON)"
123134
echo " Response: $ARTIFACTS_JSON"
135+
echo "↪ Falling back to direct download attempt (skipping preflight list)"
136+
137+
DOWNLOAD_CMD="gh run download ${{ github.run_id }} --pattern \"$ARTIFACT_PATTERN\" --dir \"$ARTIFACT_PATH\""
138+
if timeout "$DOWNLOAD_TIMEOUT" bash -c "$DOWNLOAD_CMD"; then
139+
echo "✅ Successfully downloaded artifacts via fallback"
140+
DOWNLOAD_SUCCESS=true
141+
ARTIFACTS_FOUND=1
142+
break
143+
fi
124144
125145
if [ "$CONTINUE_ON_ERROR" = "true" ]; then
126146
echo "::warning title=Invalid API Response::Artifacts API returned invalid response"
Lines changed: 39 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
# ------------------------------------------------------------------------------------
22
# Load Environment Variables Composite Action
33
#
4-
# Purpose: Loads and parses environment configuration from .env.base and .env.custom
4+
# Purpose: Loads and parses environment configuration from modular .github/env/
55
# files into JSON format for use across all GitHub Actions workflows.
66
#
77
# Loading Strategy:
8-
# 1. Load .env.base (required) - contains default configuration
9-
# 2. Load .env.custom (optional) - project-specific overrides
10-
# 3. Merge with custom values taking precedence
8+
# 1. Run .github/env/load-env.sh to load all modular env files
9+
# 2. Extract exported variables to JSON for workflow compatibility
1110
#
1211
# Outputs:
13-
# env-json: JSON object containing all merged environment variables
12+
# env-json: JSON object containing all environment variables
1413
#
1514
# Usage:
1615
# - uses: ./.github/actions/load-env
@@ -21,7 +20,7 @@
2120
# ------------------------------------------------------------------------------------
2221

2322
name: "Load Environment Variables"
24-
description: "Loads and merges environment variables from .env.base and .env.custom files"
23+
description: "Loads environment variables from modular .github/env/ files"
2524

2625
outputs:
2726
env-json:
@@ -30,154 +29,70 @@ outputs:
3029
primary-runner:
3130
description: "Primary runner OS extracted from environment variables"
3231
value: ${{ steps.load-env.outputs.primary-runner }}
33-
base-file-found:
34-
description: "Whether .env.base file was found"
35-
value: ${{ steps.load-env.outputs.base-file-found }}
36-
custom-file-found:
37-
description: "Whether .env.custom file was found"
38-
value: ${{ steps.load-env.outputs.custom-file-found }}
39-
base-var-count:
40-
description: "Number of variables loaded from .env.base"
41-
value: ${{ steps.load-env.outputs.base-var-count }}
42-
custom-var-count:
43-
description: "Number of variables loaded from .env.custom"
44-
value: ${{ steps.load-env.outputs.custom-var-count }}
45-
config-mode:
46-
description: "Configuration mode: new (base+custom) or base-only"
47-
value: ${{ steps.load-env.outputs.config-mode }}
32+
env-file-count:
33+
description: "Number of env files loaded"
34+
value: ${{ steps.load-env.outputs.env-file-count }}
35+
var-count:
36+
description: "Total number of variables loaded"
37+
value: ${{ steps.load-env.outputs.var-count }}
4838

4939
runs:
5040
using: "composite"
5141
steps:
5242
# --------------------------------------------------------------------
53-
# Load and merge environment configuration files
43+
# Load environment configuration from modular env files
5444
# --------------------------------------------------------------------
55-
- name: 🔧 Load environment variables
45+
- name: 🌍 Load environment variables
5646
id: load-env
5747
shell: bash
5848
run: |
5949
echo "📋 Loading environment configuration..."
6050
61-
# Function to parse .env file to JSON
62-
parse_env_file() {
63-
local file="$1"
64-
if [[ -f "$file" ]]; then
65-
cat "$file" | \
66-
grep -v '^#' | \
67-
grep -v '^$' | \
68-
sed 's/#.*$//' | \
69-
sed 's/[[:space:]]*$//' | \
70-
jq -Rs 'split("\n") | map(select(length > 0) | split("=") | select(length == 2) | {(.[0]): .[1]}) | add // {}'
71-
else
72-
echo "{}"
73-
fi
74-
}
51+
LOADER_SCRIPT=".github/env/load-env.sh"
7552
76-
# Function to validate environment variable names and values
77-
validate_env_vars() {
78-
local json="$1"
79-
local source="$2"
80-
81-
echo "🔒 Validating environment variables from $source..."
82-
83-
# Extract all keys and values
84-
local keys=$(echo "$json" | jq -r 'keys[]')
85-
86-
while IFS= read -r key; do
87-
# Skip empty keys
88-
[[ -z "$key" ]] && continue
89-
90-
# Validate key name: must match ^[A-Z_][A-Z0-9_]*$
91-
if ! echo "$key" | grep -qE '^[A-Z_][A-Z0-9_]*$'; then
92-
echo "❌ ERROR: Invalid environment variable name in $source: '$key'" >&2
93-
echo " Variable names must start with uppercase letter or underscore" >&2
94-
echo " and contain only uppercase letters, numbers, and underscores" >&2
95-
exit 1
96-
fi
97-
98-
# Get the value for this key
99-
local value=$(echo "$json" | jq -r --arg k "$key" '.[$k]')
100-
101-
# Validate value length (max 10000 chars to prevent DoS)
102-
if [[ ${#value} -gt 10000 ]]; then
103-
echo "❌ ERROR: Environment variable value too long in $source: '$key'" >&2
104-
echo " Maximum length is 10000 characters, got ${#value}" >&2
105-
exit 1
106-
fi
107-
108-
# Check for suspicious command injection patterns
109-
if echo "$value" | grep -qE '`|\$\(|\$\{|;|&|\||<\(|>|<|\\|'"'"'|"|\x00|[[:cntrl:]]'; then
110-
echo "⚠️ WARNING: Potentially unsafe characters in $source variable '$key'" >&2
111-
echo " Value contains backticks, command substitution, or shell metacharacters" >&2
112-
echo " Value will be treated as a literal string during extraction" >&2
113-
fi
114-
115-
done <<< "$keys"
116-
117-
echo "✅ All variables in $source passed validation"
118-
}
119-
120-
# Load configuration files in order of precedence
121-
BASE_JSON="{}"
122-
CUSTOM_JSON="{}"
123-
124-
# 1. Load base configuration (required)
125-
if [[ -f ".github/.env.base" ]]; then
126-
echo "📂 Loading base configuration from .env.base..."
127-
BASE_JSON=$(parse_env_file ".github/.env.base")
128-
BASE_COUNT=$(echo "$BASE_JSON" | jq 'keys | length')
129-
echo "✅ Loaded $BASE_COUNT base configuration variables"
53+
if [[ ! -f "$LOADER_SCRIPT" ]]; then
54+
echo "❌ ERROR: Environment loader not found at $LOADER_SCRIPT" >&2
55+
exit 1
56+
fi
13057
131-
# Validate base configuration
132-
validate_env_vars "$BASE_JSON" ".env.base"
133-
else
134-
echo "❌ ERROR: Required .env.base file not found!" >&2
58+
# Source the loader script with verbose output
59+
echo "🔄 Loading modular environment files..."
60+
if ! source "$LOADER_SCRIPT" --verbose; then
61+
echo "❌ ERROR: Failed to load environment configuration" >&2
13562
exit 1
13663
fi
13764
138-
# 2. Load custom overrides (optional)
139-
if [[ -f ".github/.env.custom" ]]; then
140-
echo "🎨 Loading custom configuration from .env.custom..."
141-
CUSTOM_JSON=$(parse_env_file ".github/.env.custom")
142-
CUSTOM_COUNT=$(echo "$CUSTOM_JSON" | jq 'keys | length')
143-
echo "✅ Loaded $CUSTOM_COUNT custom override variables"
65+
# Extract all exported variables to JSON for workflow compatibility
66+
echo "📦 Extracting environment variables to JSON..."
67+
ENV_JSON=$(env | grep -E '^[A-Z_][A-Z0-9_]*=' | while IFS='=' read -r key value; do
68+
# Escape special characters in value for JSON
69+
escaped_value=$(printf '%s' "$value" | jq -Rs '.')
70+
echo "{\"$key\": $escaped_value}"
71+
done | jq -s 'add // {}')
14472
145-
# Validate custom configuration
146-
validate_env_vars "$CUSTOM_JSON" ".env.custom"
147-
else
148-
echo "ℹ️ No custom configuration file found (this is optional)"
149-
fi
73+
TOTAL_COUNT=$(echo "$ENV_JSON" | jq 'keys | length')
74+
echo "✅ Extracted $TOTAL_COUNT variables"
15075
151-
# 3. Merge configurations with precedence: custom > base
152-
echo "🔀 Merging configuration files..."
153-
ENV_JSON=$(echo "$BASE_JSON $CUSTOM_JSON" | jq -s 'add')
76+
# Count env files loaded
77+
ENV_FILE_COUNT=$(ls -1 .github/env/*.env 2>/dev/null | wc -l | tr -d ' ')
15478
15579
# Validate merged configuration
15680
if [[ -z "$ENV_JSON" ]] || [[ "$ENV_JSON" == "null" ]] || [[ "$ENV_JSON" == "{}" ]]; then
15781
echo "❌ ERROR: No valid environment variables found!" >&2
158-
echo " Please ensure .env.base exists with valid configuration." >&2
15982
exit 1
16083
fi
16184
162-
# Output final merged configuration
85+
# Output final configuration
16386
echo "env-json<<EOF" >> $GITHUB_OUTPUT
16487
echo "$ENV_JSON" >> $GITHUB_OUTPUT
16588
echo "EOF" >> $GITHUB_OUTPUT
16689
16790
# Log summary
168-
TOTAL_COUNT=$(echo "$ENV_JSON" | jq 'keys | length')
16991
echo "✅ Environment configuration loaded successfully"
17092
echo "📊 Total variables: $TOTAL_COUNT"
93+
echo "📁 Env files loaded: $ENV_FILE_COUNT"
17194
172-
# Show merge summary if both files were used
173-
if [[ -f ".github/.env.custom" ]] && [[ "$CUSTOM_COUNT" -gt 0 ]]; then
174-
echo "📊 Configuration sources:"
175-
echo " - Base (.env.base): $BASE_COUNT variables"
176-
echo " - Custom (.env.custom): $CUSTOM_COUNT overrides"
177-
echo " - Total merged: $TOTAL_COUNT variables"
178-
fi
179-
180-
# Extract and validate PRIMARY_RUNNER for backward compatibility
95+
# Extract and validate PRIMARY_RUNNER
18196
PRIMARY_RUNNER=$(echo "$ENV_JSON" | jq -r '.PRIMARY_RUNNER')
18297
if [[ -z "$PRIMARY_RUNNER" ]] || [[ "$PRIMARY_RUNNER" == "null" ]]; then
18398
echo "❌ ERROR: PRIMARY_RUNNER is not set in the configuration!" >&2
@@ -186,33 +101,6 @@ runs:
186101
echo "primary-runner=$PRIMARY_RUNNER" >> $GITHUB_OUTPUT
187102
echo "🖥️ Primary runner: $PRIMARY_RUNNER"
188103
189-
# Output configuration file discovery information
190-
BASE_FOUND="false"
191-
CUSTOM_FOUND="false"
192-
CONFIG_MODE="new"
193-
194-
if [[ -f ".github/.env.base" ]]; then
195-
BASE_FOUND="true"
196-
fi
197-
198-
if [[ -f ".github/.env.custom" ]]; then
199-
CUSTOM_FOUND="true"
200-
fi
201-
202-
# Determine configuration mode
203-
if [[ "$CUSTOM_FOUND" == "true" ]]; then
204-
CONFIG_MODE="new"
205-
else
206-
CONFIG_MODE="base-only"
207-
fi
208-
209-
# Set default counts for missing variables
210-
BASE_COUNT=${BASE_COUNT:-0}
211-
CUSTOM_COUNT=${CUSTOM_COUNT:-0}
212-
213-
# Output all the discovery information
214-
echo "base-file-found=$BASE_FOUND" >> $GITHUB_OUTPUT
215-
echo "custom-file-found=$CUSTOM_FOUND" >> $GITHUB_OUTPUT
216-
echo "base-var-count=$BASE_COUNT" >> $GITHUB_OUTPUT
217-
echo "custom-var-count=$CUSTOM_COUNT" >> $GITHUB_OUTPUT
218-
echo "config-mode=$CONFIG_MODE" >> $GITHUB_OUTPUT
104+
# Output counts
105+
echo "env-file-count=$ENV_FILE_COUNT" >> $GITHUB_OUTPUT
106+
echo "var-count=$TOTAL_COUNT" >> $GITHUB_OUTPUT
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Repository Features
2+
3+
A comprehensive list of built-in features that ship with this repository.
4+
5+
<br>
6+
7+
---
8+
9+
<br>
10+
11+
* **Continuous Integration on Autopilot** with [GitHub Actions](https://github.com/features/actions) – every push is built, tested, and reported in minutes.
12+
* **Pull‑Request Flow That Merges Itself** thanks to [auto‑merge](../workflows/auto-merge-on-approval.yml) and hands‑free [Dependabot auto‑merge](../workflows/dependabot-auto-merge.yml).
13+
* **One‑Command Builds** powered by battle‑tested [MAGE-X](https://github.com/mrz1836/mage-x) targets for linting, testing, releases, and more.
14+
* **First‑Class Dependency Management** using native [Go Modules](https://github.com/golang/go/wiki/Modules).
15+
* **Uniform Code Style** via [gofumpt](https://github.com/mvdan/gofumpt) plus zero‑noise linting with [golangci‑lint](https://github.com/golangci/golangci-lint).
16+
* **Confidence‑Boosting Tests** with [testify](https://github.com/stretchr/testify), the Go [race detector](https://blog.golang.org/race-detector), crystal‑clear [HTML coverage](https://blog.golang.org/cover) snapshots, and automatic reporting via internal coverage system.
17+
* **Hands‑Free Releases** delivered by [GoReleaser](https://github.com/goreleaser/goreleaser) whenever you create a [new Tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging).
18+
* **Relentless Dependency & Vulnerability Scans** via [Dependabot](https://dependabot.com) (runs daily at 8am to ensure broadcast dependencies are always current), [Nancy](https://github.com/sonatype-nexus-community/nancy), and [govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck).
19+
* **Security Posture by Default** with [CodeQL](https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/about-code-scanning), [OpenSSF Scorecard](https://openssf.org), and secret‑leak detection via [gitleaks](https://github.com/gitleaks/gitleaks).
20+
* **Automatic Syndication** to [pkg.go.dev](https://pkg.go.dev/) on every release for instant godoc visibility.
21+
* **Polished Community Experience** using rich templates for [Issues & PRs](https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-go-broadcastsitory).
22+
* **All the Right Meta Files** (`LICENSE`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SUPPORT.md`, `SECURITY.md`) pre‑filled and ready.
23+
* **Code Ownership** clarified through a [CODEOWNERS](../CODEOWNERS) file, keeping reviews fast and focused.
24+
* **Zero‑Noise Dev Environments** with tuned editor settings ([`.editorconfig`](../../.editorconfig)) plus curated *ignore* files for [VS Code](../../.editorconfig), [Docker](../../.dockerignore), and [Git](../../.gitignore).
25+
* **Label Sync Magic**: your repo labels stay in lock‑step with [.github/labels.yml](../labels.yml).
26+
* **Friendly First PR Workflow** – newcomers get a warm welcome thanks to a dedicated [workflow](../workflows/pull-request-management.yml).
27+
* **Standards‑Compliant Docs** adhering to the [standard‑readme](https://github.com/RichardLitt/standard-readme/blob/master/spec.md) spec.
28+
* **Instant Cloud Workspaces** via [Gitpod](https://gitpod.io/) – spin up a fully configured dev environment with automatic linting and tests.
29+
* **Out‑of‑the‑Box VS Code Happiness** with a preconfigured [Go](https://code.visualstudio.com/docs/languages/go) workspace and [`.vscode`](../../.vscode) folder with all the right settings.
30+
* **Optional Release Broadcasts** to your community via [Slack](https://slack.com), [Discord](https://discord.com), or [Twitter](https://twitter.com) – plug in your webhook.
31+
* **AI Playbook** – machine‑readable guidelines in [tech conventions](../tech-conventions/ai-compliance.md)
32+
* **Go-Pre-commit System** - [High-performance Go-native pre-commit hooks](https://github.com/mrz1836/go-pre-commit) with 17x faster execution—run the same formatting, linting, and tests before every commit, just like CI.
33+
* **Zero Python Dependencies** - Pure Go implementation with [modular environment-based configuration](../env/README.md).
34+
* **DevContainers for Instant Onboarding** – Launch a ready-to-code environment in seconds with [VS Code DevContainers](https://containers.dev/) and the included [.devcontainer.json](../../.devcontainer.json) config.
35+
36+
<br>
37+
38+
---
39+
40+
[← Back to README](../../README.md)

0 commit comments

Comments
 (0)