Files
actions/Makefile
Ismo Vuorinen ab371bdebf feat: simplify actions (#353)
* feat: first pass simplification

* refactor: simplify actions repository structure

Major simplification reducing actions from 44 to 30:

Consolidations:
- Merge biome-check + biome-fix → biome-lint (mode: check/fix)
- Merge eslint-check + eslint-fix → eslint-lint (mode: check/fix)
- Merge prettier-check + prettier-fix → prettier-lint (mode: check/fix)
- Merge 5 version-detect actions → language-version-detect (language param)

Removals:
- common-file-check, common-retry (better served by external tools)
- docker-publish-gh, docker-publish-hub (consolidated into docker-publish)
- github-release (redundant with existing tooling)
- set-git-config (no longer needed)
- version-validator (functionality moved to language-version-detect)

Fixes:
- Rewrite docker-publish to use official Docker actions directly
- Update validate-inputs example (eslint-fix → eslint-lint)
- Update tests and documentation for new structure

Result: ~6,000 lines removed, cleaner action catalog, maintained functionality.

* refactor: complete action simplification and cleanup

Remove deprecated actions and update remaining actions:

Removed:
- common-file-check, common-retry: utility actions
- docker-publish-gh, docker-publish-hub: replaced by docker-publish wrapper
- github-release, version-validator, set-git-config: no longer needed
- Various version-detect actions: replaced by language-version-detect

Updated:
- docker-publish: rewrite as simple wrapper using official Docker actions
- validate-inputs: update example (eslint-fix → eslint-lint)
- Multiple actions: update configurations and remove deprecated dependencies
- Tests: update integration/unit tests for new structure
- Documentation: update README, remove test for deleted actions

Configuration updates:
- Linter configs, ignore files for new structure
- Makefile, pyproject.toml updates

* fix: enforce POSIX compliance in GitHub workflows

Convert all workflow shell scripts to POSIX-compliant sh:

Critical fixes:
- Replace bash with sh in all shell declarations
- Replace [[ with [ for test conditions
- Replace == with = for string comparisons
- Replace set -euo pipefail with set -eu
- Split compound AND conditions into separate [ ] tests

Files updated:
- .github/workflows/test-actions.yml (7 shell declarations, 10 test operators)
- .github/workflows/security-suite.yml (set -eu)
- .github/workflows/action-security.yml (2 shell declarations)
- .github/workflows/pr-lint.yml (3 shell declarations)
- .github/workflows/issue-stats.yml (1 shell declaration)

Ensures compatibility with minimal sh implementations and aligns with
CLAUDE.md standards requiring POSIX shell compliance across all scripts.

All tests pass: 764 pytest tests, 100% coverage.

* fix: add missing permissions for private repository support

Add critical permissions to pr-lint workflow for private repositories:

Workflow-level permissions:
+ packages: read - Access private npm/PyPI/Composer packages

Job-level permissions:
+ packages: read - Access private packages during dependency installation
+ checks: write - Create and update check runs

Fixes failures when:
- Installing private npm packages from GitHub Packages
- Installing private Composer dependencies
- Installing private Python packages
- Creating status checks with github-script

Valid permission scopes per actionlint:
actions, attestations, checks, contents, deployments, discussions,
id-token, issues, models, packages, pages, pull-requests,
repository-projects, security-events, statuses

Note: "workflows" and "metadata" are NOT valid permission scopes
(they are PAT-only scopes or auto-granted respectively).

* docs: update readmes

* fix: replace bash-specific 'source' with POSIX '.' command

Replace all occurrences of 'source' with '.' (dot) for POSIX compliance:

Changes in python-lint-fix/action.yml:
- Line 165: source .venv/bin/activate → . .venv/bin/activate
- Line 179: source .venv/bin/activate → . .venv/bin/activate
- Line 211: source .venv/bin/activate → . .venv/bin/activate

Also fixed bash-specific test operator:
- Line 192: [[ "$FAIL_ON_ERROR" == "true" ]] → [ "$FAIL_ON_ERROR" = "true" ]

The 'source' command is bash-specific. POSIX sh uses '.' (dot) to source files.
Both commands have identical functionality but '.' is portable across all
POSIX-compliant shells.

* security: fix code injection vulnerability in docker-publish

Fix CodeQL code injection warning (CWE-094, CWE-095, CWE-116):

Issue: inputs.context was used directly in GitHub Actions expression
without sanitization at line 194, allowing potential code injection
by external users.

Fix: Use environment variable indirection to prevent expression injection:
- Added env.BUILD_CONTEXT to capture inputs.context
- Changed context parameter to use ${{ env.BUILD_CONTEXT }}

Environment variables are evaluated after expression compilation,
preventing malicious code execution during workflow parsing.

Security Impact: Medium severity (CVSS 5.0)
Identified by: GitHub Advanced Security (CodeQL)
Reference: https://github.com/ivuorinen/actions/pull/353#pullrequestreview-3481935924

* security: prevent credential persistence in pr-lint checkout

Add persist-credentials: false to checkout step to mitigate untrusted
checkout vulnerability. This prevents GITHUB_TOKEN from being accessible
to potentially malicious PR code.

Fixes: CodeQL finding CWE-829 (untrusted checkout on privileged workflow)

* fix: prevent security bot from overwriting unrelated comments

Replace broad string matching with unique HTML comment marker for
identifying bot-generated comments. Previously, any comment containing
'Security Analysis' or '🔐 GitHub Actions Permissions' would be
overwritten, causing data loss.

Changes:
- Add unique marker: <!-- security-analysis-bot-comment -->
- Prepend marker to generated comment body
- Update comment identification to use marker only
- Add defensive null check for comment.body

This fixes critical data loss bug where user comments could be
permanently overwritten by the security analysis bot.

Follows same proven pattern as test-actions.yml coverage comments.

* improve: show concise permissions diff instead of full blocks

Replace verbose full-block permissions diff with line-by-line changes.
Now shows only added/removed permissions, making output much more
readable.

Changes:
- Parse permissions into individual lines
- Compare old vs new to identify actual changes
- Show only removed (-) and added (+) lines in diff
- Collapse unchanged permissions into details section (≤3 items)
- Show count summary for many unchanged permissions (>3 items)

Example output:
  Before: 30+ lines showing entire permissions block
  After: 3-5 lines showing only what changed

This addresses user feedback that permissions changes were too verbose.

* security: add input validation and trust model documentation

Add comprehensive security validation for docker-publish action to prevent
code injection attacks (CWE-094, CWE-116).

Changes:
- Add validation for context input (reject absolute paths, warn on URLs)
- Add validation for dockerfile input (reject absolute/URL paths)
- Document security trust model in README
- Add best practices for secure usage
- Explain validation rules and threat model

Prevents malicious actors from:
- Building from arbitrary file system locations
- Fetching Dockerfiles from untrusted remote sources
- Executing code injection through build context manipulation

Addresses: CodeRabbit review comments #2541434325, #2541549615
Fixes: GitHub Advanced Security code injection findings

* security: replace unmaintained nick-fields/retry with step-security/retry

Replace nick-fields/retry with step-security/retry across all 4 actions:
- csharp-build/action.yml
- php-composer/action.yml
- go-build/action.yml
- ansible-lint-fix/action.yml

The nick-fields/retry action has security vulnerabilities and low maintenance.
step-security/retry is a drop-in replacement with full API compatibility.

All inputs (timeout_minutes, max_attempts, command, retry_wait_seconds) are
compatible. Using SHA-pinned version for security.

Addresses CodeRabbit review comment #2541549598

* test: add is_input_required() helper function

Add helper function to check if an action input is required, reducing
duplication across test suites.

The function:
- Takes action_file and input_name as parameters
- Uses validation_core.py to query the 'required' property
- Returns 0 (success) if input is required
- Returns 1 (failure) if input is optional

This DRY improvement addresses CodeRabbit review comment #2541549572

* feat: add mode validation convention mapping

Add "mode" to the validation conventions mapping for lint actions
(eslint-lint, biome-lint, prettier-lint).

Note: The update-validators script doesn't currently recognize "string"
as a validator type, so mode validation coverage remains at 93%. The
actions already have inline validation for mode (check|fix), so this is
primarily for improving coverage metrics.

Addresses part of CodeRabbit review comment #2541549570
(validation coverage improvement)

* docs: fix CLAUDE.md action counts and add missing action

- Update action count from 31 to 29 (line 42)
- Add missing 'action-versioning' to Utilities category (line 74)

Addresses CodeRabbit review comments #2541553130 and #2541553110

* docs: add security considerations to docker-publish

Add security documentation to both action.yml header and README.md:
- Trust model explanation
- Input validation details for context and dockerfile
- Attack prevention information
- Best practices for secure usage

The documentation was previously removed when README was autogenerated.
Now documented in both places to ensure it persists.

* fix: correct step ID reference in docker-build

Fix incorrect step ID reference in platforms output:
- Changed steps.platforms.outputs.built to steps.detect-platforms.outputs.platforms
- The step is actually named 'detect-platforms' not 'platforms'
- Ensures output correctly references the detect-platforms step defined at line 188

* fix: ensure docker-build platforms output is always available

Make detect-platforms step unconditional to fix broken output contract.

The platforms output (line 123) references steps.detect-platforms.outputs.platforms,
but the step only ran when auto-detect-platforms was true (default: false).
This caused undefined output in most cases.

Changes:
- Remove 'if' condition from detect-platforms step
- Step now always runs and always produces platforms output
- When auto-detect is false: outputs configured architectures
- When auto-detect is true: outputs detected platforms or falls back to architectures
- Add '|| true' to grep to prevent errors when no platforms detected

Fixes CodeRabbit review comment #2541824904

* security: remove env var indirection in docker-publish BUILD_CONTEXT

Remove BUILD_CONTEXT env var indirection to address GitHub Advanced Security alert.

The inputs.context is validated at lines 137-147 (rejects absolute paths, warns on URLs)
before being used, so the env var indirection is unnecessary and triggers false positive
code injection warnings.

Changes:
- Remove BUILD_CONTEXT env var (line 254)
- Use inputs.context directly (line 256 → 254)
- Input validation remains in place (lines 137-147)

Fixes GitHub Advanced Security code injection alerts (comments #2541405269, #2541522320)

* feat: implement mode_enum validator for lint actions

Add mode_enum validator to validate mode inputs in linting actions.

Changes to conventions.py:
- Add 'mode_enum' to exact_matches mapping (line 215)
- Add 'mode_enum' to PHP-specific validators list (line 560)
- Implement _validate_mode_enum() method (lines 642-660)
  - Validates mode values against ['check', 'fix']
  - Returns clear error messages for invalid values

Updated rules.yml files:
- biome-lint: Add mode: mode_enum convention
- eslint-lint: Add mode: mode_enum convention
- prettier-lint: Add mode: mode_enum convention
- All rules.yml: Fix YAML formatting with yamlfmt

This addresses PR #353 comment #2541522326 which reported that mode validation
was being skipped due to unrecognized 'string' type, reducing coverage to 93%.

Tested with biome-lint action - correctly rejects invalid values and accepts
valid 'check' and 'fix' values.

* docs: update action count from 29 to 30 in CLAUDE.md

Update two references to action count in CLAUDE.md:
- Line 42: repository_overview memory description
- Line 74: Repository Structure section header

The repository has 30 actions total (29 listed + validate-inputs).

Addresses PR #353 comment #2541549588.

* docs: use pinned version ref in language-version-detect README

Change usage example from @main to @v2025 for security best practices.

Using pinned version refs (instead of @main) ensures:
- Predictable behavior across workflow runs
- Protection against breaking changes
- Better security through immutable references

Follows repository convention documented in main README and CLAUDE.md.

Addresses PR #353 comment #2541549588.

* refactor: remove deprecated add-snippets input from codeql-analysis

Remove add-snippets input which has been deprecated by GitHub's CodeQL action
and no longer has any effect.

Changes:
- Remove add-snippets input definition (lines 93-96)
- Remove reference in init step (line 129)
- Remove reference in analyze step (line 211)
- Regenerate README and rules.yml

This is a non-breaking change since:
- Default was 'false' (minimal usage expected)
- GitHub's action already ignores this parameter
- Aligns with recent repository simplification efforts

* feat: add mode_enum validator and update rules

Add mode_enum validator support for lint actions and regenerate all validation rules:

Validator Changes:
- Add mode_enum to action_overrides for biome-lint, eslint-lint, prettier-lint
- Remove deprecated add-snippets from codeql-analysis overrides

Rules Updates:
- All 29 action rules.yml files regenerated with consistent YAML formatting
- biome-lint, eslint-lint, prettier-lint now validate mode input (check/fix)
- Improved coverage for lint actions (79% → 83% for biome, 93% for eslint, 79% for prettier)

Documentation:
- Fix language-version-detect README to use @v2025 (not @main)
- Remove outdated docker-publish security docs (now handled by official actions)

This completes PR #353 review feedback implementation.

* fix: replace bash-specific $'\n' with POSIX-compliant printf

Replace non-POSIX $'\n' syntax in tag building loop with printf-based
approach that works in any POSIX shell.

Changed:
- Line 216: tags="${tags}"$'\n'"${image}:${tag}"
+ Line 216: tags="$(printf '%s\n%s' "$tags" "${image}:${tag}")"

This ensures docker-publish/action.yml runs correctly on systems using
/bin/sh instead of bash.
2025-11-19 15:42:06 +02:00

731 lines
29 KiB
Makefile
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Makefile for GitHub Actions repository
# Provides organized task management with parallel execution capabilities
.PHONY: help all docs update-catalog lint format check clean install-tools test test-unit test-integration test-coverage generate-tests generate-tests-dry test-generate-tests docker-build docker-push docker-test docker-login docker-all release release-dry release-prep release-tag release-undo update-version-refs bump-major-version check-version-refs
.DEFAULT_GOAL := help
# Colors for output
GREEN := $(shell printf '\033[32m')
YELLOW := $(shell printf '\033[33m')
RED := $(shell printf '\033[31m')
BLUE := $(shell printf '\033[34m')
RESET := $(shell printf '\033[0m')
# Configuration
SHELL := /bin/bash
.SHELLFLAGS := -euo pipefail -c
# Log file with timestamp
LOG_FILE := update_$(shell date +%Y%m%d_%H%M%S).log
# Detect OS for sed compatibility
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
SED_CMD := sed -i .bak
else
SED_CMD := sed -i
endif
# Help target - shows available commands
help: ## Show this help message
@echo "$(BLUE)GitHub Actions Repository Management$(RESET)"
@echo ""
@echo "$(GREEN)Available targets:$(RESET)"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf " $(YELLOW)%-20s$(RESET) %s\n", $$1, $$2}'
@echo ""
@echo "$(GREEN)Examples:$(RESET)"
@echo " make all # Generate docs, format, and lint everything"
@echo " make docs # Generate documentation only"
@echo " make lint # Run all linters"
@echo " make format # Format all files"
@echo " make test # Run all tests (unit + integration)"
@echo " make check # Quick syntax checks"
# Main targets
all: install-tools update-validators docs update-catalog format lint precommit ## Generate docs, format, lint, and run pre-commit
@echo "$(GREEN)✅ All tasks completed successfully$(RESET)"
docs: ## Generate documentation for all actions
@echo "$(BLUE)📂 Generating documentation...$(RESET)"
@failed=0; \
for dir in $$(find . -mindepth 2 -maxdepth 2 -name "action.yml" | sed 's|/action.yml||' | sed 's|./||'); do \
echo "$(BLUE)📄 Updating $$dir/README.md...$(RESET)"; \
repo="ivuorinen/actions/$$dir"; \
printf "# %s\n\n" "$$repo" > "$$dir/README.md"; \
if npx --yes action-docs -n -s "$$dir/action.yml" --no-banner >> "$$dir/README.md" 2>/dev/null; then \
$(SED_CMD) "s|\*\*\*PROJECT\*\*\*|$$repo|g" "$$dir/README.md"; \
$(SED_CMD) "s|\*\*\*VERSION\*\*\*|main|g" "$$dir/README.md"; \
$(SED_CMD) "s|\*\*\*||g" "$$dir/README.md"; \
[ "$(UNAME_S)" = "Darwin" ] && rm -f "$$dir/README.md.bak"; \
echo "$(GREEN)✅ Updated $$dir/README.md$(RESET)"; \
else \
echo "$(RED)⚠️ Failed to update $$dir/README.md$(RESET)" | tee -a $(LOG_FILE); \
failed=$$((failed + 1)); \
fi; \
done; \
[ $$failed -eq 0 ] && echo "$(GREEN)✅ All documentation updated successfully$(RESET)" || { echo "$(RED)$$failed documentation updates failed$(RESET)"; exit 1; }
update-catalog: ## Update action catalog in README.md
@echo "$(BLUE)📚 Updating action catalog...$(RESET)"
@if command -v npm >/dev/null 2>&1; then \
npm run update-catalog; \
else \
echo "$(RED)❌ npm not found. Please install Node.js$(RESET)"; \
exit 1; \
fi
@echo "$(GREEN)✅ Action catalog updated$(RESET)"
update-validators: ## Update validation rules for all actions
@echo "$(BLUE)🔧 Updating validation rules...$(RESET)"
@if command -v uv >/dev/null 2>&1; then \
cd validate-inputs && uv run scripts/update-validators.py; \
else \
echo "$(RED)❌ uv not found. Please install uv (see 'make install-tools')$(RESET)"; \
exit 1; \
fi
@echo "$(GREEN)✅ Validation rules updated$(RESET)"
update-validators-dry: ## Preview validation rules changes (dry run)
@echo "$(BLUE)🔍 Previewing validation rules changes...$(RESET)"
@if command -v uv >/dev/null 2>&1; then \
cd validate-inputs && uv run scripts/update-validators.py --dry-run; \
else \
echo "$(RED)❌ uv not found. Please install uv (see 'make install-tools')$(RESET)"; \
exit 1; \
fi
format: format-markdown format-yaml-json format-python ## Format all files
@echo "$(GREEN)✅ All files formatted$(RESET)"
lint: lint-markdown lint-yaml lint-shell lint-python ## Run all linters
@echo "$(GREEN)✅ All linting completed$(RESET)"
check: check-tools check-syntax check-local-refs ## Quick syntax and tool availability checks
@echo "$(GREEN)✅ All checks passed$(RESET)"
clean: ## Clean up temporary files and caches
@echo "$(BLUE)🧹 Cleaning up...$(RESET)"
@find . -name "*.bak" -delete 2>/dev/null || true
@find . -name "update_*.log" -mtime +7 -delete 2>/dev/null || true
@find . -name ".megalinter" -type d -exec rm -rf {} + 2>/dev/null || true
@echo "$(GREEN)✅ Cleanup completed$(RESET)"
precommit: ## Run pre-commit hooks on all files
@echo "$(BLUE)🔍 Running pre-commit hooks...$(RESET)"
@if command -v pre-commit >/dev/null 2>&1; then \
if PRE_COMMIT_USE_UV=1 pre-commit run --all-files; then \
echo "$(GREEN)✅ All pre-commit hooks passed$(RESET)"; \
else \
echo "$(RED)❌ Some pre-commit hooks failed$(RESET)"; \
exit 1; \
fi; \
else \
echo "$(RED)❌ pre-commit not found. Please install:$(RESET)"; \
echo " brew install pre-commit"; \
echo " or: pip install pre-commit"; \
exit 1; \
fi
# Local action reference validation
check-local-refs: ## Check for ../action-name references that should be ./action-name
@echo "$(BLUE)🔍 Checking local action references...$(RESET)"
@if command -v uv >/dev/null 2>&1; then \
uv run _tools/fix-local-action-refs.py --check; \
else \
echo "$(RED)❌ uv not found. Please install uv (see 'make install-tools')$(RESET)"; \
exit 1; \
fi
fix-local-refs: ## Fix ../action-name references to ./action-name
@echo "$(BLUE)🔧 Fixing local action references...$(RESET)"
@if command -v uv >/dev/null 2>&1; then \
uv run _tools/fix-local-action-refs.py; \
else \
echo "$(RED)❌ uv not found. Please install uv (see 'make install-tools')$(RESET)"; \
exit 1; \
fi
fix-local-refs-dry: ## Preview local action reference fixes (dry run)
@echo "$(BLUE)🔍 Previewing local action reference fixes...$(RESET)"
@if command -v uv >/dev/null 2>&1; then \
uv run _tools/fix-local-action-refs.py --dry-run; \
else \
echo "$(RED)❌ uv not found. Please install uv (see 'make install-tools')$(RESET)"; \
exit 1; \
fi
# Version management targets
release: ## Create a new release with version tags (usage: make release [VERSION=v2025.10.18])
@VERSION_TO_USE=$$(if [ -n "$(VERSION)" ]; then echo "$(VERSION)"; else date +v%Y.%m.%d; fi); \
echo "$(BLUE)🚀 Creating release $$VERSION_TO_USE...$(RESET)"; \
sh _tools/release.sh "$$VERSION_TO_USE"
release-dry: ## Preview release without making changes (usage: make release-dry VERSION=v2025.11.01)
@if [ -z "$(VERSION)" ]; then \
VERSION_TO_USE=$$(date +v%Y.%m.%d); \
else \
VERSION_TO_USE="$(VERSION)"; \
fi; \
echo "$(BLUE)🔍 Previewing release $$VERSION_TO_USE (dry run)...$(RESET)"; \
sh _tools/release.sh --dry-run "$$VERSION_TO_USE"
release-prep: ## Update action refs and commit (no tags) (usage: make release-prep [VERSION=v2025.11.01])
@VERSION_TO_USE=$$(if [ -n "$(VERSION)" ]; then echo "$(VERSION)"; else date +v%Y.%m.%d; fi); \
echo "$(BLUE)🔧 Preparing release $$VERSION_TO_USE...$(RESET)"; \
sh _tools/release.sh --prep-only "$$VERSION_TO_USE"; \
echo "$(GREEN)✅ Preparation complete$(RESET)"; \
echo "$(YELLOW)Next: make release-tag VERSION=$$VERSION_TO_USE$(RESET)"
release-tag: ## Create tags only (assumes prep done) (usage: make release-tag VERSION=v2025.11.01)
@if [ -z "$(VERSION)" ]; then \
echo "$(RED)❌ Error: VERSION parameter required for release-tag$(RESET)"; \
echo "Usage: make release-tag VERSION=v2025.11.01"; \
exit 1; \
fi; \
echo "$(BLUE)🏷️ Creating tags for release $(VERSION)...$(RESET)"; \
sh _tools/release.sh --tag-only "$(VERSION)"
release-undo: ## Rollback the most recent release (delete tags and reset HEAD)
@echo "$(BLUE)🔙 Rolling back release...$(RESET)"; \
sh _tools/release-undo.sh
update-version-refs: ## Update all action references to a specific version tag (usage: make update-version-refs MAJOR=v2025)
@if [ -z "$(MAJOR)" ]; then \
echo "$(RED)❌ Error: MAJOR parameter required$(RESET)"; \
echo "Usage: make update-version-refs MAJOR=v2025"; \
exit 1; \
fi
@echo "$(BLUE)🔧 Updating action references to $(MAJOR)...$(RESET)"
@sh _tools/update-action-refs.sh "$(MAJOR)"
@echo "$(GREEN)✅ Action references updated$(RESET)"
bump-major-version: ## Replace one major version with another (usage: make bump-major-version OLD=v2025 NEW=v2026)
@if [ -z "$(OLD)" ] || [ -z "$(NEW)" ]; then \
echo "$(RED)❌ Error: OLD and NEW parameters required$(RESET)"; \
echo "Usage: make bump-major-version OLD=v2025 NEW=v2026"; \
exit 1; \
fi
@echo "$(BLUE)🔄 Bumping version from $(OLD) to $(NEW)...$(RESET)"
@sh _tools/bump-major-version.sh "$(OLD)" "$(NEW)"
@echo "$(GREEN)✅ Major version bumped$(RESET)"
check-version-refs: ## List all current SHA-pinned action references
@echo "$(BLUE)🔍 Checking action references...$(RESET)"
@sh _tools/check-version-refs.sh
# Formatting targets
format-markdown: ## Format markdown files
@echo "$(BLUE)📝 Formatting markdown...$(RESET)"
@if npx --yes markdownlint-cli2 --fix "**/*.md" "#node_modules" "#.worktrees" 2>/dev/null; then \
echo "$(GREEN)✅ Markdown formatted$(RESET)"; \
else \
echo "$(YELLOW)⚠️ Markdown formatting issues found$(RESET)" | tee -a $(LOG_FILE); \
fi
format-yaml-json: ## Format YAML and JSON files
@echo "$(BLUE)✨ Formatting YAML/JSON...$(RESET)"
@if command -v yamlfmt >/dev/null 2>&1; then \
if yamlfmt . 2>/dev/null; then \
echo "$(GREEN)✅ YAML formatted with yamlfmt$(RESET)"; \
else \
echo "$(YELLOW)⚠️ YAML formatting issues found with yamlfmt$(RESET)" | tee -a $(LOG_FILE); \
fi; \
else \
echo "$(BLUE) yamlfmt not available, skipping$(RESET)"; \
fi
@if npx --yes prettier --write "**/*.md" "**/*.yml" "**/*.yaml" "**/*.json" 2>/dev/null; then \
echo "$(GREEN)✅ YAML/JSON formatted with prettier$(RESET)"; \
else \
echo "$(YELLOW)⚠️ YAML/JSON formatting issues found with prettier$(RESET)" | tee -a $(LOG_FILE); \
fi
@echo "$(BLUE)📊 Formatting tables...$(RESET)"
@if npx --yes markdown-table-formatter "**/*.md" 2>/dev/null; then \
echo "$(GREEN)✅ Tables formatted$(RESET)"; \
else \
echo "$(YELLOW)⚠️ Table formatting issues found$(RESET)" | tee -a $(LOG_FILE); \
fi
format-tables: ## Format markdown tables
@echo "$(BLUE)📊 Formatting tables...$(RESET)"
@if npx --yes markdown-table-formatter "**/*.md" 2>/dev/null; then \
echo "$(GREEN)✅ Tables formatted$(RESET)"; \
else \
echo "$(YELLOW)⚠️ Table formatting issues found$(RESET)" | tee -a $(LOG_FILE); \
fi
format-python: ## Format Python files with ruff
@echo "$(BLUE)🐍 Formatting Python files...$(RESET)"
@if command -v uv >/dev/null 2>&1; then \
if uvx ruff format . --no-cache; then \
echo "$(GREEN)✅ Python files formatted$(RESET)"; \
else \
echo "$(YELLOW)⚠️ Python formatting issues found$(RESET)" | tee -a $(LOG_FILE); \
fi; \
else \
echo "$(BLUE) uv not available, skipping Python formatting$(RESET)"; \
fi
# Linting targets
lint-markdown: ## Lint markdown files
@echo "$(BLUE)🔍 Linting markdown...$(RESET)"
@if npx --yes markdownlint-cli2 --fix "**/*.md" "#node_modules" "#.worktrees"; then \
echo "$(GREEN)✅ Markdown linting passed$(RESET)"; \
else \
echo "$(YELLOW)⚠️ Markdown linting issues found$(RESET)" | tee -a $(LOG_FILE); \
fi
lint-yaml: ## Lint YAML files
@echo "$(BLUE)🔍 Linting YAML...$(RESET)"
@if npx --yes yaml-lint "**/*.yml" "**/*.yaml" 2>/dev/null; then \
echo "$(GREEN)✅ YAML linting passed$(RESET)"; \
else \
echo "$(YELLOW)⚠️ YAML linting issues found$(RESET)" | tee -a $(LOG_FILE); \
fi
lint-shell: ## Lint shell scripts
@echo "$(BLUE)🔍 Linting shell scripts...$(RESET)"
@if ! command -v shellcheck >/dev/null 2>&1; then \
echo "$(RED)❌ shellcheck not found. Please install shellcheck:$(RESET)"; \
echo " brew install shellcheck"; \
echo " or: apt-get install shellcheck"; \
exit 1; \
fi
@if find . -name "*.sh" -not -path "./_tests/*" -not -path "./.worktrees/*" -exec shellcheck -x {} +; then \
echo "$(GREEN)✅ Shell linting passed$(RESET)"; \
else \
echo "$(RED)❌ Shell linting issues found$(RESET)"; \
exit 1; \
fi
lint-python: ## Lint Python files with ruff and pyright
@echo "$(BLUE)🔍 Linting Python files...$(RESET)"
@ruff_passed=true; pyright_passed=true; \
if command -v uv >/dev/null 2>&1; then \
uvx ruff check --fix . --no-cache; \
if ! uvx ruff check . --no-cache; then \
echo "$(YELLOW)⚠️ Python linting issues found$(RESET)" | tee -a $(LOG_FILE); \
ruff_passed=false; \
fi; \
if command -v pyright >/dev/null 2>&1; then \
if ! pyright --pythonpath $$(which python3) validate-inputs/ _tests/framework/; then \
echo "$(YELLOW)⚠️ Python type checking issues found$(RESET)" | tee -a $(LOG_FILE); \
pyright_passed=false; \
fi; \
else \
echo "$(BLUE) pyright not available, skipping type checking$(RESET)"; \
fi; \
else \
echo "$(BLUE) uv not available, skipping Python linting$(RESET)"; \
fi; \
if $$ruff_passed && $$pyright_passed; then \
echo "$(GREEN)✅ Python linting and type checking passed$(RESET)"; \
fi
# Check targets
check-tools: ## Check if required tools are available
@echo "$(BLUE)🔧 Checking required tools...$(RESET)"
@for cmd in npx sed find grep shellcheck; do \
if ! command -v $$cmd >/dev/null 2>&1; then \
echo "$(RED)❌ Error: $$cmd not found$(RESET)"; \
echo " Please install $$cmd (see 'make install-tools')"; \
exit 1; \
fi; \
done
@if ! command -v yamlfmt >/dev/null 2>&1; then \
echo "$(YELLOW)⚠️ yamlfmt not found (optional for YAML formatting)$(RESET)"; \
fi
@echo "$(GREEN)✅ All required tools available$(RESET)"
check-syntax: ## Check syntax of shell scripts and YAML files
@echo "$(BLUE)🔍 Checking syntax...$(RESET)"
@failed=0; \
find . -name "*.sh" -not -path "./_tests/*" -not -path "./.worktrees/*" -print0 | while IFS= read -r -d '' file; do \
if ! bash -n "$$file" 2>&1; then \
echo "$(RED)❌ Syntax error in $$file$(RESET)" >&2; \
failed=1; \
fi; \
done; \
if [ "$$failed" -eq 1 ]; then \
echo "$(RED)❌ Shell script syntax errors found$(RESET)"; \
exit 1; \
fi
@echo "$(GREEN)✅ Syntax checks passed$(RESET)"
install-tools: ## Install/update required tools
@echo "$(BLUE)📦 Installing/updating tools...$(RESET)"
@echo "$(YELLOW)Installing NPM tools...$(RESET)"
@npx --yes action-docs@latest --version >/dev/null
@npx --yes markdownlint-cli2 --version >/dev/null
@npx --yes prettier --version >/dev/null
@npx --yes markdown-table-formatter --version >/dev/null
@npx --yes yaml-lint --version >/dev/null
@echo "$(YELLOW)Checking shellcheck...$(RESET)"
@if ! command -v shellcheck >/dev/null 2>&1; then \
echo "$(RED)⚠️ shellcheck not found. Please install:$(RESET)"; \
echo " macOS: brew install shellcheck"; \
echo " Linux: apt-get install shellcheck"; \
else \
echo " shellcheck already installed"; \
fi
@echo "$(YELLOW)Checking yamlfmt...$(RESET)"
@if ! command -v yamlfmt >/dev/null 2>&1; then \
echo "$(RED)⚠️ yamlfmt not found. Please install:$(RESET)"; \
echo " macOS: brew install yamlfmt"; \
echo " Linux: go install github.com/google/yamlfmt/cmd/yamlfmt@latest"; \
else \
echo " yamlfmt already installed"; \
fi
@echo "$(YELLOW)Checking uv...$(RESET)"
@if ! command -v uv >/dev/null 2>&1; then \
echo "$(RED)⚠️ uv not found. Please install:$(RESET)"; \
echo " macOS: brew install uv"; \
echo " Linux: curl -LsSf https://astral.sh/uv/install.sh | sh"; \
echo " Or see: https://docs.astral.sh/uv/getting-started/installation/"; \
exit 1; \
else \
echo " uv already installed"; \
fi
@echo "$(YELLOW)Checking pre-commit...$(RESET)"
@if ! command -v pre-commit >/dev/null 2>&1; then \
echo "$(BLUE) pre-commit not found. Installing via uv tool...$(RESET)"; \
uv tool install pre-commit; \
echo " pre-commit installed"; \
else \
echo " pre-commit already installed"; \
fi
@echo "$(YELLOW)Installing git hooks with pre-commit...$(RESET)"
@if [ -d .git ] && command -v pre-commit >/dev/null 2>&1; then \
if ~/.local/bin/pre-commit install 2>/dev/null || pre-commit install 2>/dev/null; then \
echo " Git hooks installed"; \
fi; \
fi
@echo "$(YELLOW)Installing Python dependencies from pyproject.toml...$(RESET)"
@uv sync --all-extras
@echo " Python dependencies installed"
@echo "$(GREEN)✅ All tools installed/updated$(RESET)"
# Development targets
dev: ## Development workflow - format then lint
@$(MAKE) format
@$(MAKE) lint
dev-python: ## Python development workflow - format, lint, test
@echo "$(BLUE)🐍 Running Python development workflow...$(RESET)"
@$(MAKE) format-python
@$(MAKE) lint-python
@$(MAKE) test-python
ci: check docs lint ## CI workflow - check, docs, lint (no formatting)
@echo "$(GREEN)✅ CI workflow completed$(RESET)"
# Statistics
stats: ## Show repository statistics
@echo "$(BLUE)📊 Repository Statistics$(RESET)"
@printf "%-20s %6s\n" "Actions:" "$(shell find . -mindepth 2 -maxdepth 2 -name "action.yml" | wc -l | tr -d ' ')"
@printf "%-20s %6s\n" "Shell scripts:" "$(shell find . -name "*.sh" | wc -l | tr -d ' ')"
@printf "%-20s %6s\n" "YAML files:" "$(shell find . -name "*.yml" -o -name "*.yaml" | wc -l | tr -d ' ')"
@printf "%-20s %6s\n" "Markdown files:" "$(shell find . -name "*.md" | wc -l | tr -d ' ')"
@printf "%-20s %6s\n" "Total files:" "$(shell find . -type f | wc -l | tr -d ' ')"
# Watch mode for development
# Testing targets
test: test-python test-update-validators test-actions ## Run all tests (Python + Update validators + GitHub Actions)
@echo "$(GREEN)✅ All tests completed$(RESET)"
test-actions: ## Run GitHub Actions tests (unit + integration)
@echo "$(BLUE)🧪 Running GitHub Actions tests...$(RESET)"
@if ./_tests/run-tests.sh --type all --format console; then \
echo "$(GREEN)✅ All GitHub Actions tests passed$(RESET)"; \
else \
echo "$(RED)❌ Some GitHub Actions tests failed$(RESET)"; \
exit 1; \
fi
test-python: ## Run Python validation tests
@echo "$(BLUE)🐍 Running Python tests...$(RESET)"
@if command -v uv >/dev/null 2>&1; then \
if uv run pytest -v --tb=short; then \
echo "$(GREEN)✅ Python tests passed$(RESET)"; \
else \
echo "$(RED)❌ Python tests failed$(RESET)"; \
exit 1; \
fi; \
else \
echo "$(BLUE) uv not available, skipping Python tests$(RESET)"; \
fi
test-python-coverage: ## Run Python tests with coverage
@echo "$(BLUE)📊 Running Python tests with coverage...$(RESET)"
@if command -v uv >/dev/null 2>&1; then \
uv run pytest --cov=validate-inputs --cov-report=term-missing; \
else \
echo "$(BLUE) uv not available, skipping Python coverage tests$(RESET)"; \
fi
test-update-validators: ## Run tests for update-validators.py script
@echo "$(BLUE)🔧 Running update-validators.py tests...$(RESET)"
@if command -v uv >/dev/null 2>&1; then \
if uv run pytest validate-inputs/tests/test_update_validators.py -v --tb=short; then \
echo "$(GREEN)✅ Update-validators tests passed$(RESET)"; \
else \
echo "$(RED)❌ Update-validators tests failed$(RESET)"; \
exit 1; \
fi; \
else \
echo "$(BLUE) uv not available, skipping update-validators tests$(RESET)"; \
fi
test-unit: ## Run unit tests only
@echo "$(BLUE)🔬 Running unit tests...$(RESET)"
@./_tests/run-tests.sh --type unit --format console
test-integration: ## Run integration tests only
@echo "$(BLUE)🔗 Running integration tests...$(RESET)"
@./_tests/run-tests.sh --type integration --format console
test-coverage: ## Run tests with coverage reporting
@echo "$(BLUE)📊 Running tests with coverage...$(RESET)"
@./_tests/run-tests.sh --type all --coverage --format console
test-action: ## Run tests for specific action (usage: make test-action ACTION=node-setup)
@if [ -z "$(ACTION)" ]; then \
echo "$(RED)❌ Error: ACTION parameter required$(RESET)"; \
echo "Usage: make test-action ACTION=node-setup"; \
exit 1; \
fi
@echo "$(BLUE)🎯 Running tests for action: $(ACTION)$(RESET)"
@./_tests/run-tests.sh --action $(ACTION) --format console
generate-tests: ## Generate missing tests for actions and validators (won't overwrite existing tests)
@echo "$(BLUE)🧪 Generating missing tests...$(RESET)"
@if command -v uv >/dev/null 2>&1; then \
if uv run validate-inputs/scripts/generate-tests.py; then \
echo "$(GREEN)✅ Test generation completed$(RESET)"; \
else \
echo "$(RED)❌ Test generation failed$(RESET)"; \
exit 1; \
fi; \
else \
echo "$(RED)❌ uv not found. Please install uv (see 'make install-tools')$(RESET)"; \
exit 1; \
fi
generate-tests-dry: ## Preview what tests would be generated without creating files
@echo "$(BLUE)👁️ Preview test generation (dry run)...$(RESET)"
@if command -v uv >/dev/null 2>&1; then \
uv run validate-inputs/scripts/generate-tests.py --dry-run --verbose; \
else \
echo "$(RED)❌ uv not found. Please install uv (see 'make install-tools')$(RESET)"; \
exit 1; \
fi
test-generate-tests: ## Test the test generation system itself
@echo "$(BLUE)🔬 Testing test generation system...$(RESET)"
@if command -v uv >/dev/null 2>&1; then \
if uv run pytest validate-inputs/tests/test_generate_tests.py -v; then \
echo "$(GREEN)✅ Test generation tests passed$(RESET)"; \
else \
echo "$(RED)❌ Test generation tests failed$(RESET)"; \
exit 1; \
fi; \
else \
echo "$(RED)❌ uv not found. Please install uv (see 'make install-tools')$(RESET)"; \
exit 1; \
fi
# Docker targets
docker-build: ## Build the testing-tools Docker image
@echo "$(BLUE)🐳 Building testing-tools Docker image...$(RESET)"
@if ! command -v docker >/dev/null 2>&1; then \
echo "$(RED)❌ Docker not found. Please install Docker.$(RESET)"; \
exit 1; \
fi
@if bash _tools/docker-testing-tools/build.sh; then \
echo "$(GREEN)✅ Docker image built successfully$(RESET)"; \
else \
echo "$(RED)❌ Docker build failed$(RESET)"; \
exit 1; \
fi
docker-test: ## Test the Docker image locally
@echo "$(BLUE)🧪 Testing Docker image...$(RESET)"
@if ! command -v docker >/dev/null 2>&1; then \
echo "$(RED)❌ Docker not found$(RESET)"; \
exit 1; \
fi
@echo "$(BLUE)Testing basic functionality...$(RESET)"
@docker run --rm ghcr.io/ivuorinen/actions:testing-tools whoami
@docker run --rm ghcr.io/ivuorinen/actions:testing-tools shellspec --version
@docker run --rm ghcr.io/ivuorinen/actions:testing-tools act --version
@echo "$(GREEN)✅ Docker image tests passed$(RESET)"
docker-login: ## Authenticate with GitHub Container Registry
@echo "$(BLUE)🔐 Authenticating with ghcr.io...$(RESET)"
@TOKEN=""; \
TOKEN_SOURCE=""; \
if [ -n "$${GITHUB_TOKEN-}" ]; then \
echo "$(BLUE)Using GITHUB_TOKEN from environment$(RESET)"; \
TOKEN="$${GITHUB_TOKEN}"; \
TOKEN_SOURCE="env"; \
elif command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then \
echo "$(BLUE)Using token from GitHub CLI (gh)$(RESET)"; \
TOKEN=$$(gh auth token); \
TOKEN_SOURCE="gh"; \
else \
echo "$(RED)❌ No authentication method available$(RESET)"; \
echo ""; \
echo "$(YELLOW)To authenticate with ghcr.io, you need a token with 'write:packages' scope$(RESET)"; \
echo ""; \
echo "$(GREEN)Option 1: Use environment variable$(RESET)"; \
echo " export GITHUB_TOKEN=ghp_xxxxxxxxxxxx"; \
echo " make docker-login"; \
echo ""; \
echo "$(GREEN)Option 2: Use GitHub CLI with proper scopes$(RESET)"; \
echo " gh auth login --scopes 'write:packages'"; \
echo " make docker-login"; \
echo ""; \
echo "$(GREEN)Option 3: Create a Personal Access Token$(RESET)"; \
echo " 1. Go to: https://github.com/settings/tokens/new"; \
echo " 2. Check: write:packages (includes read:packages)"; \
echo " 3. Generate token and use with Option 1"; \
exit 1; \
fi; \
if printf '%s' "$${TOKEN}" | docker login ghcr.io -u ivuorinen --password-stdin 2>&1 | tee /tmp/docker-login.log | grep -q "Login Succeeded"; then \
echo "$(GREEN)✅ Successfully authenticated with ghcr.io$(RESET)"; \
rm -f /tmp/docker-login.log; \
else \
echo "$(RED)❌ Authentication failed$(RESET)"; \
echo ""; \
if grep -q "scope" /tmp/docker-login.log 2>/dev/null; then \
echo "$(YELLOW)⚠️ Token does not have required 'write:packages' scope$(RESET)"; \
echo ""; \
if [ "$$TOKEN_SOURCE" = "gh" ]; then \
echo "$(BLUE)GitHub CLI tokens need package permissions.$(RESET)"; \
echo ""; \
if [ -n "$${GITHUB_TOKEN-}" ]; then \
echo "$(YELLOW)Note: GITHUB_TOKEN is set in your environment, which prevents gh auth refresh.$(RESET)"; \
echo "Clear it first, then refresh:"; \
echo ""; \
echo "$(GREEN)For Fish shell:$(RESET)"; \
echo " set -e GITHUB_TOKEN"; \
echo " gh auth refresh --scopes 'write:packages'"; \
echo ""; \
echo "$(GREEN)For Bash/Zsh:$(RESET)"; \
echo " unset GITHUB_TOKEN"; \
echo " gh auth refresh --scopes 'write:packages'"; \
else \
echo "Run:"; \
echo " gh auth refresh --scopes 'write:packages'"; \
fi; \
echo ""; \
echo "Then try again:"; \
echo " make docker-login"; \
else \
echo "Your GITHUB_TOKEN needs 'write:packages' scope."; \
echo ""; \
echo "$(GREEN)Create a new token:$(RESET)"; \
echo " 1. Go to: https://github.com/settings/tokens/new"; \
echo " 2. Check: write:packages (includes read:packages)"; \
echo " 3. Generate and copy the token"; \
echo ""; \
echo "$(GREEN)For Fish shell:$(RESET)"; \
echo " set -gx GITHUB_TOKEN ghp_xxxxxxxxxxxx"; \
echo ""; \
echo "$(GREEN)For Bash/Zsh:$(RESET)"; \
echo " export GITHUB_TOKEN=ghp_xxxxxxxxxxxx"; \
fi; \
fi; \
rm -f /tmp/docker-login.log; \
exit 1; \
fi
docker-push: ## Push the testing-tools image to ghcr.io
@echo "$(BLUE)📤 Pushing Docker image to ghcr.io...$(RESET)"
@if ! command -v docker >/dev/null 2>&1; then \
echo "$(RED)❌ Docker not found$(RESET)"; \
exit 1; \
fi
@if ! docker images ghcr.io/ivuorinen/actions:testing-tools -q | grep -q .; then \
echo "$(RED)❌ Image not found. Run 'make docker-build' first$(RESET)"; \
exit 1; \
fi
@PUSH_OUTPUT=$$(docker push ghcr.io/ivuorinen/actions:testing-tools 2>&1); \
PUSH_EXIT=$$?; \
echo "$${PUSH_OUTPUT}"; \
if [ $$PUSH_EXIT -ne 0 ]; then \
echo ""; \
if echo "$${PUSH_OUTPUT}" | grep -q "scope"; then \
echo "$(RED)❌ Token does not have required 'write:packages' scope$(RESET)"; \
echo ""; \
echo "$(YELLOW)Fix the authentication:$(RESET)"; \
echo ""; \
if [ -n "$${GITHUB_TOKEN-}" ]; then \
echo "$(BLUE)Option 1: Clear GITHUB_TOKEN and use gh auth$(RESET)"; \
echo ""; \
echo "For Fish shell:"; \
echo " set -e GITHUB_TOKEN"; \
echo " gh auth refresh --scopes 'write:packages'"; \
echo " make docker-push"; \
echo ""; \
echo "For Bash/Zsh:"; \
echo " unset GITHUB_TOKEN"; \
echo " gh auth refresh --scopes 'write:packages'"; \
echo " make docker-push"; \
echo ""; \
echo "$(BLUE)Option 2: Create a new token with write:packages scope$(RESET)"; \
else \
echo "$(BLUE)Option 1: Use GitHub CLI$(RESET)"; \
echo " gh auth refresh --scopes 'write:packages'"; \
echo " make docker-push"; \
echo ""; \
echo "$(BLUE)Option 2: Use Personal Access Token$(RESET)"; \
fi; \
echo " 1. Go to: https://github.com/settings/tokens/new"; \
echo " 2. Check: write:packages"; \
echo " 3. Generate and copy token"; \
echo ""; \
echo " For Fish shell:"; \
echo " set -gx GITHUB_TOKEN ghp_xxxxxxxxxxxx"; \
echo " make docker-push"; \
echo ""; \
echo " For Bash/Zsh:"; \
echo " export GITHUB_TOKEN=ghp_xxxxxxxxxxxx"; \
echo " make docker-push"; \
exit 1; \
elif echo "$${PUSH_OUTPUT}" | grep -q "denied\|unauthorized"; then \
echo "$(YELLOW)⚠️ Authentication required. Attempting login...$(RESET)"; \
if $(MAKE) docker-login; then \
echo ""; \
echo "$(BLUE)Retrying push...$(RESET)"; \
if ! docker push ghcr.io/ivuorinen/actions:testing-tools; then \
echo "$(RED)❌ Retry push failed$(RESET)"; \
exit 1; \
fi; \
else \
exit 1; \
fi; \
else \
echo "$(RED)❌ Push failed$(RESET)"; \
exit 1; \
fi; \
fi
@echo "$(GREEN)✅ Image pushed successfully$(RESET)"
@echo ""
@echo "Image available at:"
@echo " ghcr.io/ivuorinen/actions:testing-tools"
docker-all: docker-build docker-test docker-push ## Build, test, and push Docker image
@echo "$(GREEN)✅ All Docker operations completed$(RESET)"
watch: ## Watch files and auto-format on changes (requires entr)
@if command -v entr >/dev/null 2>&1; then \
echo "$(BLUE)👀 Watching for changes... (press Ctrl+C to stop)$(RESET)"; \
find . \( -name "*.yml" -o -name "*.yaml" -o -name "*.md" -o -name "*.sh" \) \
-not -path "./_tests/*" -not -path "./.worktrees/*" -not -path "./node_modules/*" | \
entr -c $(MAKE) format; \
else \
echo "$(RED)❌ Error: entr not found. Install with: brew install entr$(RESET)"; \
exit 1; \
fi