@hybridlabor-api/aos 4.8.0 → 4.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/{agents.md → AGENTS.md} +3 -1
- package/.agents/nodes.json +3 -1
- package/.agents/vendor-manifest.json +23 -1
- package/.claude/agents/godmode-media-eventtech.md +1 -1
- package/.claude/hooks/conventional-commits.mjs +125 -0
- package/.claude/hooks/env-file-protection.mjs +105 -0
- package/.claude/hooks/go-gate.mjs +101 -81
- package/.claude/hooks/memb-inject.mjs +29 -1
- package/.claude/settings.json +13 -0
- package/.claude/workflows/startcycle-dispatch.mjs +23 -1
- package/.opencode/agents/godmode-media-eventtech.md +1 -1
- package/.opencode/plugins/bdb-aos.js +31 -4
- package/CLAUDE.md +0 -571
- package/README.de.md +1 -1
- package/README.md +1 -1
- package/README.pt.md +1 -1
- package/THIRD_PARTY_NOTICES.md +126 -0
- package/bin/aos-doctor.mjs +1 -1
- package/docs/skills_table.md +1 -1
- package/installer.js +187 -55
- package/package.json +7 -3
- package/packages/aos-cli/README.md +80 -0
- package/packages/aos-cli/bin/aos-cli.mjs +134 -0
- package/packages/aos-cli/core-skills.json +12 -0
- package/packages/aos-cli/extensions/aos.ts +321 -0
- package/packages/aos-cli/package-lock.json +1923 -0
- package/packages/aos-cli/package.json +29 -0
- package/packages/aos-cli/scripts/check-theme.mjs +63 -0
- package/packages/aos-cli/themes/aos.json +97 -0
- package/scripts/build-plugin-manifest.mjs +131 -0
- package/scripts/validate-skills.mjs +81 -6
- package/skills/basic/ao-orchestrator/SKILL.md +116 -0
- package/skills/basic/bdb-eventagency-skill/SKILL.md +252 -0
- package/skills/basic/bdb-shipping-skill/SKILL.md +161 -0
- package/skills/basic/godmode-eventtech/SKILL.md +4 -1
- package/skills/global_config/agenttrail/SKILL.md +6 -1
- package/skills/global_config/aos-project-init/SKILL.md +2 -0
- package/skills/global_config/aos-project-init/assets/AGENTS.template.md +1 -1
- package/skills/global_config/aos-project-init/scripts/aos-project-doctor.mjs +1 -1
- package/skills/global_config/aos-setup/SKILL.md +1 -1
- package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +1 -1
- package/skills/global_config/ask-tim/SKILL.md +7 -7
- package/skills/global_config/bash-script-generator/SKILL.md +201 -0
- package/skills/global_config/bash-script-generator/assets/templates/standard-template.sh +96 -0
- package/skills/global_config/bash-script-generator/docs/bash-scripting-guide.md +729 -0
- package/skills/global_config/bash-script-generator/docs/generation-best-practices.md +193 -0
- package/skills/global_config/bash-script-generator/docs/script-patterns.md +566 -0
- package/skills/global_config/bash-script-generator/docs/text-processing-guide.md +437 -0
- package/skills/global_config/bash-script-generator/examples/log-analyzer.sh +92 -0
- package/skills/global_config/bash-script-generator/scripts/generate_script_template.sh +123 -0
- package/skills/global_config/bash-script-generator/scripts/run_ci_checks.sh +172 -0
- package/skills/global_config/bash-script-generator/scripts/test_generator.sh +413 -0
- package/skills/global_config/bash-script-validator/SKILL.md +249 -0
- package/skills/global_config/bash-script-validator/docs/awk-reference.md +449 -0
- package/skills/global_config/bash-script-validator/docs/bash-reference.md +468 -0
- package/skills/global_config/bash-script-validator/docs/common-mistakes.md +623 -0
- package/skills/global_config/bash-script-validator/docs/grep-reference.md +395 -0
- package/skills/global_config/bash-script-validator/docs/regex-reference.md +391 -0
- package/skills/global_config/bash-script-validator/docs/sed-reference.md +454 -0
- package/skills/global_config/bash-script-validator/docs/shell-reference.md +463 -0
- package/skills/global_config/bash-script-validator/docs/shellcheck-reference.md +399 -0
- package/skills/global_config/bash-script-validator/examples/bad-bash.sh +55 -0
- package/skills/global_config/bash-script-validator/examples/bad-shell.sh +54 -0
- package/skills/global_config/bash-script-validator/examples/good-bash.sh +71 -0
- package/skills/global_config/bash-script-validator/examples/good-shell.sh +69 -0
- package/skills/global_config/bash-script-validator/scripts/run_ci_checks.sh +23 -0
- package/skills/global_config/bash-script-validator/scripts/shellcheck_wrapper.sh +174 -0
- package/skills/global_config/bash-script-validator/scripts/test_validate.sh +446 -0
- package/skills/global_config/bash-script-validator/scripts/validate.sh +512 -0
- package/skills/global_config/ci-pipeline/SKILL.md +135 -0
- package/skills/global_config/deja-memory/SKILL.md +3 -1
- package/skills/global_config/dispatching-parallel-agents/SKILL.md +170 -0
- package/skills/global_config/dockerfile-generator/SKILL.md +1038 -0
- package/skills/global_config/dockerfile-generator/examples/example.dockerignore +95 -0
- package/skills/global_config/dockerfile-generator/examples/golang-distroless.Dockerfile +34 -0
- package/skills/global_config/dockerfile-generator/examples/java-springboot.Dockerfile +45 -0
- package/skills/global_config/dockerfile-generator/examples/nextjs-production.Dockerfile +49 -0
- package/skills/global_config/dockerfile-generator/examples/nodejs-multistage.Dockerfile +55 -0
- package/skills/global_config/dockerfile-generator/examples/python-fastapi.Dockerfile +48 -0
- package/skills/global_config/dockerfile-generator/references/language_specific_guides.md +510 -0
- package/skills/global_config/dockerfile-generator/references/multistage_builds.md +570 -0
- package/skills/global_config/dockerfile-generator/references/optimization_patterns.md +492 -0
- package/skills/global_config/dockerfile-generator/references/security_best_practices.md +375 -0
- package/skills/global_config/dockerfile-generator/scripts/generate_dockerignore.sh +199 -0
- package/skills/global_config/dockerfile-generator/scripts/generate_golang.sh +172 -0
- package/skills/global_config/dockerfile-generator/scripts/generate_java.sh +185 -0
- package/skills/global_config/dockerfile-generator/scripts/generate_nodejs.sh +279 -0
- package/skills/global_config/dockerfile-generator/scripts/generate_python.sh +218 -0
- package/skills/global_config/dockerfile-generator/scripts/test_generator.sh +210 -0
- package/skills/global_config/dockerfile-validator/SKILL.md +300 -0
- package/skills/global_config/dockerfile-validator/examples/.dockerignore.example +34 -0
- package/skills/global_config/dockerfile-validator/examples/bad-example.Dockerfile +37 -0
- package/skills/global_config/dockerfile-validator/examples/golang-distroless.Dockerfile +50 -0
- package/skills/global_config/dockerfile-validator/examples/good-example.Dockerfile +52 -0
- package/skills/global_config/dockerfile-validator/examples/python-optimized.Dockerfile +53 -0
- package/skills/global_config/dockerfile-validator/examples/security-issues.Dockerfile +42 -0
- package/skills/global_config/dockerfile-validator/references/docker_best_practices.md +348 -0
- package/skills/global_config/dockerfile-validator/references/optimization_guide.md +473 -0
- package/skills/global_config/dockerfile-validator/references/security_checklist.md +208 -0
- package/skills/global_config/dockerfile-validator/scripts/dockerfile-validate.sh +699 -0
- package/skills/global_config/dockerfile-validator/scripts/test_validate.sh +35 -0
- package/skills/global_config/dockerfile-validator/tests/fixtures/copy-before-yarn-lock-read.Dockerfile +6 -0
- package/skills/global_config/dockerfile-validator/tests/fixtures/copy-before-yarn.Dockerfile +6 -0
- package/skills/global_config/dockerfile-validator/tests/fixtures/from-platform-nonroot.Dockerfile +7 -0
- package/skills/global_config/dockerfile-validator/tests/test_regression.sh +164 -0
- package/skills/global_config/finishing-a-development-branch/SKILL.md +228 -0
- package/skills/global_config/github-actions-generator/SKILL.md +353 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/composite/action.yml +82 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/docker/Dockerfile +25 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/docker/action.yml +42 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/docker/entrypoint.sh +27 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/javascript/action.yml +33 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/javascript/index.js +50 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/javascript/package.json +27 -0
- package/skills/global_config/github-actions-generator/assets/templates/workflow/basic_workflow.yml +242 -0
- package/skills/global_config/github-actions-generator/assets/templates/workflow/reusable_workflow.yml +106 -0
- package/skills/global_config/github-actions-generator/examples/README.md +147 -0
- package/skills/global_config/github-actions-generator/examples/actions/setup-node-cached/action.yml +93 -0
- package/skills/global_config/github-actions-generator/examples/caching/docker-buildkit.yml +256 -0
- package/skills/global_config/github-actions-generator/examples/security/dependency-review.yml +62 -0
- package/skills/global_config/github-actions-generator/examples/security/sbom-attestation.yml +119 -0
- package/skills/global_config/github-actions-generator/examples/triggers/chatops-commands.yml +475 -0
- package/skills/global_config/github-actions-generator/examples/triggers/repository-dispatch.yml +418 -0
- package/skills/global_config/github-actions-generator/examples/triggers/workflow-orchestration.yml +404 -0
- package/skills/global_config/github-actions-generator/examples/workflows/docker-build-push.yml +68 -0
- package/skills/global_config/github-actions-generator/examples/workflows/go-ci.yml +161 -0
- package/skills/global_config/github-actions-generator/examples/workflows/monorepo-ci.yml +340 -0
- package/skills/global_config/github-actions-generator/examples/workflows/multi-environment-deploy.yml +406 -0
- package/skills/global_config/github-actions-generator/examples/workflows/nodejs-ci.yml +122 -0
- package/skills/global_config/github-actions-generator/examples/workflows/python-ci.yml +157 -0
- package/skills/global_config/github-actions-generator/examples/workflows/scheduled-tasks.yml +376 -0
- package/skills/global_config/github-actions-generator/references/advanced-triggers.md +917 -0
- package/skills/global_config/github-actions-generator/references/best-practices.md +755 -0
- package/skills/global_config/github-actions-generator/references/common-actions.md +715 -0
- package/skills/global_config/github-actions-generator/references/custom-actions.md +320 -0
- package/skills/global_config/github-actions-generator/references/expressions-and-contexts.md +688 -0
- package/skills/global_config/github-actions-generator/references/modern-features.md +421 -0
- package/skills/global_config/github-actions-generator/scripts/test_generator.sh +344 -0
- package/skills/global_config/github-actions-templates/SKILL.md +7 -0
- package/skills/global_config/github-actions-validator/SKILL.md +576 -0
- package/skills/global_config/github-actions-validator/examples/README.md +88 -0
- package/skills/global_config/github-actions-validator/examples/outdated-versions.yml +76 -0
- package/skills/global_config/github-actions-validator/examples/valid-ci.yml +79 -0
- package/skills/global_config/github-actions-validator/examples/with-errors.yml +47 -0
- package/skills/global_config/github-actions-validator/references/act_usage.md +233 -0
- package/skills/global_config/github-actions-validator/references/action_versions.md +122 -0
- package/skills/global_config/github-actions-validator/references/actionlint_usage.md +343 -0
- package/skills/global_config/github-actions-validator/references/common_errors.md +512 -0
- package/skills/global_config/github-actions-validator/references/modern_features.md +384 -0
- package/skills/global_config/github-actions-validator/references/runners.md +317 -0
- package/skills/global_config/github-actions-validator/scripts/install_tools.sh +113 -0
- package/skills/global_config/github-actions-validator/scripts/validate_workflow.sh +910 -0
- package/skills/global_config/github-actions-validator/tests/test_validate_workflow.sh +237 -0
- package/skills/global_config/makefile-generator/SKILL.md +614 -0
- package/skills/global_config/makefile-generator/assets/templates/.gitkeep +1 -0
- package/skills/global_config/makefile-generator/docs/makefile-structure.md +530 -0
- package/skills/global_config/makefile-generator/docs/optimization-guide.md +784 -0
- package/skills/global_config/makefile-generator/docs/patterns-guide.md +642 -0
- package/skills/global_config/makefile-generator/docs/security-guide.md +361 -0
- package/skills/global_config/makefile-generator/docs/targets-guide.md +642 -0
- package/skills/global_config/makefile-generator/docs/variables-guide.md +596 -0
- package/skills/global_config/makefile-generator/scripts/add_standard_targets.sh +539 -0
- package/skills/global_config/makefile-generator/scripts/generate_makefile_template.sh +690 -0
- package/skills/global_config/makefile-generator/test/test_helper_scripts.sh +190 -0
- package/skills/global_config/makefile-validator/SKILL.md +244 -0
- package/skills/global_config/makefile-validator/docs/bake-tool.md +1000 -0
- package/skills/global_config/makefile-validator/docs/best-practices.md +858 -0
- package/skills/global_config/makefile-validator/docs/common-mistakes.md +944 -0
- package/skills/global_config/makefile-validator/examples/bad-makefile.mk +77 -0
- package/skills/global_config/makefile-validator/examples/good-makefile.mk +103 -0
- package/skills/global_config/makefile-validator/scripts/test_validate.sh +382 -0
- package/skills/global_config/makefile-validator/scripts/validate_makefile.sh +712 -0
- package/skills/global_config/{MCP_Manage → mcp-manage}/SKILL.md +3 -3
- package/skills/global_config/plan-canvas/SKILL.md +9 -2
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/ui.js +10 -2
- package/skills/global_config/plan-canvas/scripts/plan-canvas.js +1 -1
- package/skills/global_config/read-the-damn-docs/SKILL.md +175 -0
- package/skills/global_config/requesting-code-review/SKILL.md +98 -0
- package/skills/global_config/requesting-code-review/code-reviewer.md +198 -0
- package/skills/global_config/using-git-worktrees/SKILL.md +170 -0
- package/skills/global_config/verification-before-completion/SKILL.md +123 -0
- package/skills/global_config/writing-plans/SKILL.md +126 -46
- package/skills/global_config/writing-plans-legacy/SKILL.md +152 -0
- package/.claude/CLAUDE.md +0 -12
- package/mcps/RhinoMCP/docs/content/docs/getting-started/gemini.md +0 -61
- /package/{GEMINI.md → RULES.md} +0 -0
|
@@ -0,0 +1,910 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# GitHub Actions Validator - Workflow Validation Script
|
|
3
|
+
# Validates GitHub Actions workflows using actionlint and act
|
|
4
|
+
# Includes version checking and reference file hints
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
+
TOOLS_DIR="${SCRIPT_DIR}/.tools"
|
|
8
|
+
SKILL_DIR="$(dirname "${SCRIPT_DIR}")"
|
|
9
|
+
REFERENCES_DIR="${SKILL_DIR}/references"
|
|
10
|
+
|
|
11
|
+
# Colors for output
|
|
12
|
+
RED='\033[0;31m'
|
|
13
|
+
GREEN='\033[0;32m'
|
|
14
|
+
YELLOW='\033[1;33m'
|
|
15
|
+
BLUE='\033[0;34m'
|
|
16
|
+
CYAN='\033[0;36m'
|
|
17
|
+
NC='\033[0m' # No Color
|
|
18
|
+
|
|
19
|
+
log_info() {
|
|
20
|
+
echo -e "${GREEN}[INFO]${NC} $1"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
log_warn() {
|
|
24
|
+
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
log_error() {
|
|
28
|
+
echo -e "${RED}[ERROR]${NC} $1"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
log_section() {
|
|
32
|
+
echo ""
|
|
33
|
+
echo -e "${BLUE}=== $1 ===${NC}"
|
|
34
|
+
echo ""
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
log_reference() {
|
|
38
|
+
echo -e "${CYAN}[REF]${NC} $1"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Check if a tool is available either in scripts/.tools or on PATH
|
|
42
|
+
tool_exists() {
|
|
43
|
+
local tool_name=$1
|
|
44
|
+
[ -x "${TOOLS_DIR}/${tool_name}" ] || command -v "${tool_name}" &> /dev/null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Check if Docker is running
|
|
48
|
+
check_docker() {
|
|
49
|
+
if ! docker info &> /dev/null 2>&1; then
|
|
50
|
+
return 1
|
|
51
|
+
fi
|
|
52
|
+
return 0
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Pre-check Docker status and inform user early
|
|
56
|
+
precheck_docker() {
|
|
57
|
+
if ! check_docker; then
|
|
58
|
+
log_warn "Docker is not running - act testing will be skipped"
|
|
59
|
+
log_info "To enable full validation, start Docker Desktop or Docker daemon"
|
|
60
|
+
log_info "Continuing with actionlint validation only..."
|
|
61
|
+
echo ""
|
|
62
|
+
return 1
|
|
63
|
+
fi
|
|
64
|
+
return 0
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# Current recommended action versions (December 2025)
|
|
68
|
+
# Format: action_name:current_version:minimum_version
|
|
69
|
+
declare -A ACTION_VERSIONS=(
|
|
70
|
+
["actions/checkout"]="v6:v4"
|
|
71
|
+
["actions/setup-node"]="v6:v4"
|
|
72
|
+
["actions/setup-python"]="v5:v4"
|
|
73
|
+
["actions/setup-java"]="v4:v4"
|
|
74
|
+
["actions/setup-go"]="v5:v4"
|
|
75
|
+
["actions/cache"]="v4:v4"
|
|
76
|
+
["actions/upload-artifact"]="v4:v4"
|
|
77
|
+
["actions/download-artifact"]="v4:v4"
|
|
78
|
+
["docker/setup-buildx-action"]="v3:v3"
|
|
79
|
+
["docker/login-action"]="v3:v3"
|
|
80
|
+
["docker/build-push-action"]="v6:v5"
|
|
81
|
+
["docker/metadata-action"]="v5:v5"
|
|
82
|
+
["aws-actions/configure-aws-credentials"]="v4:v4"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Extract major version from version string (v4.1.1 -> 4, v4 -> 4)
|
|
86
|
+
get_major_version() {
|
|
87
|
+
local version=$1
|
|
88
|
+
# Remove 'v' prefix and get first number
|
|
89
|
+
echo "$version" | sed 's/^v//' | cut -d'.' -f1
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# Check action versions in workflow files
|
|
93
|
+
check_action_versions() {
|
|
94
|
+
local workflow_path=$1
|
|
95
|
+
log_section "Action Version Check"
|
|
96
|
+
|
|
97
|
+
local files_to_check=()
|
|
98
|
+
|
|
99
|
+
if [ -f "$workflow_path" ]; then
|
|
100
|
+
files_to_check+=("$workflow_path")
|
|
101
|
+
elif [ -d "$workflow_path" ]; then
|
|
102
|
+
while IFS= read -r -d '' file; do
|
|
103
|
+
files_to_check+=("$file")
|
|
104
|
+
done < <(find "$workflow_path" -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" \) -print0 2>/dev/null)
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
if [ ${#files_to_check[@]} -eq 0 ]; then
|
|
108
|
+
log_warn "No workflow files found to check"
|
|
109
|
+
return 0
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
local has_issues=0
|
|
113
|
+
local outdated_count=0
|
|
114
|
+
local deprecated_count=0
|
|
115
|
+
local uptodate_count=0
|
|
116
|
+
|
|
117
|
+
for file in "${files_to_check[@]}"; do
|
|
118
|
+
log_info "Checking: $file"
|
|
119
|
+
|
|
120
|
+
# Extract all 'uses:' statements
|
|
121
|
+
while IFS= read -r line; do
|
|
122
|
+
# Skip empty lines and comments
|
|
123
|
+
[[ -z "$line" ]] && continue
|
|
124
|
+
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
|
125
|
+
|
|
126
|
+
# Extract action reference (e.g., actions/checkout@v4 or actions/checkout@sha)
|
|
127
|
+
if [[ "$line" =~ uses:[[:space:]]*([^@]+)@([^[:space:]#]+) ]]; then
|
|
128
|
+
local action="${BASH_REMATCH[1]}"
|
|
129
|
+
local version="${BASH_REMATCH[2]}"
|
|
130
|
+
|
|
131
|
+
# Clean up action name (remove quotes if present)
|
|
132
|
+
action=$(echo "$action" | tr -d '"' | tr -d "'" | xargs)
|
|
133
|
+
|
|
134
|
+
# Check if this action is in our version database
|
|
135
|
+
if [[ -v ACTION_VERSIONS["$action"] ]]; then
|
|
136
|
+
local version_info="${ACTION_VERSIONS[$action]}"
|
|
137
|
+
local current_version="${version_info%%:*}"
|
|
138
|
+
local minimum_version="${version_info##*:}"
|
|
139
|
+
|
|
140
|
+
local current_major=$(get_major_version "$current_version")
|
|
141
|
+
local minimum_major=$(get_major_version "$minimum_version")
|
|
142
|
+
|
|
143
|
+
# Handle SHA pinning - extract version from comment if present
|
|
144
|
+
local used_major=""
|
|
145
|
+
if [[ "$version" =~ ^[0-9a-f]{40}$ ]] || [[ "$version" =~ ^[0-9a-f]{7,}$ ]]; then
|
|
146
|
+
# SHA pinning - try to find version in the same line or comment
|
|
147
|
+
if [[ "$line" =~ v([0-9]+) ]]; then
|
|
148
|
+
used_major="${BASH_REMATCH[1]}"
|
|
149
|
+
else
|
|
150
|
+
# Can't determine version from SHA, skip
|
|
151
|
+
echo " ⚪ ${action}@${version:0:12}... - SHA pinned (version unknown)"
|
|
152
|
+
continue
|
|
153
|
+
fi
|
|
154
|
+
else
|
|
155
|
+
used_major=$(get_major_version "$version")
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
if [ -z "$used_major" ] || ! [[ "$used_major" =~ ^[0-9]+$ ]]; then
|
|
159
|
+
echo " ⚪ ${action}@${version} - Unable to parse version"
|
|
160
|
+
continue
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
if [ "$used_major" -lt "$minimum_major" ]; then
|
|
164
|
+
echo -e " ${RED}❌${NC} ${action}@${version} - ${RED}DEPRECATED${NC} (minimum: ${minimum_version}, using: v${used_major})"
|
|
165
|
+
((deprecated_count++))
|
|
166
|
+
has_issues=1
|
|
167
|
+
elif [ "$used_major" -lt "$current_major" ]; then
|
|
168
|
+
echo -e " ${YELLOW}⚠️${NC} ${action}@${version} - ${YELLOW}OUTDATED${NC} (current: ${current_version}, using: v${used_major})"
|
|
169
|
+
((outdated_count++))
|
|
170
|
+
else
|
|
171
|
+
echo -e " ${GREEN}✅${NC} ${action}@${version} - UP-TO-DATE (current: ${current_version})"
|
|
172
|
+
((uptodate_count++))
|
|
173
|
+
fi
|
|
174
|
+
fi
|
|
175
|
+
fi
|
|
176
|
+
done < "$file"
|
|
177
|
+
done
|
|
178
|
+
|
|
179
|
+
echo ""
|
|
180
|
+
log_info "Version Check Summary:"
|
|
181
|
+
log_info " Up-to-date: $uptodate_count"
|
|
182
|
+
if [ $outdated_count -gt 0 ]; then
|
|
183
|
+
log_warn " Outdated: $outdated_count"
|
|
184
|
+
fi
|
|
185
|
+
if [ $deprecated_count -gt 0 ]; then
|
|
186
|
+
log_error " Deprecated: $deprecated_count"
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
if [ $outdated_count -gt 0 ] || [ $deprecated_count -gt 0 ]; then
|
|
190
|
+
echo ""
|
|
191
|
+
log_info "Recommendations:"
|
|
192
|
+
if [ $deprecated_count -gt 0 ]; then
|
|
193
|
+
log_error " - Update deprecated actions to current versions (see references/action_versions.md)"
|
|
194
|
+
fi
|
|
195
|
+
if [ $outdated_count -gt 0 ]; then
|
|
196
|
+
log_warn " - Consider updating outdated actions for latest features"
|
|
197
|
+
fi
|
|
198
|
+
log_info " - Use SHA pinning for security: action@SHA # vX.Y.Z"
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
return $has_issues
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
# Advisory security hardening checks.
|
|
205
|
+
# These checks emit warnings and do not change overall pass/fail status.
|
|
206
|
+
check_security_policies() {
|
|
207
|
+
local workflow_path=$1
|
|
208
|
+
log_section "Security Policy Checks (Advisory)"
|
|
209
|
+
|
|
210
|
+
local files_to_check=()
|
|
211
|
+
if [ -f "$workflow_path" ]; then
|
|
212
|
+
files_to_check+=("$workflow_path")
|
|
213
|
+
elif [ -d "$workflow_path" ]; then
|
|
214
|
+
while IFS= read -r -d '' file; do
|
|
215
|
+
files_to_check+=("$file")
|
|
216
|
+
done < <(find "$workflow_path" -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" \) -print0 2>/dev/null)
|
|
217
|
+
else
|
|
218
|
+
log_error "Path not found: $workflow_path"
|
|
219
|
+
return 1
|
|
220
|
+
fi
|
|
221
|
+
|
|
222
|
+
if [ ${#files_to_check[@]} -eq 0 ]; then
|
|
223
|
+
log_warn "No workflow files found for security checks"
|
|
224
|
+
return 0
|
|
225
|
+
fi
|
|
226
|
+
|
|
227
|
+
local warning_count=0
|
|
228
|
+
|
|
229
|
+
for file in "${files_to_check[@]}"; do
|
|
230
|
+
log_info "Checking policies in: $file"
|
|
231
|
+
|
|
232
|
+
# 1) Third-party actions should be pinned to full SHA.
|
|
233
|
+
while IFS= read -r match; do
|
|
234
|
+
local line_no="${match%%:*}"
|
|
235
|
+
local line="${match#*:}"
|
|
236
|
+
if [[ "$line" =~ uses:[[:space:]]*([^@[:space:]]+)@([^[:space:]#]+) ]]; then
|
|
237
|
+
local action="${BASH_REMATCH[1]}"
|
|
238
|
+
local version="${BASH_REMATCH[2]}"
|
|
239
|
+
action=$(echo "$action" | tr -d '"' | tr -d "'" | xargs)
|
|
240
|
+
version=$(echo "$version" | tr -d '"' | tr -d "'" | sed 's/[[:space:],]*$//')
|
|
241
|
+
|
|
242
|
+
# Skip local actions/reusable workflows and docker:// references.
|
|
243
|
+
if [[ "$action" == ./* ]] || [[ "$action" == ../* ]] || [[ "$action" == docker://* ]]; then
|
|
244
|
+
continue
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
# Only enforce SHA pinning for third-party actions.
|
|
248
|
+
if [[ "$action" == */* ]]; then
|
|
249
|
+
local owner="${action%%/*}"
|
|
250
|
+
if [ "$owner" != "actions" ] && [ "$owner" != "github" ]; then
|
|
251
|
+
if ! [[ "$version" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
|
252
|
+
log_warn "$file:$line_no third-party action is not SHA pinned: ${action}@${version}"
|
|
253
|
+
((warning_count++))
|
|
254
|
+
fi
|
|
255
|
+
fi
|
|
256
|
+
fi
|
|
257
|
+
fi
|
|
258
|
+
done < <(grep -nE '^[[:space:]]*(-[[:space:]]*)?uses:[[:space:]]*[^[:space:]]+@[^[:space:]#]+' "$file" || true)
|
|
259
|
+
|
|
260
|
+
# 2) Explicit least-privilege permissions.
|
|
261
|
+
if ! grep -qE '^[[:space:]]*permissions:[[:space:]]*' "$file"; then
|
|
262
|
+
log_warn "$file missing explicit permissions block. Add workflow/job-level permissions (or permissions: {})."
|
|
263
|
+
((warning_count++))
|
|
264
|
+
fi
|
|
265
|
+
if grep -qE '^[[:space:]]*permissions:[[:space:]]*write-all([[:space:]]*(#.*)?)?$' "$file"; then
|
|
266
|
+
log_warn "$file uses permissions: write-all. Prefer least-privilege scopes."
|
|
267
|
+
((warning_count++))
|
|
268
|
+
fi
|
|
269
|
+
|
|
270
|
+
# 3) Heuristic detection for untrusted github context in run scripts.
|
|
271
|
+
while IFS= read -r risky_line; do
|
|
272
|
+
log_warn "$file:$risky_line potential script injection risk in run step. Move untrusted input to env and quote it."
|
|
273
|
+
((warning_count++))
|
|
274
|
+
done < <(
|
|
275
|
+
awk '
|
|
276
|
+
function indent_of(s, t) { t=s; sub(/^[ ]*/, "", t); return length(s)-length(t) }
|
|
277
|
+
BEGIN { in_run_block=0; run_indent=-1 }
|
|
278
|
+
{
|
|
279
|
+
line=$0
|
|
280
|
+
indent=indent_of(line)
|
|
281
|
+
if (in_run_block && indent <= run_indent && line !~ /^[[:space:]]*$/) {
|
|
282
|
+
in_run_block=0
|
|
283
|
+
}
|
|
284
|
+
if (line ~ /^[[:space:]]*run:[[:space:]]*[|>][[:space:]]*$/) {
|
|
285
|
+
in_run_block=1
|
|
286
|
+
run_indent=indent
|
|
287
|
+
next
|
|
288
|
+
}
|
|
289
|
+
if (line ~ /^[[:space:]]*run:[[:space:]]*.*\$\{\{[[:space:]]*github\.(event|head_ref|ref_name|actor|triggering_actor|repository_owner|base_ref)/) {
|
|
290
|
+
print NR
|
|
291
|
+
next
|
|
292
|
+
}
|
|
293
|
+
if (in_run_block && line ~ /\$\{\{[[:space:]]*github\.(event|head_ref|ref_name|actor|triggering_actor|repository_owner|base_ref)/) {
|
|
294
|
+
print NR
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
' "$file"
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# 4) OIDC-integrated actions should explicitly request id-token: write.
|
|
301
|
+
if grep -qiE 'uses:[[:space:]]*(aws-actions/configure-aws-credentials|azure/login|google-github-actions/auth|hashicorp/vault-action|actions/attest-build-provenance)@' "$file"; then
|
|
302
|
+
if ! grep -qiE 'id-token:[[:space:]]*write' "$file"; then
|
|
303
|
+
log_warn "$file uses an OIDC-related action but does not declare id-token: write in permissions."
|
|
304
|
+
((warning_count++))
|
|
305
|
+
fi
|
|
306
|
+
fi
|
|
307
|
+
done
|
|
308
|
+
|
|
309
|
+
echo ""
|
|
310
|
+
if [ $warning_count -eq 0 ]; then
|
|
311
|
+
log_info "✓ No security policy warnings found"
|
|
312
|
+
else
|
|
313
|
+
log_warn "Security policy warnings: $warning_count (advisory only; exit code unchanged)"
|
|
314
|
+
log_info "See references/common_errors.md (Security section) and references/modern_features.md"
|
|
315
|
+
fi
|
|
316
|
+
|
|
317
|
+
return 0
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
# Show reference file hints based on error type
|
|
321
|
+
show_reference_hints() {
|
|
322
|
+
local error_output=$1
|
|
323
|
+
echo ""
|
|
324
|
+
log_section "Reference Documentation"
|
|
325
|
+
|
|
326
|
+
local showed_hint=0
|
|
327
|
+
|
|
328
|
+
# Check for various error patterns and suggest references
|
|
329
|
+
if echo "$error_output" | grep -qi "syntax\|yaml\|unexpected"; then
|
|
330
|
+
log_reference "Syntax errors detected - see references/common_errors.md (Syntax Errors section)"
|
|
331
|
+
showed_hint=1
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
if echo "$error_output" | grep -qi "expression\|\${{"; then
|
|
335
|
+
log_reference "Expression errors detected - see references/common_errors.md (Expression Errors section)"
|
|
336
|
+
showed_hint=1
|
|
337
|
+
fi
|
|
338
|
+
|
|
339
|
+
if echo "$error_output" | grep -qi "cron\|schedule"; then
|
|
340
|
+
log_reference "Schedule errors detected - see references/common_errors.md (Schedule Errors section)"
|
|
341
|
+
showed_hint=1
|
|
342
|
+
fi
|
|
343
|
+
|
|
344
|
+
if echo "$error_output" | grep -qi "runner\|runs-on\|ubuntu\|macos\|windows"; then
|
|
345
|
+
log_reference "Runner label issues - see references/runners.md"
|
|
346
|
+
showed_hint=1
|
|
347
|
+
fi
|
|
348
|
+
|
|
349
|
+
if echo "$error_output" | grep -qi "action\|uses:"; then
|
|
350
|
+
log_reference "Action issues detected - see references/common_errors.md (Action Errors section)"
|
|
351
|
+
showed_hint=1
|
|
352
|
+
fi
|
|
353
|
+
|
|
354
|
+
if echo "$error_output" | grep -qi "docker\|container"; then
|
|
355
|
+
log_reference "Docker/container issues - see references/act_usage.md (Troubleshooting section)"
|
|
356
|
+
showed_hint=1
|
|
357
|
+
fi
|
|
358
|
+
|
|
359
|
+
if echo "$error_output" | grep -qi "needs:\|dependency\|job"; then
|
|
360
|
+
log_reference "Job dependency issues - see references/common_errors.md (Job Configuration Errors section)"
|
|
361
|
+
showed_hint=1
|
|
362
|
+
fi
|
|
363
|
+
|
|
364
|
+
if echo "$error_output" | grep -qi "injection\|security\|secret\|untrusted"; then
|
|
365
|
+
log_reference "Security issues detected - see references/common_errors.md (Security section)"
|
|
366
|
+
showed_hint=1
|
|
367
|
+
fi
|
|
368
|
+
|
|
369
|
+
if echo "$error_output" | grep -qi "workflow_call\|reusable\|oidc\|id-token\|attestation\|environment:\|permissions:"; then
|
|
370
|
+
log_reference "Modern features - see references/modern_features.md"
|
|
371
|
+
showed_hint=1
|
|
372
|
+
fi
|
|
373
|
+
|
|
374
|
+
if echo "$error_output" | grep -qi "version\|deprecated\|outdated\|v[0-9]"; then
|
|
375
|
+
log_reference "Action versions - see references/action_versions.md"
|
|
376
|
+
showed_hint=1
|
|
377
|
+
fi
|
|
378
|
+
|
|
379
|
+
if [ $showed_hint -eq 0 ]; then
|
|
380
|
+
log_reference "No direct mapping found for this error output"
|
|
381
|
+
log_reference "Fallback: check references/common_errors.md, then search the exact error text in official docs"
|
|
382
|
+
log_reference "Include exact tool output, workflow file, and line number in your report"
|
|
383
|
+
fi
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
# Validate required tools for selected execution mode
|
|
387
|
+
check_tools() {
|
|
388
|
+
local run_actionlint=$1
|
|
389
|
+
local run_act=$2
|
|
390
|
+
local allow_fallback=$3
|
|
391
|
+
local missing_tools=0
|
|
392
|
+
|
|
393
|
+
if [ "$run_actionlint" = true ] && ! tool_exists "actionlint"; then
|
|
394
|
+
if [ "$allow_fallback" = true ] && [ "$run_act" = true ] && tool_exists "act"; then
|
|
395
|
+
log_warn "actionlint not found. Falling back to act-only validation."
|
|
396
|
+
run_actionlint=false
|
|
397
|
+
else
|
|
398
|
+
log_error "actionlint not found. Please run install_tools.sh first."
|
|
399
|
+
missing_tools=1
|
|
400
|
+
fi
|
|
401
|
+
fi
|
|
402
|
+
|
|
403
|
+
if [ "$run_act" = true ] && ! tool_exists "act"; then
|
|
404
|
+
if [ "$allow_fallback" = true ] && [ "$run_actionlint" = true ] && tool_exists "actionlint"; then
|
|
405
|
+
log_warn "act not found. Falling back to actionlint-only validation."
|
|
406
|
+
run_act=false
|
|
407
|
+
else
|
|
408
|
+
log_error "act not found. Please run install_tools.sh first."
|
|
409
|
+
missing_tools=1
|
|
410
|
+
fi
|
|
411
|
+
fi
|
|
412
|
+
|
|
413
|
+
if [ $missing_tools -eq 1 ]; then
|
|
414
|
+
log_info "Run: bash ${SCRIPT_DIR}/install_tools.sh"
|
|
415
|
+
exit 1
|
|
416
|
+
fi
|
|
417
|
+
|
|
418
|
+
CHECK_TOOLS_RUN_ACTIONLINT=$run_actionlint
|
|
419
|
+
CHECK_TOOLS_RUN_ACT=$run_act
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
# Get the appropriate tool path
|
|
423
|
+
get_tool_path() {
|
|
424
|
+
local tool_name=$1
|
|
425
|
+
|
|
426
|
+
if [ -f "${TOOLS_DIR}/${tool_name}" ]; then
|
|
427
|
+
echo "${TOOLS_DIR}/${tool_name}"
|
|
428
|
+
elif command -v "${tool_name}" &> /dev/null; then
|
|
429
|
+
command -v "${tool_name}"
|
|
430
|
+
else
|
|
431
|
+
log_error "${tool_name} not found"
|
|
432
|
+
exit 1
|
|
433
|
+
fi
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
# Validate workflow with actionlint.
|
|
437
|
+
# Captures output to global ACTIONLINT_OUTPUT (for reference hints) while printing it.
|
|
438
|
+
validate_with_actionlint() {
|
|
439
|
+
local workflow_path=$1
|
|
440
|
+
log_section "Running actionlint"
|
|
441
|
+
|
|
442
|
+
local actionlint_path
|
|
443
|
+
actionlint_path=$(get_tool_path "actionlint")
|
|
444
|
+
|
|
445
|
+
local output=""
|
|
446
|
+
local actionlint_exit=0
|
|
447
|
+
|
|
448
|
+
if [ -f "$workflow_path" ]; then
|
|
449
|
+
log_info "Validating: $workflow_path"
|
|
450
|
+
output=$("${actionlint_path}" "$workflow_path" 2>&1) || actionlint_exit=$?
|
|
451
|
+
[ -n "$output" ] && echo "$output"
|
|
452
|
+
ACTIONLINT_OUTPUT="$output"
|
|
453
|
+
if [ $actionlint_exit -eq 0 ]; then
|
|
454
|
+
log_info "✓ actionlint validation passed"
|
|
455
|
+
return 0
|
|
456
|
+
else
|
|
457
|
+
log_error "✗ actionlint found issues"
|
|
458
|
+
return 1
|
|
459
|
+
fi
|
|
460
|
+
elif [ -d "$workflow_path" ]; then
|
|
461
|
+
log_info "Validating all workflows in: $workflow_path"
|
|
462
|
+
|
|
463
|
+
# Find all .yml and .yaml files
|
|
464
|
+
local workflow_files=()
|
|
465
|
+
while IFS= read -r -d '' file; do
|
|
466
|
+
workflow_files+=("$file")
|
|
467
|
+
done < <(find "$workflow_path" -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" \) -print0 2>/dev/null)
|
|
468
|
+
|
|
469
|
+
if [ ${#workflow_files[@]} -eq 0 ]; then
|
|
470
|
+
log_warn "No workflow files found in: $workflow_path"
|
|
471
|
+
return 0
|
|
472
|
+
fi
|
|
473
|
+
|
|
474
|
+
output=$("${actionlint_path}" "${workflow_files[@]}" 2>&1) || actionlint_exit=$?
|
|
475
|
+
[ -n "$output" ] && echo "$output"
|
|
476
|
+
ACTIONLINT_OUTPUT="$output"
|
|
477
|
+
if [ $actionlint_exit -eq 0 ]; then
|
|
478
|
+
log_info "✓ actionlint validation passed for ${#workflow_files[@]} file(s)"
|
|
479
|
+
return 0
|
|
480
|
+
else
|
|
481
|
+
log_error "✗ actionlint found issues"
|
|
482
|
+
return 1
|
|
483
|
+
fi
|
|
484
|
+
else
|
|
485
|
+
log_error "Path not found: $workflow_path"
|
|
486
|
+
return 1
|
|
487
|
+
fi
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
# Test workflow with act
|
|
491
|
+
test_with_act() {
|
|
492
|
+
local workflow_path=$1
|
|
493
|
+
log_section "Running act (validation)"
|
|
494
|
+
ACT_SKIP_REASON=""
|
|
495
|
+
|
|
496
|
+
# Check if Docker is running
|
|
497
|
+
if ! check_docker; then
|
|
498
|
+
log_error "Docker is not running!"
|
|
499
|
+
log_warn "act requires Docker to validate and test workflows."
|
|
500
|
+
log_warn ""
|
|
501
|
+
log_warn "Solutions:"
|
|
502
|
+
log_warn " 1. Start Docker Desktop or Docker daemon"
|
|
503
|
+
log_warn " 2. Use --lint-only flag to skip act testing"
|
|
504
|
+
log_warn ""
|
|
505
|
+
return 1
|
|
506
|
+
fi
|
|
507
|
+
|
|
508
|
+
local act_path=$(get_tool_path "act")
|
|
509
|
+
|
|
510
|
+
# Convert workflow_path to absolute path
|
|
511
|
+
local abs_workflow_path="$workflow_path"
|
|
512
|
+
if [[ ! "$abs_workflow_path" = /* ]]; then
|
|
513
|
+
abs_workflow_path="$(cd "$(dirname "$workflow_path")" 2>/dev/null && pwd)/$(basename "$workflow_path")" || abs_workflow_path="$workflow_path"
|
|
514
|
+
fi
|
|
515
|
+
|
|
516
|
+
# Find the repository root (where .github/workflows exists)
|
|
517
|
+
local repo_root=""
|
|
518
|
+
local search_path="$abs_workflow_path"
|
|
519
|
+
|
|
520
|
+
# If workflow_path is a file, get its directory for searching
|
|
521
|
+
if [ -f "$search_path" ]; then
|
|
522
|
+
search_path="$(dirname "$search_path")"
|
|
523
|
+
fi
|
|
524
|
+
|
|
525
|
+
# Search upwards for .github/workflows directory
|
|
526
|
+
local current_dir="$search_path"
|
|
527
|
+
while [ "$current_dir" != "/" ]; do
|
|
528
|
+
if [ -d "$current_dir/.github/workflows" ]; then
|
|
529
|
+
repo_root="$current_dir"
|
|
530
|
+
break
|
|
531
|
+
fi
|
|
532
|
+
# Also check if we're inside .github/workflows
|
|
533
|
+
if [[ "$current_dir" == *"/.github/workflows"* ]] || [[ "$current_dir" == *"/.github/workflows" ]]; then
|
|
534
|
+
# Extract the part before .github
|
|
535
|
+
repo_root="${current_dir%%/.github/workflows*}"
|
|
536
|
+
if [ -d "$repo_root/.github/workflows" ]; then
|
|
537
|
+
break
|
|
538
|
+
fi
|
|
539
|
+
fi
|
|
540
|
+
current_dir="$(dirname "$current_dir")"
|
|
541
|
+
done
|
|
542
|
+
|
|
543
|
+
# Fallback to current directory
|
|
544
|
+
if [ -z "$repo_root" ] && [ -d "./.github/workflows" ]; then
|
|
545
|
+
repo_root="$(pwd)"
|
|
546
|
+
fi
|
|
547
|
+
|
|
548
|
+
if [ -z "$repo_root" ]; then
|
|
549
|
+
log_warn "No .github/workflows directory found in path hierarchy"
|
|
550
|
+
log_warn "Skipping act validation - workflows must be in .github/workflows/ directory"
|
|
551
|
+
log_info "Searched from: $workflow_path"
|
|
552
|
+
ACT_SKIP_REASON="no .github/workflows directory found in path hierarchy"
|
|
553
|
+
return 2
|
|
554
|
+
fi
|
|
555
|
+
|
|
556
|
+
log_info "Repository root: $repo_root"
|
|
557
|
+
|
|
558
|
+
# Determine the workflow file(s) to validate with act
|
|
559
|
+
# act requires workflows to be in .github/workflows/ directory
|
|
560
|
+
local workflow_flag=""
|
|
561
|
+
local target_description=""
|
|
562
|
+
|
|
563
|
+
if [ -f "$abs_workflow_path" ]; then
|
|
564
|
+
# Check if the file is inside .github/workflows
|
|
565
|
+
if [[ "$abs_workflow_path" == *"/.github/workflows/"* ]]; then
|
|
566
|
+
# File is in .github/workflows - use -W flag with relative path
|
|
567
|
+
workflow_flag="-W ${abs_workflow_path#$repo_root/}"
|
|
568
|
+
target_description="workflow: $(basename "$abs_workflow_path")"
|
|
569
|
+
else
|
|
570
|
+
# File is outside .github/workflows (e.g., examples/)
|
|
571
|
+
# act cannot directly validate files outside .github/workflows
|
|
572
|
+
log_warn "Target file is outside .github/workflows/: $abs_workflow_path"
|
|
573
|
+
log_warn "act can only validate workflows in .github/workflows/ directory"
|
|
574
|
+
log_info "Skipping act validation for this file"
|
|
575
|
+
log_info "Note: actionlint validation still applies to this file"
|
|
576
|
+
ACT_SKIP_REASON="target file is outside .github/workflows"
|
|
577
|
+
return 2
|
|
578
|
+
fi
|
|
579
|
+
elif [ -d "$abs_workflow_path" ]; then
|
|
580
|
+
# Directory specified
|
|
581
|
+
if [[ "$abs_workflow_path" == *"/.github/workflows"* ]] || [[ "$abs_workflow_path" == "$repo_root/.github/workflows" ]]; then
|
|
582
|
+
# It's the .github/workflows directory - validate all workflows
|
|
583
|
+
workflow_flag=""
|
|
584
|
+
target_description="all workflows in .github/workflows/"
|
|
585
|
+
else
|
|
586
|
+
# Directory outside .github/workflows
|
|
587
|
+
log_warn "Target directory is outside .github/workflows/: $abs_workflow_path"
|
|
588
|
+
log_warn "act can only validate workflows in .github/workflows/ directory"
|
|
589
|
+
log_info "Skipping act validation for this directory"
|
|
590
|
+
ACT_SKIP_REASON="target directory is outside .github/workflows"
|
|
591
|
+
return 2
|
|
592
|
+
fi
|
|
593
|
+
fi
|
|
594
|
+
|
|
595
|
+
# Save current directory
|
|
596
|
+
local original_dir="$(pwd)"
|
|
597
|
+
|
|
598
|
+
# Change to repository root for act
|
|
599
|
+
cd "$repo_root" || {
|
|
600
|
+
log_error "Failed to change to repository root: $repo_root"
|
|
601
|
+
return 1
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
log_info "Target: $target_description"
|
|
605
|
+
log_info "Step 1: Listing workflows..."
|
|
606
|
+
echo ""
|
|
607
|
+
|
|
608
|
+
# Define default runner images to avoid interactive prompts
|
|
609
|
+
# Using medium-sized images for good compatibility without huge downloads
|
|
610
|
+
local runner_images=(
|
|
611
|
+
"-P" "ubuntu-latest=catthehacker/ubuntu:act-latest"
|
|
612
|
+
"-P" "ubuntu-22.04=catthehacker/ubuntu:act-22.04"
|
|
613
|
+
"-P" "ubuntu-20.04=catthehacker/ubuntu:act-20.04"
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
# Use --list to show available workflows
|
|
617
|
+
# This validates that workflows can be parsed
|
|
618
|
+
local list_cmd="${act_path} --list ${workflow_flag} ${runner_images[*]}"
|
|
619
|
+
log_info "Running: act --list ${workflow_flag}"
|
|
620
|
+
if ! eval "${list_cmd}" 2>&1 | head -30; then
|
|
621
|
+
log_warn "Could not list workflows - this may indicate parsing issues"
|
|
622
|
+
echo ""
|
|
623
|
+
else
|
|
624
|
+
echo ""
|
|
625
|
+
log_info "✓ Workflow listing successful"
|
|
626
|
+
fi
|
|
627
|
+
|
|
628
|
+
echo ""
|
|
629
|
+
log_info "Step 2: Validating workflow syntax with dry-run..."
|
|
630
|
+
log_info "Note: This validates workflow structure without executing jobs"
|
|
631
|
+
log_info "Using medium-sized runner images (catthehacker/ubuntu:act-*)"
|
|
632
|
+
echo ""
|
|
633
|
+
|
|
634
|
+
# Run act in dry-run mode
|
|
635
|
+
# --dryrun: validates without executing
|
|
636
|
+
# --container-architecture: ensures consistent platform
|
|
637
|
+
# -W: specifies the workflow file to validate
|
|
638
|
+
# -P: specifies runner images to avoid interactive prompt
|
|
639
|
+
# 2>&1: capture both stdout and stderr
|
|
640
|
+
local act_output
|
|
641
|
+
local act_exit_code
|
|
642
|
+
|
|
643
|
+
local dryrun_cmd="${act_path} --dryrun ${workflow_flag} --container-architecture linux/amd64 ${runner_images[*]}"
|
|
644
|
+
log_info "Running: act --dryrun ${workflow_flag} --container-architecture linux/amd64"
|
|
645
|
+
act_output=$(eval "${dryrun_cmd}" 2>&1)
|
|
646
|
+
act_exit_code=$?
|
|
647
|
+
|
|
648
|
+
# Display output
|
|
649
|
+
echo "$act_output"
|
|
650
|
+
echo ""
|
|
651
|
+
|
|
652
|
+
# Interpret results
|
|
653
|
+
if [ $act_exit_code -eq 0 ]; then
|
|
654
|
+
log_info "✓ act validation passed"
|
|
655
|
+
cd "$original_dir"
|
|
656
|
+
return 0
|
|
657
|
+
else
|
|
658
|
+
# Check for specific error conditions
|
|
659
|
+
if echo "$act_output" | grep -qi "EOF"; then
|
|
660
|
+
log_error "✗ act encountered EOF error"
|
|
661
|
+
log_warn "This should not happen with -P flags set"
|
|
662
|
+
log_info "Try running: act --list manually to diagnose"
|
|
663
|
+
cd "$original_dir"
|
|
664
|
+
return 1
|
|
665
|
+
elif echo "$act_output" | grep -q "unable to get git repo"; then
|
|
666
|
+
log_warn "Not a git repository - some act features limited"
|
|
667
|
+
log_info "act validation completed with warnings"
|
|
668
|
+
cd "$original_dir"
|
|
669
|
+
return 0
|
|
670
|
+
elif echo "$act_output" | grep -qi "pull access denied\|image.*not found"; then
|
|
671
|
+
log_error "✗ Docker image pull failed"
|
|
672
|
+
log_warn "Cannot pull runner images. This may be due to:"
|
|
673
|
+
log_warn " - Docker registry connectivity issues"
|
|
674
|
+
log_warn " - Rate limiting"
|
|
675
|
+
log_warn "First-time run will download ~500MB of images"
|
|
676
|
+
cd "$original_dir"
|
|
677
|
+
return 1
|
|
678
|
+
elif echo "$act_output" | grep -qi "error\|failed"; then
|
|
679
|
+
log_error "✗ act validation failed (exit code: $act_exit_code)"
|
|
680
|
+
log_warn "This may indicate:"
|
|
681
|
+
log_warn " - Workflow syntax errors"
|
|
682
|
+
log_warn " - Invalid action references"
|
|
683
|
+
log_warn " - Docker image issues"
|
|
684
|
+
log_warn " - Configuration problems"
|
|
685
|
+
cd "$original_dir"
|
|
686
|
+
return 1
|
|
687
|
+
else
|
|
688
|
+
log_warn "act completed with warnings (exit code: $act_exit_code)"
|
|
689
|
+
cd "$original_dir"
|
|
690
|
+
return 0
|
|
691
|
+
fi
|
|
692
|
+
fi
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
# Display usage
|
|
696
|
+
# Optional first argument: exit code (default 0, pass 1 for error paths)
|
|
697
|
+
usage() {
|
|
698
|
+
local exit_code="${1:-0}"
|
|
699
|
+
echo "Usage: $0 [OPTIONS] <workflow-file-or-directory>"
|
|
700
|
+
echo ""
|
|
701
|
+
echo "Options:"
|
|
702
|
+
echo " --lint-only Run only actionlint validation"
|
|
703
|
+
echo " --test-only Run only act testing (requires Docker)"
|
|
704
|
+
echo " --check-versions Check action versions against recommended versions"
|
|
705
|
+
echo " --policy-checks Run advisory security policy checks (warnings only)"
|
|
706
|
+
echo " --help Display this help message"
|
|
707
|
+
echo ""
|
|
708
|
+
echo "Examples:"
|
|
709
|
+
echo " $0 .github/workflows/ci.yml"
|
|
710
|
+
echo " $0 .github/workflows/"
|
|
711
|
+
echo " $0 --lint-only .github/workflows/ci.yml"
|
|
712
|
+
echo " $0 --test-only .github/workflows/"
|
|
713
|
+
echo " $0 --check-versions .github/workflows/ci.yml"
|
|
714
|
+
echo " $0 --lint-only --policy-checks .github/workflows/ci.yml"
|
|
715
|
+
echo ""
|
|
716
|
+
echo "Requirements:"
|
|
717
|
+
echo " - actionlint: For static analysis (installed via install_tools.sh)"
|
|
718
|
+
echo " - act: For workflow testing (installed via install_tools.sh)"
|
|
719
|
+
echo " - Docker: Required for act to run (must be running)"
|
|
720
|
+
echo ""
|
|
721
|
+
exit "$exit_code"
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
# Main validation
|
|
725
|
+
main() {
|
|
726
|
+
local workflow_path=""
|
|
727
|
+
local lint_only=false
|
|
728
|
+
local test_only=false
|
|
729
|
+
local check_versions=false
|
|
730
|
+
local policy_checks=false
|
|
731
|
+
local version_only=false
|
|
732
|
+
local run_actionlint=true
|
|
733
|
+
local run_act=true
|
|
734
|
+
local allow_tool_fallback=false
|
|
735
|
+
local docker_available=true
|
|
736
|
+
local did_actionlint=false
|
|
737
|
+
local did_act=false
|
|
738
|
+
local act_skip_reason=""
|
|
739
|
+
local act_result=0
|
|
740
|
+
CHECK_TOOLS_RUN_ACTIONLINT=true
|
|
741
|
+
CHECK_TOOLS_RUN_ACT=true
|
|
742
|
+
ACT_SKIP_REASON=""
|
|
743
|
+
|
|
744
|
+
# Parse arguments
|
|
745
|
+
while [[ $# -gt 0 ]]; do
|
|
746
|
+
case $1 in
|
|
747
|
+
--lint-only)
|
|
748
|
+
lint_only=true
|
|
749
|
+
shift
|
|
750
|
+
;;
|
|
751
|
+
--test-only)
|
|
752
|
+
test_only=true
|
|
753
|
+
shift
|
|
754
|
+
;;
|
|
755
|
+
--check-versions)
|
|
756
|
+
check_versions=true
|
|
757
|
+
shift
|
|
758
|
+
;;
|
|
759
|
+
--policy-checks)
|
|
760
|
+
policy_checks=true
|
|
761
|
+
shift
|
|
762
|
+
;;
|
|
763
|
+
--help)
|
|
764
|
+
usage
|
|
765
|
+
;;
|
|
766
|
+
*)
|
|
767
|
+
workflow_path=$1
|
|
768
|
+
shift
|
|
769
|
+
;;
|
|
770
|
+
esac
|
|
771
|
+
done
|
|
772
|
+
|
|
773
|
+
if [ -z "$workflow_path" ]; then
|
|
774
|
+
log_error "No workflow file or directory specified"
|
|
775
|
+
echo ""
|
|
776
|
+
usage 1
|
|
777
|
+
fi
|
|
778
|
+
|
|
779
|
+
if [ "$lint_only" = true ] && [ "$test_only" = true ]; then
|
|
780
|
+
log_error "Cannot combine --lint-only and --test-only"
|
|
781
|
+
exit 1
|
|
782
|
+
fi
|
|
783
|
+
|
|
784
|
+
# Determine mode-specific execution
|
|
785
|
+
if [ "$test_only" = true ]; then
|
|
786
|
+
run_actionlint=false
|
|
787
|
+
run_act=true
|
|
788
|
+
elif [ "$lint_only" = true ]; then
|
|
789
|
+
run_actionlint=true
|
|
790
|
+
run_act=false
|
|
791
|
+
else
|
|
792
|
+
run_actionlint=true
|
|
793
|
+
run_act=true
|
|
794
|
+
allow_tool_fallback=true
|
|
795
|
+
fi
|
|
796
|
+
|
|
797
|
+
if [ "$check_versions" = true ] && [ "$lint_only" = false ] && [ "$test_only" = false ] && [ "$policy_checks" = false ]; then
|
|
798
|
+
version_only=true
|
|
799
|
+
run_actionlint=false
|
|
800
|
+
run_act=false
|
|
801
|
+
allow_tool_fallback=false
|
|
802
|
+
fi
|
|
803
|
+
|
|
804
|
+
log_section "GitHub Actions Validator"
|
|
805
|
+
log_info "Target: $workflow_path"
|
|
806
|
+
|
|
807
|
+
check_tools "$run_actionlint" "$run_act" "$allow_tool_fallback"
|
|
808
|
+
run_actionlint=$CHECK_TOOLS_RUN_ACTIONLINT
|
|
809
|
+
run_act=$CHECK_TOOLS_RUN_ACT
|
|
810
|
+
|
|
811
|
+
# Pre-check Docker status if act testing is enabled
|
|
812
|
+
if [ "$run_act" = true ]; then
|
|
813
|
+
if ! precheck_docker; then
|
|
814
|
+
if [ "$test_only" = true ]; then
|
|
815
|
+
log_error "Docker is required for --test-only mode"
|
|
816
|
+
exit 1
|
|
817
|
+
fi
|
|
818
|
+
if [ "$run_actionlint" = true ]; then
|
|
819
|
+
docker_available=false
|
|
820
|
+
run_act=false
|
|
821
|
+
log_warn "Proceeding without act because Docker is unavailable"
|
|
822
|
+
else
|
|
823
|
+
log_error "Docker is required for act validation in the selected mode"
|
|
824
|
+
exit 1
|
|
825
|
+
fi
|
|
826
|
+
fi
|
|
827
|
+
fi
|
|
828
|
+
|
|
829
|
+
local exit_code=0
|
|
830
|
+
ACTIONLINT_OUTPUT=""
|
|
831
|
+
|
|
832
|
+
# Run version check if requested
|
|
833
|
+
if [ "$check_versions" = true ]; then
|
|
834
|
+
if ! check_action_versions "$workflow_path"; then
|
|
835
|
+
exit_code=1
|
|
836
|
+
fi
|
|
837
|
+
# If only checking versions, exit here
|
|
838
|
+
if [ "$version_only" = true ]; then
|
|
839
|
+
log_section "Version Check Complete"
|
|
840
|
+
exit $exit_code
|
|
841
|
+
fi
|
|
842
|
+
fi
|
|
843
|
+
|
|
844
|
+
# Run actionlint (output is captured and printed inside validate_with_actionlint,
|
|
845
|
+
# and stored in ACTIONLINT_OUTPUT for reference hint routing)
|
|
846
|
+
if [ "$run_actionlint" = true ]; then
|
|
847
|
+
did_actionlint=true
|
|
848
|
+
if ! validate_with_actionlint "$workflow_path"; then
|
|
849
|
+
exit_code=1
|
|
850
|
+
fi
|
|
851
|
+
fi
|
|
852
|
+
|
|
853
|
+
# Run advisory security checks if requested.
|
|
854
|
+
if [ "$policy_checks" = true ]; then
|
|
855
|
+
if ! check_security_policies "$workflow_path"; then
|
|
856
|
+
exit_code=1
|
|
857
|
+
fi
|
|
858
|
+
fi
|
|
859
|
+
|
|
860
|
+
# Run act if enabled and Docker is available
|
|
861
|
+
if [ "$run_act" = true ] && [ "$docker_available" = true ]; then
|
|
862
|
+
test_with_act "$workflow_path"
|
|
863
|
+
act_result=$?
|
|
864
|
+
if [ $act_result -eq 0 ]; then
|
|
865
|
+
did_act=true
|
|
866
|
+
elif [ $act_result -eq 2 ]; then
|
|
867
|
+
act_skip_reason="$ACT_SKIP_REASON"
|
|
868
|
+
log_warn "act validation skipped: $act_skip_reason"
|
|
869
|
+
if [ "$did_actionlint" = false ]; then
|
|
870
|
+
log_error "No effective validator executed: act was skipped and actionlint did not run"
|
|
871
|
+
exit_code=1
|
|
872
|
+
fi
|
|
873
|
+
else
|
|
874
|
+
exit_code=1
|
|
875
|
+
fi
|
|
876
|
+
fi
|
|
877
|
+
|
|
878
|
+
if [ "$version_only" = false ] && [ "$did_actionlint" = false ] && [ "$did_act" = false ]; then
|
|
879
|
+
log_error "No validator executed; refusing to report success"
|
|
880
|
+
exit_code=1
|
|
881
|
+
fi
|
|
882
|
+
|
|
883
|
+
log_section "Validation Summary"
|
|
884
|
+
if [ $exit_code -eq 0 ]; then
|
|
885
|
+
if [ -n "$act_skip_reason" ]; then
|
|
886
|
+
log_info "✓ Validation passed (actionlint completed; act skipped: $act_skip_reason)"
|
|
887
|
+
else
|
|
888
|
+
log_info "✓ All validations passed"
|
|
889
|
+
fi
|
|
890
|
+
else
|
|
891
|
+
log_error "✗ Some validations failed"
|
|
892
|
+
|
|
893
|
+
# Show reference hints based on errors
|
|
894
|
+
if [ -n "$ACTIONLINT_OUTPUT" ]; then
|
|
895
|
+
show_reference_hints "$ACTIONLINT_OUTPUT"
|
|
896
|
+
fi
|
|
897
|
+
|
|
898
|
+
echo ""
|
|
899
|
+
log_info "Tips:"
|
|
900
|
+
log_info " - Review error messages above"
|
|
901
|
+
log_info " - Use --lint-only to skip Docker-dependent tests"
|
|
902
|
+
log_info " - Use --check-versions to check for outdated actions"
|
|
903
|
+
log_info " - Use --policy-checks for security hardening warnings"
|
|
904
|
+
log_info " - Check references/common_errors.md for solutions"
|
|
905
|
+
fi
|
|
906
|
+
|
|
907
|
+
exit $exit_code
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
main "$@"
|