mirror of
https://github.com/ivuorinen/actions.git
synced 2026-01-26 03:23:59 +00:00
fix: local references, release workflow (#301)
* fix: local references, release workflow * chore: apply cr comments
This commit is contained in:
6
.github/workflows/action-security.yml
vendored
6
.github/workflows/action-security.yml
vendored
@@ -52,7 +52,7 @@ jobs:
|
||||
# Check Gitleaks configuration and license
|
||||
if [ -f ".gitleaks.toml" ] && [ -n "${{ secrets.GITLEAKS_LICENSE }}" ]; then
|
||||
echo "Gitleaks config and license found"
|
||||
echo "run_gitleaks=true" >> "$GITHUB_OUTPUT"
|
||||
printf '%s\n' "run_gitleaks=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "::warning::Gitleaks config or license missing - skipping Gitleaks scan"
|
||||
fi
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
# Check Trivy results
|
||||
if [ -f "trivy-results.sarif" ]; then
|
||||
if jq -e . </dev/null 2>&1 <"trivy-results.sarif"; then
|
||||
echo "has_trivy=true" >> "$GITHUB_OUTPUT"
|
||||
printf '%s\n' "has_trivy=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "::warning::Trivy SARIF file exists but is not valid JSON"
|
||||
fi
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
if [ "${{ steps.check-configs.outputs.run_gitleaks }}" = "true" ]; then
|
||||
if [ -f "gitleaks-report.sarif" ]; then
|
||||
if jq -e . </dev/null 2>&1 <"gitleaks-report.sarif"; then
|
||||
echo "has_gitleaks=true" >> "$GITHUB_OUTPUT"
|
||||
printf '%s\n' "has_gitleaks=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "::warning::Gitleaks SARIF file exists but is not valid JSON"
|
||||
fi
|
||||
|
||||
4
.github/workflows/pr-lint.yml
vendored
4
.github/workflows/pr-lint.yml
vendored
@@ -78,12 +78,12 @@ jobs:
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
echo "status=success" >> "$GITHUB_OUTPUT"
|
||||
printf '%s\n' "status=success" >> "$GITHUB_OUTPUT"
|
||||
|
||||
if [ -f "${{ env.REPORT_OUTPUT_FOLDER }}/megalinter.log" ]; then
|
||||
if grep -q "ERROR\|CRITICAL" "${{ env.REPORT_OUTPUT_FOLDER }}/megalinter.log"; then
|
||||
echo "Linting errors found"
|
||||
echo "status=failure" >> "$GITHUB_OUTPUT"
|
||||
printf '%s\n' "status=failure" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
else
|
||||
echo "::warning::MegaLinter log file not found"
|
||||
|
||||
4
.github/workflows/test-actions.yml
vendored
4
.github/workflows/test-actions.yml
vendored
@@ -125,10 +125,10 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -d "_tests/reports/integration" ] && [ -n "$(find _tests/reports/integration -type f 2>/dev/null)" ]; then
|
||||
echo "reports-found=true" >> $GITHUB_OUTPUT
|
||||
printf '%s\n' "reports-found=true" >> $GITHUB_OUTPUT
|
||||
echo "Integration test reports found"
|
||||
else
|
||||
echo "reports-found=false" >> $GITHUB_OUTPUT
|
||||
printf '%s\n' "reports-found=false" >> $GITHUB_OUTPUT
|
||||
echo "No integration test reports found"
|
||||
fi
|
||||
|
||||
|
||||
127
.github/workflows/version-maintenance.yml
vendored
Normal file
127
.github/workflows/version-maintenance.yml
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
---
|
||||
name: Version Maintenance
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run weekly on Monday at 9 AM UTC
|
||||
- cron: '0 9 * * 1'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
major-version:
|
||||
description: 'Major version to check (e.g., v2025)'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
check-and-update:
|
||||
name: Check Version References
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Determine Major Version
|
||||
id: version
|
||||
shell: sh
|
||||
run: |
|
||||
if [ -n "${{ inputs.major-version }}" ]; then
|
||||
printf '%s\n' "major=${{ inputs.major-version }}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
current_year=$(date +%Y)
|
||||
printf '%s\n' "major=v$current_year" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Run Action Versioning
|
||||
id: action-versioning
|
||||
uses: ./action-versioning
|
||||
with:
|
||||
major-version: ${{ steps.version.outputs.major }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create Pull Request
|
||||
if: steps.action-versioning.outputs.updated == 'true'
|
||||
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: 'chore: update action references to ${{ steps.version.outputs.major }}'
|
||||
title: 'chore: Update action references to ${{ steps.version.outputs.major }}'
|
||||
body: |
|
||||
## Version Maintenance
|
||||
|
||||
This PR updates all internal action references to match the latest ${{ steps.version.outputs.major }} tag.
|
||||
|
||||
**Updated SHA**: `${{ steps.action-versioning.outputs.commit-sha }}`
|
||||
|
||||
### Changes
|
||||
- Updated all `*/action.yml` files to reference the current ${{ steps.version.outputs.major }} SHA
|
||||
|
||||
### Verification
|
||||
```bash
|
||||
make check-version-refs
|
||||
```
|
||||
|
||||
🤖 Auto-generated by version-maintenance workflow
|
||||
branch: automated/version-update-${{ steps.version.outputs.major }}
|
||||
delete-branch: true
|
||||
labels: |
|
||||
automated
|
||||
dependencies
|
||||
|
||||
- name: Check for Annual Bump
|
||||
if: steps.action-versioning.outputs.needs-annual-bump == 'true'
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
with:
|
||||
script: |
|
||||
const currentYear = new Date().getFullYear();
|
||||
const majorVersion = '${{ steps.version.outputs.major }}';
|
||||
|
||||
await github.rest.issues.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title: `🔄 Annual Version Bump Needed: ${majorVersion} → v${currentYear}`,
|
||||
body: `## Annual Version Bump Required
|
||||
|
||||
It's time to bump the major version from ${majorVersion} to v${currentYear}.
|
||||
|
||||
### Steps
|
||||
|
||||
1. **Create the new major version tag:**
|
||||
\`\`\`bash
|
||||
git tag -a v${currentYear} -m "Major version v${currentYear}"
|
||||
git push origin v${currentYear}
|
||||
\`\`\`
|
||||
|
||||
2. **Bump all action references:**
|
||||
\`\`\`bash
|
||||
make bump-major-version OLD=${majorVersion} NEW=v${currentYear}
|
||||
\`\`\`
|
||||
|
||||
3. **Update documentation:**
|
||||
\`\`\`bash
|
||||
make docs
|
||||
\`\`\`
|
||||
|
||||
4. **Commit and push:**
|
||||
\`\`\`bash
|
||||
git push origin main
|
||||
\`\`\`
|
||||
|
||||
### Verification
|
||||
|
||||
\`\`\`bash
|
||||
make check-version-refs
|
||||
\`\`\`
|
||||
|
||||
🤖 Auto-generated by version-maintenance workflow
|
||||
`,
|
||||
labels: ['maintenance', 'high-priority']
|
||||
});
|
||||
@@ -74,7 +74,7 @@ repos:
|
||||
rev: v0.11.0.1
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
args: ['--severity=warning', '-x']
|
||||
args: ['-x']
|
||||
exclude: '^_tests/.*\.sh$'
|
||||
|
||||
- repo: https://github.com/rhysd/actionlint
|
||||
|
||||
@@ -22,17 +22,19 @@
|
||||
- Unquoted variables cause word splitting and globbing
|
||||
- Example: `"$variable"` not `$variable`, `basename -- "$path"` not `basename $path`
|
||||
|
||||
6. **ALWAYS** use local paths (`./action-name`) for intra-repo actions
|
||||
- Avoids external dependencies and version drift
|
||||
- Pattern: `uses: ./common-cache` not `uses: ivuorinen/actions/common-cache@main`
|
||||
6. **ALWAYS** use SHA-pinned references for internal actions in action.yml
|
||||
- Security: immutable, auditable, portable when used externally
|
||||
- Pattern: `uses: ivuorinen/actions/common-cache@7061aafd35a2f21b57653e34f2b634b2a19334a9`
|
||||
- Test workflows use local: `uses: ./common-cache` (within repo only)
|
||||
|
||||
7. **ALWAYS** test regex patterns against edge cases
|
||||
- Include prerelease tags (`1.0.0-rc.1`), build metadata (`1.0.0+build.123`)
|
||||
- Version validation should support full semver/calver formats
|
||||
|
||||
8. **ALWAYS** use `set -euo pipefail` at script start
|
||||
- `-e`: Exit on error, `-u`: Exit on undefined variable, `-o pipefail`: Exit on pipe failures
|
||||
- Critical for fail-fast behavior in composite actions
|
||||
8. **ALWAYS** use POSIX shell (`set -eu`) for all scripts
|
||||
- Maximum portability: works on Alpine, busybox, all shells
|
||||
- Use `#!/bin/sh` not `#!/usr/bin/env bash`
|
||||
- Use `set -eu` not `set -euo pipefail` (pipefail not POSIX)
|
||||
|
||||
9. **Avoid** nesting `${{ }}` expressions inside quoted strings in specific contexts
|
||||
- In `hashFiles()`: `"${{ inputs.value }}"` breaks cache key generation - use unquoted or extract to variable
|
||||
@@ -92,42 +94,71 @@ Comprehensive linting with 30+ rule categories including:
|
||||
|
||||
**Example**: `# ruff: noqa: T201, S603` for action step scripts only
|
||||
|
||||
## Shell Script Standards
|
||||
## Shell Script Standards (POSIX)
|
||||
|
||||
### Required Hardening Checklist
|
||||
**ALL scripts use POSIX shell** (`#!/bin/sh`) for maximum portability.
|
||||
|
||||
- ✅ **Shebang**: `#!/usr/bin/env bash` (POSIX-compliant)
|
||||
- ✅ **Error Handling**: `set -euo pipefail` at script start
|
||||
- ✅ **Safe IFS**: `IFS=$' \t\n'` (space, tab, newline only)
|
||||
- ✅ **Exit Trap**: `trap cleanup EXIT` for cleanup operations
|
||||
- ✅ **Error Trap**: `trap 'echo "Error at line $LINENO" >&2' ERR` for debugging
|
||||
### Required POSIX Compliance Checklist
|
||||
|
||||
- ✅ **Shebang**: `#!/bin/sh` (POSIX-compliant, not bash)
|
||||
- ✅ **Error Handling**: `set -eu` at script start (no pipefail - not POSIX)
|
||||
- ✅ **Defensive Expansion**: Use `${var:-default}` or `${var:?message}` patterns
|
||||
- ✅ **Quote Everything**: Always quote expansions: `"$var"`, `basename -- "$path"`
|
||||
- ✅ **Tool Availability**: `command -v tool >/dev/null 2>&1 || { echo "Missing tool"; exit 1; }`
|
||||
- ✅ **Portable Output**: Use `printf` instead of `echo -e`
|
||||
- ✅ **Portable Sourcing**: Use `. file` instead of `source file`
|
||||
- ✅ **POSIX Tests**: Use `[ ]` instead of `[[ ]]`
|
||||
- ✅ **Parsing**: Use `cut`, `grep`, pipes instead of here-strings `<<<`
|
||||
- ✅ **No Associative Arrays**: Use temp files or line-based processing
|
||||
|
||||
### Key POSIX Differences from Bash
|
||||
|
||||
| Bash Feature | POSIX Replacement |
|
||||
| --------------------- | --------------------------------- |
|
||||
| `#!/usr/bin/env bash` | `#!/bin/sh` |
|
||||
| `set -euo pipefail` | `set -eu` |
|
||||
| `[[ condition ]]` | `[ condition ]` |
|
||||
| `[[ $var =~ regex ]]` | `echo "$var" \| grep -qE 'regex'` |
|
||||
| `<<<` here-strings | `echo \| cut` or pipes |
|
||||
| `source file` | `. file` |
|
||||
| `$BASH_SOURCE` | `$0` |
|
||||
| `((var++))` | `var=$((var + 1))` |
|
||||
| `((var < 10))` | `[ "$var" -lt 10 ]` |
|
||||
| `echo -e` | `printf '%b'` |
|
||||
| `declare -A map` | temp files + sort/uniq |
|
||||
| Process substitution | pipes or temp files |
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
IFS=$' \t\n'
|
||||
|
||||
# Cleanup trap
|
||||
cleanup() { rm -f /tmp/tempfile; }
|
||||
trap cleanup EXIT
|
||||
|
||||
# Error trap with line number
|
||||
trap 'echo "Error at line $LINENO" >&2' ERR
|
||||
```sh
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# Defensive parameter expansion
|
||||
config_file="${CONFIG_FILE:-config.yml}" # Use default if unset
|
||||
required_param="${REQUIRED_PARAM:?Missing value}" # Error if unset
|
||||
required_param="${REQUIRED_PARAM:?Missing value}" # Error if unset
|
||||
|
||||
# Always quote expansions
|
||||
echo "Processing: $config_file"
|
||||
printf 'Processing: %s\n' "$config_file"
|
||||
result=$(basename -- "$file_path")
|
||||
|
||||
# POSIX test conditions
|
||||
if [ -f "$config_file" ]; then
|
||||
printf 'Found config\n'
|
||||
fi
|
||||
|
||||
# Portable output
|
||||
printf '%b' "Color: ${GREEN}text${NC}\n"
|
||||
```
|
||||
|
||||
### Why POSIX Shell
|
||||
|
||||
- **Portability**: Works on Alpine Linux, busybox, minimal containers, all POSIX shells
|
||||
- **Performance**: POSIX shells are lighter and faster than bash
|
||||
- **CI-Friendly**: Minimal dependencies, works everywhere
|
||||
- **Standards**: Follows POSIX best practices
|
||||
- **Compatibility**: Works with sh, dash, ash, bash, zsh
|
||||
|
||||
### Additional Requirements
|
||||
|
||||
- **Security**: All external actions SHA-pinned
|
||||
@@ -189,48 +220,49 @@ if: github.event_name == 'push'
|
||||
- Don't quote in `with:`, `env:`, `if:` - GitHub evaluates these
|
||||
- Never nest expressions: `"${{ inputs.value }}"` inside hashFiles breaks caching
|
||||
|
||||
### **Local Action References**
|
||||
### Internal Action References (SHA-Pinned)
|
||||
|
||||
**CRITICAL**: When referencing actions within the same repository:
|
||||
**CRITICAL**: Action files (`*/action.yml`) use SHA-pinned references for security:
|
||||
|
||||
- ✅ **CORRECT**: `uses: ./action-name` (relative to workspace root)
|
||||
- ❌ **INCORRECT**: `uses: ../action-name` (relative paths that assume directory structure)
|
||||
- ❌ **INCORRECT**: `uses: owner/repo/action-name@main` (floating branch reference)
|
||||
- ✅ **CORRECT**: `uses: ivuorinen/actions/action-name@7061aafd35a2f21b57653e34f2b634b2a19334a9`
|
||||
- ❌ **INCORRECT**: `uses: ./action-name` (security risk, not portable when used externally)
|
||||
- ❌ **INCORRECT**: `uses: ivuorinen/actions/action-name@main` (floating reference)
|
||||
|
||||
**Rationale**:
|
||||
|
||||
- Uses GitHub workspace root (`$GITHUB_WORKSPACE`) as reference point
|
||||
- Clear and unambiguous regardless of where action is called from
|
||||
- Follows GitHub's recommended pattern for same-repository references
|
||||
- Avoids issues if action checks out repository to different location
|
||||
- Eliminates external dependencies and supply chain risks
|
||||
- **Security**: Immutable, auditable references
|
||||
- **Reproducibility**: Exact version control
|
||||
- **Portability**: Works when actions used externally (e.g., `ivuorinen/f2b` using `ivuorinen/actions/pr-lint`)
|
||||
- **Prevention**: No accidental version drift
|
||||
|
||||
**Examples**:
|
||||
**Test Workflows Exception**:
|
||||
|
||||
Test workflows in `_tests/` use local references since they run within the repo:
|
||||
|
||||
```yaml
|
||||
# ✅ Correct - relative to workspace root
|
||||
- uses: ./validate-inputs
|
||||
- uses: ./common-cache
|
||||
- uses: ./node-setup
|
||||
|
||||
# ❌ Incorrect - relative directory navigation
|
||||
- uses: ../validate-inputs
|
||||
- uses: ../common-cache
|
||||
- uses: ../node-setup
|
||||
|
||||
# ❌ Incorrect - external reference to same repo
|
||||
- uses: ivuorinen/actions/validate-inputs@main
|
||||
- uses: ivuorinen/actions/common-cache@v1
|
||||
# ✅ Test workflows only
|
||||
uses: ./validate-inputs
|
||||
```
|
||||
|
||||
### **Step Output References**
|
||||
### External Action References (SHA-Pinned)
|
||||
|
||||
```yaml
|
||||
# ✅ Correct - SHA-pinned
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
|
||||
# ❌ Incorrect - floating reference
|
||||
uses: actions/checkout@main
|
||||
uses: actions/checkout@v4
|
||||
```
|
||||
|
||||
### Step Output References
|
||||
|
||||
**CRITICAL**: Steps must have `id:` to reference their outputs:
|
||||
|
||||
```yaml
|
||||
# ❌ INCORRECT - missing id
|
||||
- name: Detect Version
|
||||
uses: ./version-detect
|
||||
uses: ivuorinen/actions/version-detect@<SHA>
|
||||
|
||||
- name: Setup
|
||||
with:
|
||||
@@ -239,7 +271,7 @@ if: github.event_name == 'push'
|
||||
# ✅ CORRECT - id present
|
||||
- name: Detect Version
|
||||
id: detect-version # Required for output reference
|
||||
uses: ./version-detect
|
||||
uses: ivuorinen/actions/version-detect@<SHA>
|
||||
|
||||
- name: Setup
|
||||
with:
|
||||
@@ -250,7 +282,7 @@ if: github.event_name == 'push'
|
||||
|
||||
- **No Secrets**: Never commit secrets or keys to repository
|
||||
- **No Logging**: Never expose or log secrets/keys in code
|
||||
- **SHA Pinning**: All external actions use SHA commits, not tags
|
||||
- **SHA Pinning**: All action references (internal + external) use SHA commits, not tags
|
||||
- **Input Validation**: All actions import from shared validation library (`validate-inputs/`) - stateless validation functions, no inter-action dependencies
|
||||
- **Output Sanitization**: Use `printf` or heredoc for `$GITHUB_OUTPUT` writes
|
||||
- **Injection Prevention**: Validate inputs for command injection patterns (`;`, `&&`, `|`, backticks)
|
||||
@@ -276,6 +308,7 @@ if: github.event_name == 'push'
|
||||
- **Convention-Based**: Automatic rule generation based on input naming patterns
|
||||
- **Error Handling**: Comprehensive error messages and proper exit codes
|
||||
- **Defensive Programming**: Check tool availability, validate inputs, handle edge cases
|
||||
- **POSIX Compliance**: All scripts portable across POSIX shells
|
||||
|
||||
## Pre-commit and Security Configuration
|
||||
|
||||
|
||||
@@ -44,6 +44,17 @@ make generate-tests # Create missing tests
|
||||
make generate-tests-dry # Preview test generation
|
||||
```
|
||||
|
||||
### Version Management
|
||||
|
||||
```bash
|
||||
make release [VERSION=vYYYY.MM.DD] # Create new release (auto-generates version from date if omitted)
|
||||
make update-version-refs MAJOR=vYYYY # Update refs to version
|
||||
make bump-major-version OLD=vYYYY NEW=vYYYY # Annual bump
|
||||
make check-version-refs # Verify current refs
|
||||
```
|
||||
|
||||
See `versioning_system` memory for complete details.
|
||||
|
||||
## Code Style
|
||||
|
||||
### EditorConfig (BLOCKING ERRORS)
|
||||
@@ -55,18 +66,36 @@ make generate-tests-dry # Preview test generation
|
||||
- **Final Newline**: Required
|
||||
- **Trailing Whitespace**: Trimmed
|
||||
|
||||
### Shell Scripts (REQUIRED)
|
||||
### Shell Scripts (POSIX REQUIRED)
|
||||
|
||||
**ALL scripts use POSIX shell** (`#!/bin/sh`) for maximum portability:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail # MANDATORY
|
||||
IFS=$' \t\n'
|
||||
trap cleanup EXIT
|
||||
trap 'echo "Error at line $LINENO" >&2' ERR
|
||||
# Always quote: "$variable", basename -- "$path"
|
||||
#!/bin/sh
|
||||
set -eu # MANDATORY (no pipefail - not POSIX)
|
||||
# Quote everything: "$variable", basename -- "$path"
|
||||
# Check tools: command -v jq >/dev/null 2>&1
|
||||
# Use printf instead of echo -e for portability
|
||||
```
|
||||
|
||||
**Why POSIX:**
|
||||
|
||||
- Works on Alpine Linux, busybox, minimal containers
|
||||
- Faster than bash
|
||||
- Maximum compatibility (sh, dash, ash, bash, zsh)
|
||||
- CI-friendly, minimal dependencies
|
||||
|
||||
**Key Differences from Bash:**
|
||||
|
||||
- Use `#!/bin/sh` not `#!/usr/bin/env bash`
|
||||
- Use `set -eu` not `set -euo pipefail` (pipefail not POSIX)
|
||||
- Use `[ ]` not `[[ ]]`
|
||||
- Use `printf` not `echo -e`
|
||||
- Use `. file` not `source file`
|
||||
- Use `cut`/`grep` for parsing, not here-strings `<<<`
|
||||
- Use temp files instead of associative arrays
|
||||
- Use `$0` not `$BASH_SOURCE`
|
||||
|
||||
### Python (Ruff)
|
||||
|
||||
- **Line Length**: 100 chars
|
||||
@@ -78,15 +107,68 @@ trap 'echo "Error at line $LINENO" >&2' ERR
|
||||
### YAML/Actions
|
||||
|
||||
- **Indent**: 2 spaces
|
||||
- **Local Actions**: `uses: ./action-name` (never `../` or `@main`)
|
||||
- **Internal Actions (action.yml)**: `ivuorinen/actions/action-name@<SHA>` (SHA-pinned, security)
|
||||
- **Test Workflows**: `./action-name` (local reference, runs within repo)
|
||||
- **Internal Workflows**: `./action-name` (local reference for sync-labels.yml etc)
|
||||
- **External Actions**: SHA-pinned (not `@main`/`@v1`)
|
||||
- **Step IDs**: Required when outputs referenced
|
||||
- **Permissions**: Minimal scope (contents: read default)
|
||||
- **Output Sanitization**: Use `printf`, never `echo` for `$GITHUB_OUTPUT`
|
||||
|
||||
## Versioning System
|
||||
|
||||
### Internal References (SHA-Pinned)
|
||||
|
||||
All `*/action.yml` files use SHA-pinned references for security and reproducibility:
|
||||
|
||||
```yaml
|
||||
uses: ivuorinen/actions/validate-inputs@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
```
|
||||
|
||||
**Why SHA-pinned internally:**
|
||||
|
||||
- Security: immutable, auditable references
|
||||
- Reproducibility: exact version control
|
||||
- Portability: works when actions used externally
|
||||
- Prevention: no accidental version drift
|
||||
|
||||
### Test Workflows (Local References)
|
||||
|
||||
Test workflows in `_tests/` use local references:
|
||||
|
||||
```yaml
|
||||
uses: ./validate-inputs
|
||||
```
|
||||
|
||||
**Why local in tests:** Tests run within the repo, faster, simpler
|
||||
|
||||
### External User References
|
||||
|
||||
Users reference with version tags:
|
||||
|
||||
```yaml
|
||||
uses: ivuorinen/actions/validate-inputs@v2025
|
||||
```
|
||||
|
||||
### Version Format (CalVer)
|
||||
|
||||
- Major: `v2025` (year)
|
||||
- Minor: `v2025.10` (year.month)
|
||||
- Patch: `v2025.10.18` (year.month.day)
|
||||
|
||||
All three tags point to the same commit SHA.
|
||||
|
||||
### Creating Releases
|
||||
|
||||
```bash
|
||||
make release # Auto-generates vYYYY.MM.DD from today's date
|
||||
make release VERSION=v2025.10.18 # Specific version
|
||||
git push origin main --tags --force-with-lease
|
||||
```
|
||||
|
||||
## Security Requirements
|
||||
|
||||
1. **SHA Pinning**: All external actions use commit SHAs
|
||||
1. **SHA Pinning**: All action references use commit SHAs (not moving tags)
|
||||
2. **Token Safety**: `${{ github.token }}`, never hardcoded
|
||||
3. **Input Validation**: All inputs validated via centralized system
|
||||
4. **Output Sanitization**: `printf '%s\n' "$value" >> $GITHUB_OUTPUT`
|
||||
@@ -104,9 +186,13 @@ trap 'echo "Error at line $LINENO" >&2' ERR
|
||||
- Never skip testing after changes
|
||||
- Never create files unless absolutely necessary
|
||||
- Never nest `${{ }}` in quoted YAML strings (breaks hashFiles)
|
||||
- Never use `@main` for internal action references (use SHA-pinned)
|
||||
- Never use bash-specific features (scripts must be POSIX sh)
|
||||
|
||||
## Preferred Patterns
|
||||
|
||||
- POSIX shell for all scripts (not bash)
|
||||
- SHA-pinned internal action references (security)
|
||||
- Edit existing files over creating new ones
|
||||
- Use centralized validation for all input handling
|
||||
- Follow existing conventions in codebase
|
||||
|
||||
219
.serena/memories/versioning_system.md
Normal file
219
.serena/memories/versioning_system.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# Version System Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
This repository uses a CalVer-based SHA-pinned versioning system for all internal action references.
|
||||
|
||||
## Version Format
|
||||
|
||||
### CalVer: vYYYY.MM.DD
|
||||
|
||||
- **Major**: `v2025` (year, updated annually)
|
||||
- **Minor**: `v2025.10` (year.month)
|
||||
- **Patch**: `v2025.10.18` (year.month.day)
|
||||
|
||||
Example: Release `v2025.10.18` creates three tags pointing to the same commit:
|
||||
|
||||
- `v2025.10.18` (patch - specific release)
|
||||
- `v2025.10` (minor - latest October 2025 release)
|
||||
- `v2025` (major - latest 2025 release)
|
||||
|
||||
## Internal vs External References
|
||||
|
||||
### Internal (action.yml files)
|
||||
|
||||
- **Format**: `ivuorinen/actions/validate-inputs@<40-char-SHA>`
|
||||
- **Purpose**: Security, reproducibility, precise control
|
||||
- **Example**: `ivuorinen/actions/validate-inputs@7061aafd35a2f21b57653e34f2b634b2a19334a9`
|
||||
|
||||
### External (user consumption)
|
||||
|
||||
- **Format**: `ivuorinen/actions/validate-inputs@v2025`
|
||||
- **Purpose**: Convenience, always gets latest release
|
||||
- **Options**: `@v2025`, `@v2025.10`, or `@v2025.10.18`
|
||||
|
||||
### Test Workflows
|
||||
|
||||
- **Format**: `uses: ./action-name` (local reference)
|
||||
- **Location**: `_tests/integration/workflows/*.yml`
|
||||
- **Reason**: Tests run within the actions repo context
|
||||
|
||||
### Internal Workflows
|
||||
|
||||
- **Format**: `uses: ./sync-labels` (local reference)
|
||||
- **Location**: `.github/workflows/sync-labels.yml`
|
||||
- **Reason**: Runs within the actions repo, local is sufficient
|
||||
|
||||
## Release Process
|
||||
|
||||
### Creating a Release
|
||||
|
||||
```bash
|
||||
# 1. Create release with version tags
|
||||
make release VERSION=v2025.10.18
|
||||
|
||||
# This automatically:
|
||||
# - Updates all action.yml SHA refs to current HEAD
|
||||
# - Commits the changes
|
||||
# - Creates tags: v2025.10.18, v2025.10, v2025
|
||||
# - All tags point to the same commit SHA
|
||||
|
||||
# 2. Push to remote
|
||||
git push origin main --tags --force-with-lease
|
||||
```
|
||||
|
||||
### After Each Release
|
||||
|
||||
Tags are force-pushed to ensure `v2025` and `v2025.10` always point to latest:
|
||||
|
||||
```bash
|
||||
git push origin v2025 --force
|
||||
git push origin v2025.10 --force
|
||||
git push origin v2025.10.18
|
||||
```
|
||||
|
||||
Or use `--tags --force-with-lease` to push all at once.
|
||||
|
||||
## Makefile Targets
|
||||
|
||||
### `make release VERSION=v2025.10.18`
|
||||
|
||||
Creates new release with version tags and updates all action references.
|
||||
|
||||
### `make update-version-refs MAJOR=v2025`
|
||||
|
||||
Updates all action.yml files to reference the SHA of the specified major version tag.
|
||||
|
||||
### `make bump-major-version OLD=v2025 NEW=v2026`
|
||||
|
||||
Annual version bump - replaces all references from one major version to another.
|
||||
|
||||
### `make check-version-refs`
|
||||
|
||||
Lists all current SHA-pinned references grouped by SHA. Useful for verification.
|
||||
|
||||
## Helper Scripts (\_tools/)
|
||||
|
||||
### release.sh
|
||||
|
||||
Main release script - validates version, updates refs, creates tags.
|
||||
|
||||
### validate-version.sh
|
||||
|
||||
Validates CalVer format (vYYYY.MM.DD, vYYYY.MM, vYYYY).
|
||||
|
||||
### update-action-refs.sh
|
||||
|
||||
Updates all action references to a specific SHA or version tag.
|
||||
|
||||
### bump-major-version.sh
|
||||
|
||||
Handles annual version bumps with commit creation.
|
||||
|
||||
### check-version-refs.sh
|
||||
|
||||
Displays current SHA-pinned references with tag information.
|
||||
|
||||
### get-action-sha.sh
|
||||
|
||||
Retrieves SHA for a specific version tag.
|
||||
|
||||
## Action Versioning Action
|
||||
|
||||
**Location**: `action-versioning/action.yml`
|
||||
|
||||
Automatically checks if major version tag has moved and updates all action references.
|
||||
|
||||
**Usage in CI**:
|
||||
|
||||
```yaml
|
||||
- uses: ./action-versioning
|
||||
with:
|
||||
major-version: v2025
|
||||
```
|
||||
|
||||
**Outputs**:
|
||||
|
||||
- `updated`: true/false
|
||||
- `commit-sha`: SHA of created commit (if any)
|
||||
- `needs-annual-bump`: true/false (year mismatch)
|
||||
|
||||
## CI Workflow
|
||||
|
||||
**File**: `.github/workflows/version-maintenance.yml`
|
||||
|
||||
**Triggers**:
|
||||
|
||||
- Weekly (Monday 9 AM UTC)
|
||||
- Manual (workflow_dispatch)
|
||||
|
||||
**Actions**:
|
||||
|
||||
1. Checks if `v2025` tag has moved
|
||||
2. Updates action references if needed
|
||||
3. Creates PR with changes
|
||||
4. Creates issue if annual bump needed
|
||||
|
||||
## Annual Version Bump
|
||||
|
||||
**When**: Start of each new year
|
||||
|
||||
**Process**:
|
||||
|
||||
```bash
|
||||
# 1. Create new major version tag
|
||||
git tag -a v2026 -m "Major version v2026"
|
||||
git push origin v2026
|
||||
|
||||
# 2. Bump all references
|
||||
make bump-major-version OLD=v2025 NEW=v2026
|
||||
|
||||
# 3. Update documentation
|
||||
make docs
|
||||
|
||||
# 4. Push changes
|
||||
git push origin main
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
### Check Current Refs
|
||||
|
||||
```bash
|
||||
make check-version-refs
|
||||
```
|
||||
|
||||
### Verify All Refs Match
|
||||
|
||||
All action references should point to the same SHA after a release.
|
||||
|
||||
### Test External Usage
|
||||
|
||||
Create a test repo and use:
|
||||
|
||||
```yaml
|
||||
uses: ivuorinen/actions/pr-lint@v2025
|
||||
```
|
||||
|
||||
## Migration from @main
|
||||
|
||||
All action.yml files have been migrated from:
|
||||
|
||||
- `uses: ./action-name`
|
||||
- `uses: ivuorinen/actions/action-name@main`
|
||||
|
||||
To:
|
||||
|
||||
- `uses: ivuorinen/actions/action-name@<SHA>`
|
||||
|
||||
Test workflows still use `./action-name` for local testing.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
**SHA Pinning**: Prevents supply chain attacks by ensuring exact commit is used.
|
||||
|
||||
**Version Tags**: Provide user-friendly references while maintaining security internally.
|
||||
|
||||
**Tag Verification**: Always verify tags point to expected commits before force-pushing.
|
||||
|
||||
**Annual Review**: Each year requires conscious version bump, preventing accidental drift.
|
||||
33
CLAUDE.md
33
CLAUDE.md
@@ -86,7 +86,12 @@ Validation (validate-inputs)
|
||||
|
||||
**Validation**: `make update-validators`, `make update-validators-dry`
|
||||
|
||||
**References**: `make check-local-refs`, `make fix-local-refs`, `make fix-local-refs-dry`
|
||||
**Versioning**:
|
||||
|
||||
- `make release [VERSION=vYYYY.MM.DD]` - Create release (auto-generates version from date if omitted)
|
||||
- `make update-version-refs MAJOR=vYYYY` - Update action refs to version
|
||||
- `make bump-major-version OLD=vYYYY NEW=vYYYY` - Annual version bump
|
||||
- `make check-version-refs` - Verify current action references
|
||||
|
||||
### Linters
|
||||
|
||||
@@ -105,24 +110,38 @@ Violations cause runtime failures:
|
||||
3. Sanitize `$GITHUB_OUTPUT`: use `printf '%s\n' "$val"` not `echo "$val"`
|
||||
4. Pin external actions to SHA commits (not `@main`/`@v1`)
|
||||
5. Quote shell vars: `"$var"`, `basename -- "$path"` (handles spaces)
|
||||
6. Use local paths: `./action-name` (not `owner/repo/action@main`)
|
||||
6. Use SHA-pinned refs for internal actions: `ivuorinen/actions/action-name@<SHA>`
|
||||
(security, not `./` or `@main`)
|
||||
7. Test regex edge cases (support `1.0.0-rc.1`, `1.0.0+build`)
|
||||
8. Use `set -euo pipefail` at script start
|
||||
8. Use `set -eu` (POSIX) in shell scripts (all scripts are POSIX sh, not bash)
|
||||
9. Never nest `${{ }}` in quoted YAML strings (breaks hashFiles)
|
||||
10. Provide tool fallbacks (macOS/Windows lack Linux tools)
|
||||
|
||||
### Core Requirements
|
||||
|
||||
- External actions SHA-pinned, use `${{ github.token }}`, `set -euo pipefail`
|
||||
- All actions SHA-pinned (external + internal), use `${{ github.token }}`, POSIX shell (`set -eu`)
|
||||
- EditorConfig: 2-space indent, UTF-8, LF, max 200 chars (120 for MD)
|
||||
- Auto-gen README via `action-docs` (note: `npx action-docs --update-readme` doesn't work)
|
||||
- Required error handling
|
||||
- Required error handling, POSIX-compliant scripts
|
||||
|
||||
### Action References
|
||||
|
||||
✅ `./action-name` | ❌ `../action-name` | ❌ `owner/repo/action@main`
|
||||
**Internal actions (in action.yml)**: SHA-pinned full references
|
||||
|
||||
Check: `make check-local-refs`, `make fix-local-refs`
|
||||
- ✅ `ivuorinen/actions/action-name@7061aafd35a2f21b57653e34f2b634b2a19334a9`
|
||||
- ❌ `./action-name` (security risk, not portable when used externally)
|
||||
- ❌ `owner/repo/action@main` (floating reference)
|
||||
|
||||
**Test workflows**: Local references
|
||||
|
||||
- ✅ `./action-name` (tests run within repo)
|
||||
- ❌ `../action-name` (ambiguous paths)
|
||||
|
||||
**External users**: Version tags
|
||||
|
||||
- ✅ `ivuorinen/actions/action-name@v2025` (CalVer major version)
|
||||
|
||||
Check: `make check-version-refs`
|
||||
|
||||
## Validation System
|
||||
|
||||
|
||||
54
Makefile
54
Makefile
@@ -1,7 +1,7 @@
|
||||
# Makefile for GitHub Actions repository
|
||||
# Provides organized task management with parallel execution capabilities
|
||||
|
||||
.PHONY: help all docs 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
|
||||
.PHONY: help all docs 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 update-version-refs bump-major-version check-version-refs
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
# Colors for output
|
||||
@@ -145,6 +145,41 @@ fix-local-refs-dry: ## Preview local action reference fixes (dry run)
|
||||
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"; \
|
||||
echo "$(GREEN)✅ Release created$(RESET)"; \
|
||||
echo ""; \
|
||||
echo "$(YELLOW)Next steps:$(RESET)"; \
|
||||
echo " 1. Review changes: git show HEAD"; \
|
||||
echo " 2. Push tags: git push origin main --tags --force-with-lease"
|
||||
|
||||
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)"
|
||||
@@ -216,14 +251,17 @@ lint-yaml: ## Lint YAML files
|
||||
|
||||
lint-shell: ## Lint shell scripts
|
||||
@echo "$(BLUE)🔍 Linting shell scripts...$(RESET)"
|
||||
@if command -v shellcheck >/dev/null 2>&1; then \
|
||||
if find . -name "*.sh" -not -path "./_tests/*" -exec shellcheck -x {} + 2>/dev/null; then \
|
||||
echo "$(GREEN)✅ Shell linting passed$(RESET)"; \
|
||||
else \
|
||||
echo "$(YELLOW)⚠️ Shell linting issues found$(RESET)" | tee -a $(LOG_FILE); \
|
||||
fi; \
|
||||
@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/*" -exec shellcheck -x {} +; then \
|
||||
echo "$(GREEN)✅ Shell linting passed$(RESET)"; \
|
||||
else \
|
||||
echo "$(BLUE)ℹ️ shellcheck not available, skipping shell script linting$(RESET)"; \
|
||||
echo "$(RED)❌ Shell linting issues found$(RESET)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
lint-python: ## Lint Python files with ruff and pyright
|
||||
|
||||
94
_tools/bump-major-version.sh
Executable file
94
_tools/bump-major-version.sh
Executable file
@@ -0,0 +1,94 @@
|
||||
#!/bin/sh
|
||||
# Bump from one major version to another (annual version bump)
|
||||
set -eu
|
||||
|
||||
OLD_VERSION="${1:-}"
|
||||
NEW_VERSION="${2:-}"
|
||||
|
||||
# Source shared utilities
|
||||
# shellcheck source=_tools/shared.sh
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/shared.sh"
|
||||
|
||||
# Check git availability
|
||||
require_git
|
||||
|
||||
if [ -z "$OLD_VERSION" ] || [ -z "$NEW_VERSION" ]; then
|
||||
printf '%b' "${RED}Error: OLD_VERSION and NEW_VERSION arguments required${NC}\n"
|
||||
printf 'Usage: %s v2025 v2026\n' "$0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate major version format
|
||||
if ! validate_major_version "$OLD_VERSION"; then
|
||||
printf '%b' "${RED}Error: Invalid old version format: $OLD_VERSION${NC}\n"
|
||||
printf 'Expected: vYYYY (e.g., v2025)\n'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! validate_major_version "$NEW_VERSION"; then
|
||||
printf '%b' "${RED}Error: Invalid new version format: $NEW_VERSION${NC}\n"
|
||||
printf 'Expected: vYYYY (e.g., v2026)\n'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%b' "${BLUE}Bumping major version from $OLD_VERSION to $NEW_VERSION${NC}\n"
|
||||
printf '\n'
|
||||
|
||||
# Get SHA for new version tag
|
||||
if ! git rev-parse "$NEW_VERSION" >/dev/null 2>&1; then
|
||||
printf '%b' "${YELLOW}Warning: Tag $NEW_VERSION not found${NC}\n"
|
||||
printf 'Creating tag %s pointing to current HEAD...\n' "$NEW_VERSION"
|
||||
|
||||
if ! current_sha=$(git rev-parse HEAD 2>&1); then
|
||||
printf '%b' "${RED}Error: Failed to get current HEAD SHA${NC}\n" >&2
|
||||
printf 'Git command failed: git rev-parse HEAD\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git tag -a "$NEW_VERSION" -m "Major version $NEW_VERSION"
|
||||
printf '%b' "${GREEN}✓ Created tag $NEW_VERSION pointing to $current_sha${NC}\n"
|
||||
printf '\n'
|
||||
fi
|
||||
|
||||
if ! new_sha=$(git rev-list -n 1 "$NEW_VERSION" 2>&1); then
|
||||
printf '%b' "${RED}Error: Failed to get SHA for tag $NEW_VERSION${NC}\n" >&2
|
||||
printf 'Git command failed: git rev-list -n 1 "%s"\n' "$NEW_VERSION" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$new_sha" ]; then
|
||||
printf '%b' "${RED}Error: Empty SHA returned for tag $NEW_VERSION${NC}\n" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%b' "Target SHA for $NEW_VERSION: ${GREEN}$new_sha${NC}\n"
|
||||
printf '\n'
|
||||
|
||||
# Update all action references
|
||||
printf '%b' "${BLUE}Updating action references...${NC}\n"
|
||||
"$SCRIPT_DIR/update-action-refs.sh" "$NEW_VERSION" "tag"
|
||||
|
||||
# Commit the changes
|
||||
if ! git diff --quiet; then
|
||||
git add -- */action.yml
|
||||
git commit -m "chore: bump major version from $OLD_VERSION to $NEW_VERSION
|
||||
|
||||
This commit updates all internal action references from $OLD_VERSION
|
||||
to $NEW_VERSION.
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>"
|
||||
|
||||
printf '%b' "${GREEN}✅ Committed version bump${NC}\n"
|
||||
else
|
||||
printf '%b' "${BLUE}No changes to commit${NC}\n"
|
||||
fi
|
||||
|
||||
printf '\n'
|
||||
printf '%b' "${GREEN}✅ Major version bumped successfully${NC}\n"
|
||||
printf '\n'
|
||||
printf '%b' "${YELLOW}Remember to update READMEs:${NC}\n"
|
||||
printf ' make docs\n'
|
||||
120
_tools/check-version-refs.sh
Executable file
120
_tools/check-version-refs.sh
Executable file
@@ -0,0 +1,120 @@
|
||||
#!/bin/sh
|
||||
# Check and display all current SHA-pinned action references
|
||||
set -eu
|
||||
|
||||
# Source shared utilities
|
||||
# shellcheck source=_tools/shared.sh
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/shared.sh"
|
||||
|
||||
# Warn once if git is not available
|
||||
if ! has_git; then
|
||||
printf '%b' "${YELLOW}Warning: git is not installed or not in PATH${NC}\n" >&2
|
||||
printf 'Git tag information will not be available.\n' >&2
|
||||
fi
|
||||
|
||||
# Check for required coreutils
|
||||
for tool in find grep sed printf sort cut tr wc; do
|
||||
if ! command -v "$tool" >/dev/null 2>&1; then
|
||||
printf '%b' "${RED}Error: Required tool '%s' is not installed or not in PATH${NC}\n" "$tool" >&2
|
||||
printf 'Please install coreutils to use this script.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
printf '%b' "${BLUE}Current SHA-pinned action references:${NC}\n"
|
||||
printf '\n'
|
||||
|
||||
# Create temp files for processing
|
||||
temp_file=$(safe_mktemp)
|
||||
trap 'rm -f "$temp_file"' EXIT
|
||||
|
||||
temp_input=$(safe_mktemp)
|
||||
trap 'rm -f "$temp_file" "$temp_input"' EXIT
|
||||
|
||||
# Find all action references and collect SHA|action pairs
|
||||
# Use input redirection to avoid subshell issues with pipeline
|
||||
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" ! -path "./_*" ! -path "./.github/*" -exec grep -h "uses: ivuorinen/actions/" {} \; > "$temp_input"
|
||||
|
||||
while IFS= read -r line; do
|
||||
# Extract action name and SHA using sed
|
||||
action=$(echo "$line" | sed -n 's|.*ivuorinen/actions/\([a-z-]*\)@.*|\1|p')
|
||||
sha=$(echo "$line" | sed -n 's|.*@\([a-f0-9]\{40\}\).*|\1|p')
|
||||
|
||||
if [ -n "$action" ] && [ -n "$sha" ]; then
|
||||
printf '%s\n' "$sha|$action" >> "$temp_file"
|
||||
fi
|
||||
done < "$temp_input"
|
||||
|
||||
# Check if we found any references
|
||||
if [ ! -s "$temp_file" ]; then
|
||||
printf '%b' "${YELLOW}No SHA-pinned references found${NC}\n"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Sort by SHA and group
|
||||
sort "$temp_file" | uniq > "${temp_file}.sorted"
|
||||
mv "${temp_file}.sorted" "$temp_file"
|
||||
|
||||
# Count unique SHAs
|
||||
sha_count=$(cut -d'|' -f1 "$temp_file" | sort -u | wc -l | tr -d ' ')
|
||||
|
||||
if [ "$sha_count" -eq 1 ]; then
|
||||
printf '%b' "${GREEN}✓ All references use the same SHA (consistent)${NC}\n"
|
||||
printf '\n'
|
||||
fi
|
||||
|
||||
# Process and display grouped by SHA
|
||||
current_sha=""
|
||||
actions_list=""
|
||||
|
||||
while IFS='|' read -r sha action; do
|
||||
if [ "$sha" != "$current_sha" ]; then
|
||||
# Print previous SHA group if exists
|
||||
if [ -n "$current_sha" ]; then
|
||||
# Try to find tags pointing to this SHA
|
||||
if has_git; then
|
||||
tags=$(git tag --points-at "$current_sha" 2>/dev/null | tr '\n' ', ' | sed 's/,$//')
|
||||
else
|
||||
tags=""
|
||||
fi
|
||||
|
||||
printf '%b' "${GREEN}SHA: $current_sha${NC}\n"
|
||||
if [ -n "$tags" ]; then
|
||||
printf '%b' " Tags: ${BLUE}$tags${NC}\n"
|
||||
fi
|
||||
printf ' Actions: %s\n' "$actions_list"
|
||||
printf '\n'
|
||||
fi
|
||||
|
||||
# Start new SHA group
|
||||
current_sha="$sha"
|
||||
actions_list="$action"
|
||||
else
|
||||
# Add to current SHA group
|
||||
actions_list="$actions_list, $action"
|
||||
fi
|
||||
done < "$temp_file"
|
||||
|
||||
# Print last SHA group
|
||||
if [ -n "$current_sha" ]; then
|
||||
if has_git; then
|
||||
tags=$(git tag --points-at "$current_sha" 2>/dev/null | tr '\n' ', ' | sed 's/,$//')
|
||||
else
|
||||
tags=""
|
||||
fi
|
||||
|
||||
printf '%b' "${GREEN}SHA: $current_sha${NC}\n"
|
||||
if [ -n "$tags" ]; then
|
||||
printf '%b' " Tags: ${BLUE}$tags${NC}\n"
|
||||
fi
|
||||
printf ' Actions: %s\n' "$actions_list"
|
||||
printf '\n'
|
||||
fi
|
||||
|
||||
printf '%b' "${BLUE}Summary:${NC}\n"
|
||||
printf ' Unique SHAs: %s\n' "$sha_count"
|
||||
if [ "$sha_count" -gt 1 ]; then
|
||||
printf '%b' " ${YELLOW}⚠ Warning: Multiple SHAs in use (consider updating)${NC}\n"
|
||||
fi
|
||||
@@ -1,15 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/sh
|
||||
# Build script for GitHub Actions Testing Docker Image
|
||||
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
IMAGE_NAME="ghcr.io/ivuorinen/actions"
|
||||
IMAGE_TAG="${1:-testing-tools}"
|
||||
FULL_IMAGE_NAME="${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
|
||||
echo "Building GitHub Actions Testing Docker Image..."
|
||||
echo "Image: $FULL_IMAGE_NAME"
|
||||
printf 'Building GitHub Actions Testing Docker Image...\n'
|
||||
printf 'Image: %s\n' "$FULL_IMAGE_NAME"
|
||||
|
||||
# Enable BuildKit for better caching and performance
|
||||
export DOCKER_BUILDKIT=1
|
||||
@@ -17,7 +17,7 @@ export DOCKER_BUILDKIT=1
|
||||
# Build the multi-stage image
|
||||
# Check for buildx support up front, then run the appropriate build command
|
||||
if docker buildx version >/dev/null 2>&1; then
|
||||
echo "Using buildx (multi-arch capable)"
|
||||
printf 'Using buildx (multi-arch capable)\n'
|
||||
docker buildx build \
|
||||
--pull \
|
||||
--tag "$FULL_IMAGE_NAME" \
|
||||
@@ -26,7 +26,7 @@ if docker buildx version >/dev/null 2>&1; then
|
||||
--load \
|
||||
"$SCRIPT_DIR"
|
||||
else
|
||||
echo "⚠️ buildx not available, using standard docker build"
|
||||
printf '⚠️ buildx not available, using standard docker build\n'
|
||||
docker build \
|
||||
--pull \
|
||||
--tag "$FULL_IMAGE_NAME" \
|
||||
@@ -35,22 +35,22 @@ else
|
||||
"$SCRIPT_DIR"
|
||||
fi
|
||||
|
||||
echo "Build completed successfully!"
|
||||
echo ""
|
||||
echo "Testing the image..."
|
||||
printf 'Build completed successfully!\n'
|
||||
printf '\n'
|
||||
printf 'Testing the image...\n'
|
||||
|
||||
# Test basic functionality
|
||||
docker run --rm "$FULL_IMAGE_NAME" whoami
|
||||
docker run --rm "$FULL_IMAGE_NAME" shellspec --version
|
||||
docker run --rm "$FULL_IMAGE_NAME" act --version
|
||||
|
||||
echo "Image tests passed!"
|
||||
echo ""
|
||||
echo "To test the image locally:"
|
||||
echo " docker run --rm -it $FULL_IMAGE_NAME"
|
||||
echo ""
|
||||
echo "To push to registry:"
|
||||
echo " docker push $FULL_IMAGE_NAME"
|
||||
echo ""
|
||||
echo "To use in GitHub Actions:"
|
||||
echo " container: $FULL_IMAGE_NAME"
|
||||
printf 'Image tests passed!\n'
|
||||
printf '\n'
|
||||
printf 'To test the image locally:\n'
|
||||
printf ' docker run --rm -it %s\n' "$FULL_IMAGE_NAME"
|
||||
printf '\n'
|
||||
printf 'To push to registry:\n'
|
||||
printf ' docker push %s\n' "$FULL_IMAGE_NAME"
|
||||
printf '\n'
|
||||
printf 'To use in GitHub Actions:\n'
|
||||
printf ' container: %s\n' "$FULL_IMAGE_NAME"
|
||||
|
||||
41
_tools/get-action-sha.sh
Executable file
41
_tools/get-action-sha.sh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/bin/sh
|
||||
# Get the SHA for a specific version tag
|
||||
set -eu
|
||||
|
||||
VERSION="${1:-}"
|
||||
|
||||
# Source shared utilities
|
||||
# shellcheck source=_tools/shared.sh
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/shared.sh"
|
||||
|
||||
# Check git availability
|
||||
require_git
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
printf '%b' "${RED}Error: VERSION argument required${NC}\n" >&2
|
||||
printf 'Usage: %s v2025\n' "$0" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if tag exists
|
||||
if ! git rev-parse "$VERSION" >/dev/null 2>&1; then
|
||||
printf '%b' "${RED}Error: Tag $VERSION not found${NC}\n" >&2
|
||||
printf '\n' >&2
|
||||
printf '%b' "${BLUE}Available tags:${NC}\n" >&2
|
||||
git tag -l 'v*' | head -20 >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get SHA for the tag
|
||||
sha=$(git rev-list -n 1 "$VERSION")
|
||||
|
||||
# Check if output is for terminal or pipe
|
||||
if [ -t 1 ]; then
|
||||
# Terminal output - show with colors
|
||||
printf '%b' "${GREEN}$sha${NC}\n"
|
||||
else
|
||||
# Piped output - just the SHA
|
||||
printf '%s\n' "$sha"
|
||||
fi
|
||||
102
_tools/release.sh
Executable file
102
_tools/release.sh
Executable file
@@ -0,0 +1,102 @@
|
||||
#!/bin/sh
|
||||
# Release script for creating versioned tags and updating action references
|
||||
set -eu
|
||||
|
||||
VERSION="${1:-}"
|
||||
|
||||
# Source shared utilities
|
||||
# shellcheck source=_tools/shared.sh
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/shared.sh"
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
printf '%b' "${RED}Error: VERSION argument required${NC}\n"
|
||||
printf 'Usage: %s v2025.10.18\n' "$0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate version format
|
||||
if ! validate_version "$VERSION"; then
|
||||
printf '%b' "${RED}Error: Invalid version format: $VERSION${NC}\n"
|
||||
printf 'Expected: vYYYY.MM.DD (e.g., v2025.10.18)\n'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract version components
|
||||
# Remove leading 'v'
|
||||
version_no_v="${VERSION#v}"
|
||||
# Extract year, month, day
|
||||
year=$(echo "$version_no_v" | cut -d'.' -f1)
|
||||
month=$(echo "$version_no_v" | cut -d'.' -f2)
|
||||
day=$(echo "$version_no_v" | cut -d'.' -f3)
|
||||
|
||||
major="v$year"
|
||||
minor="v$year.$month"
|
||||
patch="v$year.$month.$day"
|
||||
|
||||
printf '%b' "${BLUE}Creating release $VERSION${NC}\n"
|
||||
printf ' Major: %s\n' "$major"
|
||||
printf ' Minor: %s\n' "$minor"
|
||||
printf ' Patch: %s\n' "$patch"
|
||||
printf '\n'
|
||||
|
||||
# Get current commit SHA
|
||||
current_sha=$(git rev-parse HEAD)
|
||||
printf '%b' "Current HEAD: ${GREEN}$current_sha${NC}\n"
|
||||
printf '\n'
|
||||
|
||||
# Update all action references to current SHA
|
||||
printf '%b' "${BLUE}Updating action references to $current_sha...${NC}\n"
|
||||
"$SCRIPT_DIR/update-action-refs.sh" "$current_sha" "direct"
|
||||
|
||||
# Commit the changes
|
||||
if ! git diff --quiet; then
|
||||
git add -- */action.yml
|
||||
git commit -m "chore: update action references for release $VERSION
|
||||
|
||||
This commit updates all internal action references to point to the current
|
||||
commit SHA in preparation for release $VERSION."
|
||||
|
||||
# Update SHA since we just created a new commit
|
||||
current_sha=$(git rev-parse HEAD)
|
||||
printf '%b' "${GREEN}✅ Committed updated action references${NC}\n"
|
||||
printf '%b' "New HEAD: ${GREEN}$current_sha${NC}\n"
|
||||
else
|
||||
printf '%b' "${BLUE}No changes to commit${NC}\n"
|
||||
fi
|
||||
|
||||
# Create/update tags
|
||||
printf '%b' "${BLUE}Creating tags...${NC}\n"
|
||||
|
||||
# Create patch tag
|
||||
git tag -a "$patch" -m "Release $patch"
|
||||
printf '%b' " ${GREEN}✓${NC} Created tag: $patch\n"
|
||||
|
||||
# Move/create minor tag
|
||||
if git rev-parse "$minor" >/dev/null 2>&1; then
|
||||
git tag -f -a "$minor" -m "Latest $minor release: $patch"
|
||||
printf '%b' " ${GREEN}✓${NC} Updated tag: $minor (force)\n"
|
||||
else
|
||||
git tag -a "$minor" -m "Latest $minor release: $patch"
|
||||
printf '%b' " ${GREEN}✓${NC} Created tag: $minor\n"
|
||||
fi
|
||||
|
||||
# Move/create major tag
|
||||
if git rev-parse "$major" >/dev/null 2>&1; then
|
||||
git tag -f -a "$major" -m "Latest $major release: $patch"
|
||||
printf '%b' " ${GREEN}✓${NC} Updated tag: $major (force)\n"
|
||||
else
|
||||
git tag -a "$major" -m "Latest $major release: $patch"
|
||||
printf '%b' " ${GREEN}✓${NC} Created tag: $major\n"
|
||||
fi
|
||||
|
||||
printf '\n'
|
||||
printf '%b' "${GREEN}✅ Release $VERSION created successfully${NC}\n"
|
||||
printf '\n'
|
||||
printf '%b' "${YELLOW}All tags point to: $current_sha${NC}\n"
|
||||
printf '\n'
|
||||
printf '%b' "${BLUE}Tags created:${NC}\n"
|
||||
printf ' %s\n' "$patch"
|
||||
printf ' %s\n' "$minor"
|
||||
printf ' %s\n' "$major"
|
||||
124
_tools/shared.sh
Executable file
124
_tools/shared.sh
Executable file
@@ -0,0 +1,124 @@
|
||||
#!/bin/sh
|
||||
# Shared functions and utilities for _tools/ scripts
|
||||
# This file is sourced by other scripts, not executed directly
|
||||
|
||||
# Colors (exported for use by sourcing scripts)
|
||||
# shellcheck disable=SC2034
|
||||
RED='\033[0;31m'
|
||||
# shellcheck disable=SC2034
|
||||
GREEN='\033[0;32m'
|
||||
# shellcheck disable=SC2034
|
||||
BLUE='\033[0;34m'
|
||||
# shellcheck disable=SC2034
|
||||
YELLOW='\033[1;33m'
|
||||
# shellcheck disable=SC2034
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Validate CalVer version format: vYYYY.MM.DD
|
||||
validate_version() {
|
||||
version="$1"
|
||||
|
||||
# Check format: vYYYY.MM.DD using grep
|
||||
if ! echo "$version" | grep -qE '^v[0-9]{4}\.[0-9]{1,2}\.[0-9]{1,2}$'; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Extract components
|
||||
version_no_v="${version#v}"
|
||||
year=$(echo "$version_no_v" | cut -d'.' -f1)
|
||||
month=$(echo "$version_no_v" | cut -d'.' -f2)
|
||||
day=$(echo "$version_no_v" | cut -d'.' -f3)
|
||||
|
||||
# Validate year (2020-2099)
|
||||
if [ "$year" -lt 2020 ] || [ "$year" -gt 2099 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Validate month (1-12)
|
||||
if [ "$month" -lt 1 ] || [ "$month" -gt 12 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Validate day (1-31)
|
||||
if [ "$day" -lt 1 ] || [ "$day" -gt 31 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Validate major version format: vYYYY
|
||||
validate_major_version() {
|
||||
version="$1"
|
||||
|
||||
# Check format: vYYYY using grep
|
||||
if ! echo "$version" | grep -qE '^v[0-9]{4}$'; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Extract year
|
||||
year="${version#v}"
|
||||
|
||||
# Validate year (2020-2099)
|
||||
if [ "$year" -lt 2020 ] || [ "$year" -gt 2099 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Validate minor version format: vYYYY.MM
|
||||
validate_minor_version() {
|
||||
version="$1"
|
||||
|
||||
# Check format: vYYYY.MM using grep
|
||||
if ! echo "$version" | grep -qE '^v[0-9]{4}\.[0-9]{1,2}$'; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Extract components
|
||||
version_no_v="${version#v}"
|
||||
year=$(echo "$version_no_v" | cut -d'.' -f1)
|
||||
month=$(echo "$version_no_v" | cut -d'.' -f2)
|
||||
|
||||
# Validate year (2020-2099)
|
||||
if [ "$year" -lt 2020 ] || [ "$year" -gt 2099 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Validate month (1-12)
|
||||
if [ "$month" -lt 1 ] || [ "$month" -gt 12 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Get the directory where the calling script is located
|
||||
get_script_dir() {
|
||||
cd "$(dirname -- "$1")" && pwd
|
||||
}
|
||||
|
||||
# Check if git is available
|
||||
has_git() {
|
||||
command -v git >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Require git to be available, exit with error if not
|
||||
require_git() {
|
||||
if ! has_git; then
|
||||
printf '%b' "${RED}Error: git is not installed or not in PATH${NC}\n" >&2
|
||||
printf 'Please install git to use this script.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Create temp file with error checking
|
||||
safe_mktemp() {
|
||||
_temp_file=""
|
||||
if ! _temp_file=$(mktemp); then
|
||||
printf '%b' "${RED}Error: Failed to create temp file${NC}\n" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf '%s' "$_temp_file"
|
||||
}
|
||||
71
_tools/update-action-refs.sh
Executable file
71
_tools/update-action-refs.sh
Executable file
@@ -0,0 +1,71 @@
|
||||
#!/bin/sh
|
||||
# Update all action references to a specific version tag or SHA
|
||||
set -eu
|
||||
|
||||
TARGET="${1:-}"
|
||||
MODE="${2:-tag}" # 'tag' or 'direct'
|
||||
|
||||
# Source shared utilities
|
||||
# shellcheck source=_tools/shared.sh
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/shared.sh"
|
||||
|
||||
# Check git availability
|
||||
require_git
|
||||
|
||||
if [ -z "$TARGET" ]; then
|
||||
printf '%b' "${RED}Error: TARGET argument required${NC}\n"
|
||||
printf 'Usage: %s v2025 [mode]\n' "$0"
|
||||
printf ' mode: '\''tag'\'' (default) or '\''direct'\''\n'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get SHA based on mode
|
||||
if [ "$MODE" = "direct" ]; then
|
||||
# Direct SHA provided
|
||||
target_sha="$TARGET"
|
||||
printf '%b' "${BLUE}Using direct SHA: $target_sha${NC}\n"
|
||||
elif [ "$MODE" = "tag" ]; then
|
||||
# Resolve tag to SHA
|
||||
if ! git rev-parse "$TARGET" >/dev/null 2>&1; then
|
||||
printf '%b' "${RED}Error: Tag $TARGET not found${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
target_sha=$(git rev-list -n 1 "$TARGET")
|
||||
printf '%b' "${BLUE}Resolved $TARGET to SHA: $target_sha${NC}\n"
|
||||
else
|
||||
printf '%b' "${RED}Error: Invalid mode: $MODE${NC}\n"
|
||||
printf 'Mode must be '\''tag'\'' or '\''direct'\''\n'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate SHA format
|
||||
if ! echo "$target_sha" | grep -qE '^[a-f0-9]{40}$'; then
|
||||
printf '%b' "${RED}Error: Invalid SHA format: $target_sha${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%b' "${BLUE}Updating action references...${NC}\n"
|
||||
|
||||
# Update all action.yml files (excluding tests and .github workflows)
|
||||
# Create temp file to store results
|
||||
temp_file=$(safe_mktemp)
|
||||
trap 'rm -f "$temp_file"' EXIT
|
||||
|
||||
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" ! -path "./_*" ! -path "./.github/*" | while IFS= read -r file; do
|
||||
# Use .bak extension for cross-platform sed compatibility
|
||||
if sed -i.bak "s|ivuorinen/actions/\([a-z-]*\)@[a-f0-9]\{40\}|ivuorinen/actions/\1@$target_sha|g" "$file"; then
|
||||
rm -f "${file}.bak"
|
||||
printf '%b' " ${GREEN}✓${NC} Updated: $file\n"
|
||||
echo "$file" >> "$temp_file"
|
||||
fi
|
||||
done
|
||||
|
||||
printf '\n'
|
||||
if [ -s "$temp_file" ]; then
|
||||
updated_count=$(wc -l < "$temp_file" | tr -d ' ')
|
||||
printf '%b' "${GREEN}✅ Updated $updated_count action files${NC}\n"
|
||||
else
|
||||
printf '%b' "${BLUE}No files needed updating${NC}\n"
|
||||
fi
|
||||
44
action-versioning/README.md
Normal file
44
action-versioning/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# ivuorinen/actions/action-versioning
|
||||
|
||||
## Action Versioning
|
||||
|
||||
### Description
|
||||
|
||||
Automatically update SHA-pinned action references to match latest version tags
|
||||
|
||||
### Inputs
|
||||
|
||||
| name | description | required | default |
|
||||
|-----------------|------------------------------------------------|----------|---------|
|
||||
| `major-version` | <p>Major version tag to sync (e.g., v2025)</p> | `true` | `""` |
|
||||
| `token` | <p>GitHub token for authentication</p> | `false` | `""` |
|
||||
|
||||
### Outputs
|
||||
|
||||
| name | description |
|
||||
|---------------------|------------------------------------------------------------|
|
||||
| `updated` | <p>Whether action references were updated (true/false)</p> |
|
||||
| `commit-sha` | <p>SHA of the commit that was created (if any)</p> |
|
||||
| `needs-annual-bump` | <p>Whether annual version bump is needed (true/false)</p> |
|
||||
|
||||
### Runs
|
||||
|
||||
This action is a `composite` action.
|
||||
|
||||
### Usage
|
||||
|
||||
```yaml
|
||||
- uses: ivuorinen/actions/action-versioning@main
|
||||
with:
|
||||
major-version:
|
||||
# Major version tag to sync (e.g., v2025)
|
||||
#
|
||||
# Required: true
|
||||
# Default: ""
|
||||
|
||||
token:
|
||||
# GitHub token for authentication
|
||||
#
|
||||
# Required: false
|
||||
# Default: ""
|
||||
```
|
||||
165
action-versioning/action.yml
Normal file
165
action-versioning/action.yml
Normal file
@@ -0,0 +1,165 @@
|
||||
# yaml-language-server: $schema=https://json.schemastore.org/github-action.json
|
||||
# permissions:
|
||||
# - contents: write # Required for creating commits
|
||||
---
|
||||
name: Action Versioning
|
||||
description: 'Automatically update SHA-pinned action references to match latest version tags'
|
||||
author: 'Ismo Vuorinen'
|
||||
|
||||
branding:
|
||||
icon: git-commit
|
||||
color: blue
|
||||
|
||||
inputs:
|
||||
major-version:
|
||||
description: 'Major version tag to sync (e.g., v2025)'
|
||||
required: true
|
||||
token:
|
||||
description: 'GitHub token for authentication'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
outputs:
|
||||
updated:
|
||||
description: 'Whether action references were updated (true/false)'
|
||||
value: ${{ steps.check-update.outputs.updated }}
|
||||
commit-sha:
|
||||
description: 'SHA of the commit that was created (if any)'
|
||||
value: ${{ steps.commit.outputs.sha }}
|
||||
needs-annual-bump:
|
||||
description: 'Whether annual version bump is needed (true/false)'
|
||||
value: ${{ steps.check-year.outputs.needs-bump }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
token: ${{ inputs.token || github.token }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check Current Year
|
||||
id: check-year
|
||||
shell: sh
|
||||
env:
|
||||
MAJOR_VERSION: ${{ inputs.major-version }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
current_year=$(date +%Y)
|
||||
version_year="${MAJOR_VERSION#v}"
|
||||
|
||||
if [ "$version_year" != "$current_year" ]; then
|
||||
echo "::warning::Annual version bump needed: $MAJOR_VERSION -> v$current_year"
|
||||
printf '%s\n' "needs-bump=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
printf '%s\n' "needs-bump=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Fetch Version Tag SHA
|
||||
id: fetch-sha
|
||||
shell: sh
|
||||
env:
|
||||
MAJOR_VERSION: ${{ inputs.major-version }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
# Fetch all tags
|
||||
git fetch --tags --force
|
||||
|
||||
# Get SHA for the major version tag
|
||||
if ! tag_sha=$(git rev-list -n 1 "$MAJOR_VERSION" 2>/dev/null); then
|
||||
echo "::error::Tag $MAJOR_VERSION not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "tag-sha=$tag_sha" >> "$GITHUB_OUTPUT"
|
||||
echo "Tag $MAJOR_VERSION points to: $tag_sha"
|
||||
|
||||
- name: Check if Update Needed
|
||||
id: check-update
|
||||
shell: sh
|
||||
env:
|
||||
TAG_SHA: ${{ steps.fetch-sha.outputs.tag-sha }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
# Find all action references and check if any don't match the tag SHA
|
||||
needs_update=false
|
||||
|
||||
# Create temp file for action references
|
||||
temp_file=$(mktemp)
|
||||
trap 'rm -f "$temp_file"' EXIT
|
||||
|
||||
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" ! -path "./_*" ! -path "./.github/*" -exec grep -h "uses: ivuorinen/actions/" {} \; > "$temp_file"
|
||||
|
||||
while IFS= read -r line; do
|
||||
current_sha=$(echo "$line" | grep -oE '@[a-f0-9]{40}' | sed 's/@//')
|
||||
|
||||
if [ "$current_sha" != "$TAG_SHA" ]; then
|
||||
echo "Found outdated reference: $current_sha (should be $TAG_SHA)"
|
||||
needs_update=true
|
||||
fi
|
||||
done < "$temp_file"
|
||||
|
||||
if [ "$needs_update" = "true" ]; then
|
||||
printf '%s\n' "updated=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Update needed - references are outdated"
|
||||
else
|
||||
printf '%s\n' "updated=false" >> "$GITHUB_OUTPUT"
|
||||
echo "No update needed - all references are current"
|
||||
fi
|
||||
|
||||
- name: Update Action References
|
||||
if: steps.check-update.outputs.updated == 'true'
|
||||
shell: sh
|
||||
env:
|
||||
TAG_SHA: ${{ steps.fetch-sha.outputs.tag-sha }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
echo "Updating all action references to SHA: $TAG_SHA"
|
||||
|
||||
# Update all action.yml files (excluding tests and .github)
|
||||
# Use .bak extension for cross-platform sed compatibility
|
||||
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" ! -path "./_*" ! -path "./.github/*" -exec sed -i.bak \
|
||||
"s|ivuorinen/actions/\([a-z-]*\)@[a-f0-9]\{40\}|ivuorinen/actions/\1@$TAG_SHA|g" {} \;
|
||||
|
||||
# Remove backup files
|
||||
find . -maxdepth 2 -name "action.yml.bak" -path "*/action.yml.bak" ! -path "./_*" ! -path "./.github/*" -delete
|
||||
|
||||
echo "Action references updated successfully"
|
||||
|
||||
- name: Commit Changes
|
||||
if: steps.check-update.outputs.updated == 'true'
|
||||
id: commit
|
||||
shell: sh
|
||||
env:
|
||||
MAJOR_VERSION: ${{ inputs.major-version }}
|
||||
TAG_SHA: ${{ steps.fetch-sha.outputs.tag-sha }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
git add -- */action.yml
|
||||
|
||||
if git diff --staged --quiet; then
|
||||
echo "No changes to commit"
|
||||
printf '%s\n' "sha=" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
git commit -m "chore: update action references to $MAJOR_VERSION ($TAG_SHA)" \
|
||||
-m "" \
|
||||
-m "This commit updates all internal action references to point to the latest" \
|
||||
-m "$MAJOR_VERSION tag SHA." \
|
||||
-m "" \
|
||||
-m "🤖 Generated with [Claude Code](https://claude.com/claude-code)" \
|
||||
-m "" \
|
||||
-m "Co-Authored-By: Claude <noreply@anthropic.com>"
|
||||
|
||||
commit_sha=$(git rev-parse HEAD)
|
||||
printf '%s\n' "sha=$commit_sha" >> "$GITHUB_OUTPUT"
|
||||
echo "Created commit: $commit_sha"
|
||||
fi
|
||||
37
action-versioning/rules.yml
Normal file
37
action-versioning/rules.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
# Validation rules for action-versioning action
|
||||
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
|
||||
# Schema version: 1.0
|
||||
# Coverage: 100% (2/2 inputs)
|
||||
#
|
||||
# This file defines validation rules for the action-versioning GitHub Action.
|
||||
# Rules are automatically applied by validate-inputs action when this
|
||||
# action is used.
|
||||
#
|
||||
|
||||
schema_version: '1.0'
|
||||
action: action-versioning
|
||||
description: Automatically update SHA-pinned action references to match latest version tags
|
||||
generator_version: 1.0.0
|
||||
required_inputs:
|
||||
- major-version
|
||||
optional_inputs:
|
||||
- token
|
||||
conventions:
|
||||
major-version: semantic_version
|
||||
token: github_token
|
||||
overrides: {}
|
||||
statistics:
|
||||
total_inputs: 2
|
||||
validated_inputs: 2
|
||||
skipped_inputs: 0
|
||||
coverage_percentage: 100
|
||||
validation_coverage: 100
|
||||
auto_detected: true
|
||||
manual_review_required: false
|
||||
quality_indicators:
|
||||
has_required_inputs: true
|
||||
has_token_validation: true
|
||||
has_version_validation: true
|
||||
has_file_validation: false
|
||||
has_security_validation: true
|
||||
@@ -112,7 +112,7 @@ runs:
|
||||
- name: Cache Python Dependencies
|
||||
if: steps.check-files.outputs.files_found == 'true'
|
||||
id: cache-pip
|
||||
uses: ./common-cache
|
||||
uses: ivuorinen/actions/common-cache@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
type: 'pip'
|
||||
paths: '~/.cache/pip'
|
||||
@@ -120,8 +120,9 @@ runs:
|
||||
key-prefix: 'ansible-lint-fix'
|
||||
|
||||
- name: Install ansible-lint
|
||||
id: install-ansible-lint
|
||||
if: steps.check-files.outputs.files_found == 'true'
|
||||
uses: ./common-retry
|
||||
uses: ivuorinen/actions/common-retry@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
command: 'pip install ansible-lint==6.22.1'
|
||||
max-retries: ${{ inputs.max-retries }}
|
||||
@@ -159,8 +160,9 @@ runs:
|
||||
exit "$lint_exit_code"
|
||||
|
||||
- name: Set Git Config for Fixes
|
||||
id: set-git-config
|
||||
if: steps.check-files.outputs.files_found == 'true'
|
||||
uses: ./set-git-config
|
||||
uses: ivuorinen/actions/set-git-config@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
token: ${{ inputs.token }}
|
||||
username: ${{ inputs.username }}
|
||||
|
||||
@@ -44,7 +44,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Validate Inputs (Centralized)
|
||||
uses: ./validate-inputs
|
||||
uses: ivuorinen/actions/validate-inputs@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
action: biome-check
|
||||
|
||||
@@ -112,7 +112,7 @@ runs:
|
||||
token: ${{ inputs.token }}
|
||||
|
||||
- name: Set Git Config
|
||||
uses: ./set-git-config
|
||||
uses: ivuorinen/actions/set-git-config@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
token: ${{ inputs.token }}
|
||||
username: ${{ inputs.username }}
|
||||
@@ -120,11 +120,11 @@ runs:
|
||||
|
||||
- name: Node Setup
|
||||
id: node-setup
|
||||
uses: ./node-setup
|
||||
uses: ivuorinen/actions/node-setup@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
|
||||
- name: Cache Node Dependencies
|
||||
id: cache
|
||||
uses: ./common-cache
|
||||
uses: ivuorinen/actions/common-cache@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
type: 'npm'
|
||||
paths: 'node_modules'
|
||||
|
||||
@@ -41,44 +41,48 @@ runs:
|
||||
steps:
|
||||
- name: Validate Inputs
|
||||
id: validate
|
||||
shell: bash
|
||||
shell: sh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.token }}
|
||||
EMAIL: ${{ inputs.email }}
|
||||
USERNAME: ${{ inputs.username }}
|
||||
MAX_RETRIES: ${{ inputs.max-retries }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
|
||||
# Validate GitHub token format (basic validation)
|
||||
if [[ -n "$GITHUB_TOKEN" ]]; then
|
||||
if [ -n "$GITHUB_TOKEN" ]; then
|
||||
# Skip validation for GitHub expressions (they'll be resolved at runtime)
|
||||
if ! [[ "$GITHUB_TOKEN" =~ ^gh[efpousr]_[a-zA-Z0-9]{36}$ ]] && ! [[ "$GITHUB_TOKEN" =~ ^\$\{\{ ]]; then
|
||||
if ! echo "$GITHUB_TOKEN" | grep -Eq '^gh[efpousr]_[a-zA-Z0-9]{36}$' && ! echo "$GITHUB_TOKEN" | grep -q '^\${{'; then
|
||||
echo "::warning::GitHub token format may be invalid. Expected format: gh*_36characters"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate email format (basic check)
|
||||
if [[ "$EMAIL" != *"@"* ]] || [[ "$EMAIL" != *"."* ]]; then
|
||||
echo "::error::Invalid email format: '$EMAIL'. Expected valid email address"
|
||||
exit 1
|
||||
fi
|
||||
case "$EMAIL" in
|
||||
*@*.*) ;;
|
||||
*)
|
||||
echo "::error::Invalid email format: '$EMAIL'. Expected valid email address"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Validate username format (prevent command injection)
|
||||
if [[ "$USERNAME" =~ [;&|] ]]; then
|
||||
if echo "$USERNAME" | grep -Eq '[;&|]'; then
|
||||
echo "::error::Invalid username: '$USERNAME'. Command injection patterns not allowed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate username length
|
||||
username="$USERNAME"
|
||||
if [ ${#username} -gt 39 ]; then
|
||||
echo "::error::Username too long: ${#username} characters. GitHub usernames are max 39 characters"
|
||||
username_len=$(echo -n "$username" | wc -c | tr -d ' ')
|
||||
if [ "$username_len" -gt 39 ]; then
|
||||
echo "::error::Username too long: ${username_len} characters. GitHub usernames are max 39 characters"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate max retries (positive integer with reasonable upper limit)
|
||||
if ! [[ "$MAX_RETRIES" =~ ^[0-9]+$ ]] || [ "$MAX_RETRIES" -le 0 ] || [ "$MAX_RETRIES" -gt 10 ]; then
|
||||
if ! echo "$MAX_RETRIES" | grep -Eq '^[0-9]+$' || [ "$MAX_RETRIES" -le 0 ] || [ "$MAX_RETRIES" -gt 10 ]; then
|
||||
echo "::error::Invalid max-retries: '$MAX_RETRIES'. Must be a positive integer between 1 and 10"
|
||||
exit 1
|
||||
fi
|
||||
@@ -91,7 +95,7 @@ runs:
|
||||
token: ${{ inputs.token }}
|
||||
|
||||
- name: Set Git Config
|
||||
uses: ./set-git-config
|
||||
uses: ivuorinen/actions/set-git-config@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
token: ${{ inputs.token }}
|
||||
username: ${{ inputs.username }}
|
||||
@@ -99,11 +103,11 @@ runs:
|
||||
|
||||
- name: Node Setup
|
||||
id: node-setup
|
||||
uses: ./node-setup
|
||||
uses: ivuorinen/actions/node-setup@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
|
||||
- name: Cache Node Dependencies
|
||||
id: cache
|
||||
uses: ./common-cache
|
||||
uses: ivuorinen/actions/common-cache@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
type: 'npm'
|
||||
paths: 'node_modules'
|
||||
@@ -111,12 +115,12 @@ runs:
|
||||
key-prefix: 'biome-fix-${{ steps.node-setup.outputs.package-manager }}'
|
||||
|
||||
- name: Install Biome
|
||||
shell: bash
|
||||
shell: sh
|
||||
env:
|
||||
PACKAGE_MANAGER: ${{ steps.node-setup.outputs.package-manager }}
|
||||
MAX_RETRIES: ${{ inputs.max-retries }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
|
||||
# Check if biome is already installed
|
||||
if command -v biome >/dev/null 2>&1; then
|
||||
@@ -167,9 +171,9 @@ runs:
|
||||
|
||||
- name: Run Biome Fix
|
||||
id: fix
|
||||
shell: bash
|
||||
shell: sh
|
||||
run: |
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
|
||||
echo "Running Biome fix..."
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Validate inputs
|
||||
uses: ./validate-inputs
|
||||
uses: ivuorinen/actions/validate-inputs@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
action-type: codeql-analysis
|
||||
language: ${{ inputs.language }}
|
||||
|
||||
@@ -143,7 +143,7 @@ runs:
|
||||
fi
|
||||
- name: Set Git Config
|
||||
id: set-git-config
|
||||
uses: ./set-git-config
|
||||
uses: ivuorinen/actions/set-git-config@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
token: ${{ inputs.token }}
|
||||
username: ${{ inputs.username }}
|
||||
|
||||
@@ -50,7 +50,7 @@ runs:
|
||||
|
||||
- name: Detect .NET SDK Version
|
||||
id: detect-dotnet-version
|
||||
uses: ./dotnet-version-detect
|
||||
uses: ivuorinen/actions/dotnet-version-detect@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
default-version: "${{ inputs.dotnet-version || '7.0' }}"
|
||||
|
||||
@@ -61,7 +61,7 @@ runs:
|
||||
|
||||
- name: Cache NuGet packages
|
||||
id: cache-nuget
|
||||
uses: ./common-cache
|
||||
uses: ivuorinen/actions/common-cache@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
type: 'nuget'
|
||||
paths: '~/.nuget/packages'
|
||||
@@ -70,7 +70,7 @@ runs:
|
||||
|
||||
- name: Restore Dependencies
|
||||
if: steps.cache-nuget.outputs.cache-hit != 'true'
|
||||
uses: ./common-retry
|
||||
uses: ivuorinen/actions/common-retry@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
command: |
|
||||
echo "Restoring .NET dependencies..."
|
||||
|
||||
@@ -66,7 +66,7 @@ runs:
|
||||
|
||||
- name: Detect .NET SDK Version
|
||||
id: detect-dotnet-version
|
||||
uses: ./dotnet-version-detect
|
||||
uses: ivuorinen/actions/dotnet-version-detect@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
default-version: ${{ inputs.dotnet-version || '7.0' }}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ runs:
|
||||
|
||||
- name: Validate Inputs
|
||||
id: validate
|
||||
uses: ./validate-inputs
|
||||
uses: ivuorinen/actions/validate-inputs@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
action-type: 'csharp-publish'
|
||||
token: ${{ inputs.token }}
|
||||
@@ -60,7 +60,7 @@ runs:
|
||||
|
||||
- name: Detect .NET SDK Version
|
||||
id: detect-dotnet-version
|
||||
uses: ./dotnet-version-detect
|
||||
uses: ivuorinen/actions/dotnet-version-detect@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
default-version: '7.0'
|
||||
|
||||
@@ -71,7 +71,7 @@ runs:
|
||||
|
||||
- name: Cache NuGet packages
|
||||
id: cache-nuget
|
||||
uses: ./common-cache
|
||||
uses: ivuorinen/actions/common-cache@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
type: 'nuget'
|
||||
paths: '~/.nuget/packages'
|
||||
@@ -116,11 +116,11 @@ runs:
|
||||
if [ -n "$PACKAGE_FILE" ]; then
|
||||
# Extract version from filename (assumes standard naming: PackageName.Version.nupkg)
|
||||
VERSION=$(basename "$PACKAGE_FILE" .nupkg | sed 's/.*\.\([0-9]\+\.[0-9]\+\.[0-9]\+.*\)$/\1/')
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "package_file=$PACKAGE_FILE" >> "$GITHUB_OUTPUT"
|
||||
printf '%s\n' "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
printf '%s\n' "package_file=$PACKAGE_FILE" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "version=unknown" >> "$GITHUB_OUTPUT"
|
||||
echo "package_file=" >> "$GITHUB_OUTPUT"
|
||||
printf '%s\n' "version=unknown" >> "$GITHUB_OUTPUT"
|
||||
printf '%s\n' "package_file=" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Publish Package
|
||||
@@ -133,7 +133,7 @@ runs:
|
||||
set -euo pipefail
|
||||
|
||||
PACKAGE_URL="https://github.com/$NAMESPACE/packages/nuget"
|
||||
echo "package_url=$PACKAGE_URL" >> $GITHUB_OUTPUT
|
||||
printf '%s\n' "package_url=$PACKAGE_URL" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# First attempt
|
||||
if ! dotnet nuget push ./artifacts/*.nupkg \
|
||||
@@ -159,4 +159,4 @@ runs:
|
||||
env:
|
||||
PUBLISH_STATUS: ${{ steps.publish-package.outcome == 'success' && 'success' || 'failure' }}
|
||||
run: |-
|
||||
echo "status=$PUBLISH_STATUS" >> $GITHUB_OUTPUT
|
||||
printf '%s\n' "status=$PUBLISH_STATUS" >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -147,7 +147,7 @@ runs:
|
||||
|
||||
- name: Validate Inputs
|
||||
id: validate
|
||||
uses: ./validate-inputs
|
||||
uses: ivuorinen/actions/validate-inputs@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
action-type: 'docker-build'
|
||||
image-name: ${{ inputs.image-name }}
|
||||
|
||||
@@ -170,7 +170,7 @@ runs:
|
||||
|
||||
- name: Build Multi-Arch Docker Image
|
||||
id: build
|
||||
uses: ./docker-build
|
||||
uses: ivuorinen/actions/docker-build@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
tag: ${{ steps.tags.outputs.all-tags }}
|
||||
architectures: ${{ inputs.platforms }}
|
||||
@@ -185,7 +185,7 @@ runs:
|
||||
- name: Publish to Docker Hub
|
||||
id: publish-dockerhub
|
||||
if: contains(steps.dest.outputs.reg, 'dockerhub')
|
||||
uses: ./docker-publish-hub
|
||||
uses: ivuorinen/actions/docker-publish-hub@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
tags: ${{ steps.tags.outputs.all-tags }}
|
||||
platforms: ${{ inputs.platforms }}
|
||||
@@ -201,7 +201,7 @@ runs:
|
||||
- name: Publish to GitHub Packages
|
||||
id: publish-github
|
||||
if: contains(steps.dest.outputs.reg, 'github')
|
||||
uses: ./docker-publish-gh
|
||||
uses: ivuorinen/actions/docker-publish-gh@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
tags: ${{ steps.tags.outputs.all-tags }}
|
||||
platforms: ${{ inputs.platforms }}
|
||||
|
||||
@@ -58,7 +58,7 @@ runs:
|
||||
|
||||
- name: Parse .NET Version
|
||||
id: parse-version
|
||||
uses: ./version-file-parser
|
||||
uses: ivuorinen/actions/version-file-parser@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
language: 'dotnet'
|
||||
tool-versions-key: 'dotnet'
|
||||
|
||||
@@ -176,11 +176,11 @@ runs:
|
||||
|
||||
- name: Setup Node.js
|
||||
id: node-setup
|
||||
uses: ./node-setup
|
||||
uses: ivuorinen/actions/node-setup@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
|
||||
- name: Cache Node Dependencies
|
||||
id: cache
|
||||
uses: ./common-cache
|
||||
uses: ivuorinen/actions/common-cache@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
type: 'npm'
|
||||
paths: 'node_modules'
|
||||
|
||||
@@ -44,7 +44,7 @@ runs:
|
||||
steps:
|
||||
- name: Validate Inputs
|
||||
id: validate
|
||||
uses: ./validate-inputs
|
||||
uses: ivuorinen/actions/validate-inputs@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
action-type: 'eslint-fix'
|
||||
token: ${{ inputs.token }}
|
||||
@@ -58,7 +58,7 @@ runs:
|
||||
token: ${{ inputs.token }}
|
||||
|
||||
- name: Set Git Config
|
||||
uses: ./set-git-config
|
||||
uses: ivuorinen/actions/set-git-config@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
token: ${{ inputs.token }}
|
||||
username: ${{ inputs.username }}
|
||||
@@ -66,11 +66,11 @@ runs:
|
||||
|
||||
- name: Node Setup
|
||||
id: node-setup
|
||||
uses: ./node-setup
|
||||
uses: ivuorinen/actions/node-setup@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
|
||||
- name: Cache Node Dependencies
|
||||
id: cache
|
||||
uses: ./common-cache
|
||||
uses: ivuorinen/actions/common-cache@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
type: 'npm'
|
||||
paths: 'node_modules'
|
||||
|
||||
@@ -54,7 +54,7 @@ runs:
|
||||
|
||||
- name: Detect Go Version
|
||||
id: detect-go-version
|
||||
uses: ./go-version-detect
|
||||
uses: ivuorinen/actions/go-version-detect@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
default-version: "${{ inputs.go-version || '1.21' }}"
|
||||
|
||||
@@ -66,7 +66,7 @@ runs:
|
||||
|
||||
- name: Cache Go Dependencies
|
||||
id: cache-go
|
||||
uses: ./common-cache
|
||||
uses: ivuorinen/actions/common-cache@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
type: 'go'
|
||||
paths: '~/go/pkg/mod'
|
||||
@@ -75,7 +75,7 @@ runs:
|
||||
|
||||
- name: Download Dependencies
|
||||
if: steps.cache-go.outputs.cache-hit != 'true'
|
||||
uses: ./common-retry
|
||||
uses: ivuorinen/actions/common-retry@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
command: |
|
||||
echo "Downloading Go dependencies..."
|
||||
|
||||
@@ -86,7 +86,7 @@ runs:
|
||||
steps:
|
||||
- name: Validate Inputs
|
||||
id: validate
|
||||
shell: bash
|
||||
shell: sh
|
||||
env:
|
||||
WORKING_DIRECTORY: ${{ inputs.working-directory }}
|
||||
GOLANGCI_LINT_VERSION: ${{ inputs.golangci-lint-version }}
|
||||
@@ -102,7 +102,7 @@ runs:
|
||||
ENABLE_LINTERS: ${{ inputs.enable-linters }}
|
||||
DISABLE_LINTERS: ${{ inputs.disable-linters }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
|
||||
# Validate working directory exists
|
||||
if [ ! -d "$WORKING_DIRECTORY" ]; then
|
||||
@@ -111,49 +111,56 @@ runs:
|
||||
fi
|
||||
|
||||
# Validate working directory path security (prevent traversal)
|
||||
if [[ "$WORKING_DIRECTORY" == *".."* ]]; then
|
||||
echo "::error::Invalid working directory path: '$WORKING_DIRECTORY'. Path traversal not allowed"
|
||||
exit 1
|
||||
fi
|
||||
case "$WORKING_DIRECTORY" in
|
||||
*..*)
|
||||
echo "::error::Invalid working directory path: '$WORKING_DIRECTORY'. Path traversal not allowed"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Validate golangci-lint version format
|
||||
if [[ -n "$GOLANGCI_LINT_VERSION" ]] && [[ "$GOLANGCI_LINT_VERSION" != "latest" ]]; then
|
||||
if ! [[ "$GOLANGCI_LINT_VERSION" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$ ]]; then
|
||||
if [ -n "$GOLANGCI_LINT_VERSION" ] && [ "$GOLANGCI_LINT_VERSION" != "latest" ]; then
|
||||
if ! echo "$GOLANGCI_LINT_VERSION" | grep -Eq '^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$'; then
|
||||
echo "::error::Invalid golangci-lint-version format: '$GOLANGCI_LINT_VERSION'. Expected format: vX.Y.Z or 'latest' (e.g., v1.55.2, latest)"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate Go version format
|
||||
if [[ -n "$GO_VERSION" ]] && [[ "$GO_VERSION" != "stable" ]]; then
|
||||
if ! [[ "$GO_VERSION" =~ ^[0-9]+(\.[0-9]+(\.[0-9]+)?)?$ ]]; then
|
||||
if [ -n "$GO_VERSION" ] && [ "$GO_VERSION" != "stable" ]; then
|
||||
if ! echo "$GO_VERSION" | grep -Eq '^[0-9]+(\.[0-9]+(\.[0-9]+)?)?$'; then
|
||||
echo "::error::Invalid go-version format: '$GO_VERSION'. Expected format: X.Y or X.Y.Z or 'stable' (e.g., 1.21, 1.21.5, stable)"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate config file path if not default
|
||||
if [[ "$CONFIG_FILE" != ".golangci.yml" ]] && [[ "$CONFIG_FILE" == *".."* ]]; then
|
||||
echo "::error::Invalid config file path: '$CONFIG_FILE'. Path traversal not allowed"
|
||||
exit 1
|
||||
if [ "$CONFIG_FILE" != ".golangci.yml" ]; then
|
||||
case "$CONFIG_FILE" in
|
||||
*..*)
|
||||
echo "::error::Invalid config file path: '$CONFIG_FILE'. Path traversal not allowed"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Validate timeout format (duration with unit)
|
||||
if ! [[ "$TIMEOUT" =~ ^[0-9]+(ns|us|µs|ms|s|m|h)$ ]]; then
|
||||
if ! echo "$TIMEOUT" | grep -Eq '^[0-9]+(ns|us|µs|ms|s|m|h)$'; then
|
||||
echo "::error::Invalid timeout format: '$TIMEOUT'. Expected format with unit: 5m, 1h, 300s (e.g., 5m, 30s, 2h)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate boolean inputs
|
||||
validate_boolean() {
|
||||
local value="$1"
|
||||
local name="$2"
|
||||
_value="$1"
|
||||
_name="$2"
|
||||
_value_lower=$(echo "$_value" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
case "${value,,}" in
|
||||
case "$_value_lower" in
|
||||
true|false)
|
||||
;;
|
||||
*)
|
||||
echo "::error::Invalid boolean value for $name: '$value'. Expected: true or false"
|
||||
echo "::error::Invalid boolean value for $_name: '$_value'. Expected: true or false"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -176,19 +183,19 @@ runs:
|
||||
esac
|
||||
|
||||
# Validate max retries (positive integer with reasonable upper limit)
|
||||
if ! [[ "$MAX_RETRIES" =~ ^[0-9]+$ ]] || [ "$MAX_RETRIES" -le 0 ] || [ "$MAX_RETRIES" -gt 10 ]; then
|
||||
if ! echo "$MAX_RETRIES" | grep -Eq '^[0-9]+$' || [ "$MAX_RETRIES" -le 0 ] || [ "$MAX_RETRIES" -gt 10 ]; then
|
||||
echo "::error::Invalid max-retries: '$MAX_RETRIES'. Must be a positive integer between 1 and 10"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate linter lists if provided
|
||||
validate_linter_list() {
|
||||
local linter_list="$1"
|
||||
local name="$2"
|
||||
_linter_list="$1"
|
||||
_name="$2"
|
||||
|
||||
if [[ -n "$linter_list" ]]; then
|
||||
if ! [[ "$linter_list" =~ ^[a-zA-Z0-9]+(,[a-zA-Z0-9]+)*$ ]]; then
|
||||
echo "::error::Invalid $name format: '$linter_list'. Expected comma-separated linter names (e.g., gosec,govet,staticcheck)"
|
||||
if [ -n "$_linter_list" ]; then
|
||||
if ! echo "$_linter_list" | grep -Eq '^[a-zA-Z0-9]+(,[a-zA-Z0-9]+)*$'; then
|
||||
echo "::error::Invalid $_name format: '$_linter_list'. Expected comma-separated linter names (e.g., gosec,govet,staticcheck)"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
@@ -211,7 +218,7 @@ runs:
|
||||
- name: Set up Cache
|
||||
id: cache
|
||||
if: inputs.cache == 'true'
|
||||
uses: ./common-cache
|
||||
uses: ivuorinen/actions/common-cache@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
type: 'go'
|
||||
paths: '~/.cache/golangci-lint,~/.cache/go-build'
|
||||
@@ -220,26 +227,29 @@ runs:
|
||||
restore-keys: '${{ runner.os }}-golangci-${{ inputs.golangci-lint-version }}-'
|
||||
|
||||
- name: Install golangci-lint
|
||||
shell: bash
|
||||
shell: sh
|
||||
env:
|
||||
MAX_RETRIES: ${{ inputs.max-retries }}
|
||||
GOLANGCI_LINT_VERSION: ${{ inputs.golangci-lint-version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
|
||||
# Function to install golangci-lint with retries
|
||||
install_golangci_lint() {
|
||||
local attempt=1
|
||||
local max_attempts="$MAX_RETRIES"
|
||||
local version="$GOLANGCI_LINT_VERSION"
|
||||
_attempt=1
|
||||
_max_attempts="$MAX_RETRIES"
|
||||
_version="$GOLANGCI_LINT_VERSION"
|
||||
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
echo "Installation attempt $attempt of $max_attempts"
|
||||
while [ $_attempt -le $_max_attempts ]; do
|
||||
echo "Installation attempt $_attempt of $_max_attempts"
|
||||
|
||||
# Add 'v' prefix if version is not 'latest' and doesn't already have it
|
||||
install_version="$version"
|
||||
if [[ "$version" != "latest" ]] && [[ "$version" != v* ]]; then
|
||||
install_version="v$version"
|
||||
install_version="$_version"
|
||||
if [ "$_version" != "latest" ]; then
|
||||
case "$_version" in
|
||||
v*) ;;
|
||||
*) install_version="v$_version" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \
|
||||
@@ -247,14 +257,14 @@ runs:
|
||||
return 0
|
||||
fi
|
||||
|
||||
attempt=$((attempt + 1))
|
||||
if [ $attempt -le $max_attempts ]; then
|
||||
_attempt=$((_attempt + 1))
|
||||
if [ $_attempt -le $_max_attempts ]; then
|
||||
echo "Installation failed, waiting 10 seconds before retry..."
|
||||
sleep 10
|
||||
fi
|
||||
done
|
||||
|
||||
echo "::error::Failed to install golangci-lint after $max_attempts attempts"
|
||||
echo "::error::Failed to install golangci-lint after $_max_attempts attempts"
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -262,13 +272,13 @@ runs:
|
||||
|
||||
- name: Prepare Configuration
|
||||
id: config
|
||||
shell: bash
|
||||
shell: sh
|
||||
env:
|
||||
WORKING_DIRECTORY: ${{ inputs.working-directory }}
|
||||
CONFIG_FILE: ${{ inputs.config-file }}
|
||||
TIMEOUT: ${{ inputs.timeout }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
|
||||
cd "$WORKING_DIRECTORY"
|
||||
|
||||
@@ -314,7 +324,7 @@ runs:
|
||||
|
||||
- name: Run golangci-lint
|
||||
id: lint
|
||||
shell: bash
|
||||
shell: sh
|
||||
env:
|
||||
WORKING_DIRECTORY: ${{ inputs.working-directory }}
|
||||
DISABLE_ALL: ${{ inputs.disable-all }}
|
||||
@@ -327,7 +337,7 @@ runs:
|
||||
REPORT_FORMAT: ${{ inputs.report-format }}
|
||||
FAIL_ON_ERROR: ${{ inputs.fail-on-error }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
|
||||
cd "$WORKING_DIRECTORY"
|
||||
|
||||
@@ -410,12 +420,12 @@ runs:
|
||||
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
shell: bash
|
||||
shell: sh
|
||||
env:
|
||||
WORKING_DIRECTORY: ${{ inputs.working-directory }}
|
||||
CACHE: ${{ inputs.cache }}
|
||||
run: |-
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
|
||||
cd "$WORKING_DIRECTORY"
|
||||
|
||||
|
||||
@@ -30,14 +30,14 @@ runs:
|
||||
steps:
|
||||
- name: Validate Inputs
|
||||
id: validate
|
||||
shell: bash
|
||||
shell: sh
|
||||
env:
|
||||
DEFAULT_VERSION: ${{ inputs.default-version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
|
||||
# Validate default-version format
|
||||
if ! [[ "$DEFAULT_VERSION" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
|
||||
if ! echo "$DEFAULT_VERSION" | grep -Eq '^[0-9]+\.[0-9]+(\.[0-9]+)?$'; then
|
||||
echo "::error::Invalid default-version format: '$DEFAULT_VERSION'. Expected format: X.Y or X.Y.Z (e.g., 1.22, 1.21.5)"
|
||||
exit 1
|
||||
fi
|
||||
@@ -65,7 +65,7 @@ runs:
|
||||
|
||||
- name: Parse Go Version
|
||||
id: parse-version
|
||||
uses: ./version-file-parser
|
||||
uses: ivuorinen/actions/version-file-parser@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
language: 'go'
|
||||
tool-versions-key: 'golang'
|
||||
|
||||
@@ -176,7 +176,7 @@ runs:
|
||||
|
||||
- name: Parse Node.js Version
|
||||
id: version
|
||||
uses: ./version-file-parser
|
||||
uses: ivuorinen/actions/version-file-parser@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
language: 'node'
|
||||
tool-versions-key: 'nodejs'
|
||||
@@ -299,7 +299,7 @@ runs:
|
||||
- name: Cache Dependencies
|
||||
if: inputs.cache == 'true'
|
||||
id: deps-cache
|
||||
uses: ./common-cache
|
||||
uses: ivuorinen/actions/common-cache@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
type: 'npm'
|
||||
paths: '~/.npm,~/.yarn/cache,~/.pnpm-store,~/.bun/install/cache,node_modules'
|
||||
@@ -359,7 +359,7 @@ runs:
|
||||
|
||||
- name: Install Dependencies
|
||||
if: inputs.install == 'true' && steps.deps-cache.outputs.cache-hit != 'true'
|
||||
uses: ./common-retry
|
||||
uses: ivuorinen/actions/common-retry@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
command: |
|
||||
package_manager="$PACKAGE_MANAGER"
|
||||
|
||||
@@ -12,6 +12,9 @@ branding:
|
||||
color: green
|
||||
|
||||
inputs:
|
||||
npm_token:
|
||||
description: 'NPM token.'
|
||||
required: true
|
||||
registry-url:
|
||||
description: 'Registry URL for publishing.'
|
||||
required: false
|
||||
@@ -24,10 +27,6 @@ inputs:
|
||||
description: 'The version to publish.'
|
||||
required: false
|
||||
default: ${{ github.event.release.tag_name }}
|
||||
npm_token:
|
||||
description: 'NPM token.'
|
||||
required: true
|
||||
default: ''
|
||||
token:
|
||||
description: 'GitHub token for authentication'
|
||||
required: false
|
||||
@@ -48,43 +47,44 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Mask Secrets
|
||||
shell: bash
|
||||
shell: sh
|
||||
env:
|
||||
NPM_TOKEN: ${{ inputs.npm_token }}
|
||||
run: |
|
||||
set -eu
|
||||
echo "::add-mask::$NPM_TOKEN"
|
||||
|
||||
- name: Validate Inputs
|
||||
id: validate
|
||||
shell: bash
|
||||
shell: sh
|
||||
env:
|
||||
REGISTRY_URL: ${{ inputs.registry-url }}
|
||||
PACKAGE_SCOPE: ${{ inputs.scope }}
|
||||
PACKAGE_VERSION: ${{ inputs.package-version }}
|
||||
NPM_TOKEN: ${{ inputs.npm_token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
|
||||
# Validate registry URL format
|
||||
if ! [[ "$REGISTRY_URL" =~ ^https?://[a-zA-Z0-9.-]+(/.*)?/?$ ]]; then
|
||||
if ! echo "$REGISTRY_URL" | grep -Eq '^https?://[a-zA-Z0-9.-]+(/.*)?/?$'; then
|
||||
echo "::error::Invalid registry URL format: '$REGISTRY_URL'. Expected http:// or https:// URL (e.g., 'https://registry.npmjs.org/')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate package version format (semver)
|
||||
if ! [[ "$PACKAGE_VERSION" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$ ]]; then
|
||||
if ! echo "$PACKAGE_VERSION" | grep -Eq '^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$'; then
|
||||
echo "::error::Invalid package version format: '$PACKAGE_VERSION'. Expected semantic version (e.g., '1.2.3', 'v1.2.3-alpha', '1.2.3+build')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate scope format (if provided)
|
||||
if [[ -n "$PACKAGE_SCOPE" ]] && ! [[ "$PACKAGE_SCOPE" =~ ^@[a-z0-9-~][a-z0-9-._~]*$ ]]; then
|
||||
if [ -n "$PACKAGE_SCOPE" ] && ! echo "$PACKAGE_SCOPE" | grep -Eq '^@[a-z0-9-~][a-z0-9-._~]*$'; then
|
||||
echo "::error::Invalid NPM scope format: '$PACKAGE_SCOPE'. Expected format: @scope-name (e.g., '@myorg', '@my-org')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate NPM token is provided
|
||||
if [[ -z "$NPM_TOKEN" ]]; then
|
||||
if [ -z "$NPM_TOKEN" ]; then
|
||||
echo "::error::NPM token is required for publishing"
|
||||
exit 1
|
||||
fi
|
||||
@@ -101,29 +101,29 @@ runs:
|
||||
token: ${{ inputs.token || github.token }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./node-setup
|
||||
uses: ivuorinen/actions/node-setup@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
|
||||
- name: Authenticate NPM
|
||||
shell: bash
|
||||
shell: sh
|
||||
env:
|
||||
REGISTRY_URL: ${{ inputs.registry-url }}
|
||||
NPM_TOKEN: ${{ inputs.npm_token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
|
||||
registry_host="$(echo "$REGISTRY_URL" | sed -E 's#^https?://##; s#/$##')"
|
||||
echo "//${registry_host}/:_authToken=$NPM_TOKEN" > ~/.npmrc
|
||||
echo "always-auth=true" >> ~/.npmrc
|
||||
|
||||
- name: Publish Package
|
||||
shell: bash
|
||||
shell: sh
|
||||
env:
|
||||
REGISTRY_URL: ${{ inputs.registry-url }}
|
||||
PACKAGE_SCOPE: ${{ inputs.scope }}
|
||||
PACKAGE_VERSION: ${{ inputs.package-version }}
|
||||
NPM_TOKEN: ${{ inputs.npm_token }}
|
||||
run: |-
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
|
||||
pkg_version=$(node -p "require('./package.json').version")
|
||||
input_version="$PACKAGE_VERSION"
|
||||
|
||||
@@ -79,7 +79,7 @@ runs:
|
||||
|
||||
- name: Validate Inputs
|
||||
id: validate
|
||||
uses: ./validate-inputs
|
||||
uses: ivuorinen/actions/validate-inputs@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
action: php-composer
|
||||
|
||||
@@ -176,10 +176,10 @@ runs:
|
||||
|
||||
- name: Cache Composer packages
|
||||
id: composer-cache
|
||||
uses: ./common-cache
|
||||
uses: ivuorinen/actions/common-cache@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
type: 'composer'
|
||||
paths: 'vendor,~/.composer/cache${{ inputs.cache-directories != "" && format(",{0}", inputs.cache-directories) || "" }}'
|
||||
paths: vendor,~/.composer/cache${{ inputs.cache-directories != "" && format(",{0}", inputs.cache-directories) || "" }}
|
||||
key-prefix: 'php-${{ inputs.php }}-composer-${{ inputs.composer-version }}'
|
||||
key-files: 'composer.lock,composer.json'
|
||||
restore-keys: |
|
||||
@@ -196,9 +196,9 @@ runs:
|
||||
composer clear-cache
|
||||
|
||||
- name: Install Dependencies
|
||||
uses: ./common-retry
|
||||
uses: ivuorinen/actions/common-retry@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
command: 'composer install ${{ inputs.args }}'
|
||||
command: composer install ${{ inputs.args }}
|
||||
max-retries: ${{ inputs.max-retries }}
|
||||
retry-delay: '30'
|
||||
description: 'Installing PHP dependencies via Composer'
|
||||
|
||||
@@ -60,7 +60,7 @@ runs:
|
||||
|
||||
- name: Detect PHP Version
|
||||
id: php-version
|
||||
uses: ./php-version-detect
|
||||
uses: ivuorinen/actions/php-version-detect@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
default-version: ${{ inputs.php-version }}
|
||||
|
||||
|
||||
@@ -86,14 +86,14 @@ runs:
|
||||
token: ${{ inputs.token || github.token }}
|
||||
|
||||
- name: Set Git Config
|
||||
uses: ./set-git-config
|
||||
uses: ivuorinen/actions/set-git-config@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
token: ${{ inputs.token != '' && inputs.token || github.token }}
|
||||
username: ${{ inputs.username }}
|
||||
email: ${{ inputs.email }}
|
||||
|
||||
- name: Composer Install
|
||||
uses: ./php-composer
|
||||
uses: ivuorinen/actions/php-composer@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
|
||||
- name: Run PHPUnit Tests
|
||||
id: test
|
||||
|
||||
@@ -67,7 +67,7 @@ runs:
|
||||
|
||||
- name: Parse PHP Version
|
||||
id: parse-version
|
||||
uses: ./version-file-parser
|
||||
uses: ivuorinen/actions/version-file-parser@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
language: 'php'
|
||||
tool-versions-key: 'php'
|
||||
|
||||
@@ -40,7 +40,7 @@ runs:
|
||||
steps:
|
||||
- name: Validate Inputs
|
||||
id: validate
|
||||
uses: ./validate-inputs
|
||||
uses: ivuorinen/actions/validate-inputs@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
action: pr-lint
|
||||
token: ${{ inputs.token }}
|
||||
@@ -64,7 +64,7 @@ runs:
|
||||
# ╰──────────────────────────────────────────────────────────╯
|
||||
- name: Setup Git Config
|
||||
id: git-config
|
||||
uses: ./set-git-config
|
||||
uses: ivuorinen/actions/set-git-config@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
token: ${{ inputs.token }}
|
||||
username: ${{ inputs.username }}
|
||||
@@ -87,7 +87,7 @@ runs:
|
||||
|
||||
- name: Setup Node.js environment
|
||||
if: steps.detect-node.outputs.found == 'true'
|
||||
uses: ./node-setup
|
||||
uses: ivuorinen/actions/node-setup@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
install: true
|
||||
cache: true
|
||||
@@ -106,7 +106,7 @@ runs:
|
||||
- name: Detect PHP Version
|
||||
if: steps.detect-php.outputs.found == 'true'
|
||||
id: php-version
|
||||
uses: ./php-version-detect
|
||||
uses: ivuorinen/actions/php-version-detect@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
|
||||
- name: Setup PHP
|
||||
if: steps.detect-php.outputs.found == 'true'
|
||||
@@ -150,7 +150,7 @@ runs:
|
||||
- name: Detect Python Version
|
||||
if: steps.detect-python.outputs.found == 'true'
|
||||
id: python-version
|
||||
uses: ./python-version-detect
|
||||
uses: ivuorinen/actions/python-version-detect@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
|
||||
- name: Setup Python
|
||||
if: steps.detect-python.outputs.found == 'true'
|
||||
@@ -181,7 +181,7 @@ runs:
|
||||
- name: Detect Go Version
|
||||
if: steps.detect-go.outputs.found == 'true'
|
||||
id: go-version
|
||||
uses: ./go-version-detect
|
||||
uses: ivuorinen/actions/go-version-detect@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
|
||||
- name: Setup Go
|
||||
if: steps.detect-go.outputs.found == 'true'
|
||||
|
||||
@@ -49,7 +49,7 @@ runs:
|
||||
|
||||
- name: Validate Inputs
|
||||
id: validate
|
||||
uses: ./validate-inputs
|
||||
uses: ivuorinen/actions/validate-inputs@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
action-type: 'pre-commit'
|
||||
token: ${{ inputs.token }}
|
||||
@@ -58,7 +58,7 @@ runs:
|
||||
email: ${{ inputs.commit_email }}
|
||||
username: ${{ inputs.commit_user }}
|
||||
- name: Set Git Config
|
||||
uses: ./set-git-config
|
||||
uses: ivuorinen/actions/set-git-config@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
token: ${{ inputs.token }}
|
||||
username: ${{ inputs.commit_user }}
|
||||
|
||||
@@ -202,11 +202,11 @@ runs:
|
||||
|
||||
- name: Setup Node.js
|
||||
id: node-setup
|
||||
uses: ./node-setup
|
||||
uses: ivuorinen/actions/node-setup@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
|
||||
- name: Set up Cache
|
||||
id: cache
|
||||
uses: ./common-cache
|
||||
uses: ivuorinen/actions/common-cache@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
if: inputs.cache == 'true'
|
||||
with:
|
||||
type: 'npm'
|
||||
|
||||
@@ -91,7 +91,7 @@ runs:
|
||||
token: ${{ inputs.token }}
|
||||
|
||||
- name: Set Git Config
|
||||
uses: ./set-git-config
|
||||
uses: ivuorinen/actions/set-git-config@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
token: ${{ inputs.token }}
|
||||
username: ${{ inputs.username }}
|
||||
@@ -99,11 +99,11 @@ runs:
|
||||
|
||||
- name: Node Setup
|
||||
id: node-setup
|
||||
uses: ./node-setup
|
||||
uses: ivuorinen/actions/node-setup@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
|
||||
- name: Cache npm Dependencies
|
||||
id: cache-npm
|
||||
uses: ./common-cache
|
||||
uses: ivuorinen/actions/common-cache@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
type: 'npm'
|
||||
paths: 'node_modules'
|
||||
|
||||
@@ -155,7 +155,7 @@ runs:
|
||||
|
||||
- name: Detect Python Version
|
||||
id: python-version
|
||||
uses: ./python-version-detect
|
||||
uses: ivuorinen/actions/python-version-detect@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
default-version: ${{ inputs.python-version }}
|
||||
|
||||
@@ -189,7 +189,7 @@ runs:
|
||||
- name: Cache Python Dependencies
|
||||
if: steps.check-files.outputs.result == 'found'
|
||||
id: cache-pip
|
||||
uses: ./common-cache
|
||||
uses: ivuorinen/actions/common-cache@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
type: 'pip'
|
||||
paths: '~/.cache/pip'
|
||||
@@ -325,7 +325,7 @@ runs:
|
||||
|
||||
- name: Set Git Config for Fixes
|
||||
if: ${{ fromJSON(steps.fix.outputs.fixed_count) > 0 }}
|
||||
uses: ./set-git-config
|
||||
uses: ivuorinen/actions/set-git-config@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
token: ${{ inputs.token }}
|
||||
username: ${{ inputs.username }}
|
||||
|
||||
@@ -33,17 +33,21 @@ runs:
|
||||
steps:
|
||||
- name: Validate Inputs
|
||||
id: validate
|
||||
shell: bash
|
||||
shell: sh
|
||||
env:
|
||||
DEFAULT_VERSION: ${{ inputs.default-version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
|
||||
# Validate default-version format
|
||||
if ! [[ "$DEFAULT_VERSION" =~ ^[0-9]+(\.[0-9]+(\.[0-9]+)?)?$ ]]; then
|
||||
echo "::error::Invalid default-version format: '$DEFAULT_VERSION'. Expected format: X.Y or X.Y.Z (e.g., 3.12, 3.11.5)"
|
||||
exit 1
|
||||
fi
|
||||
case "$DEFAULT_VERSION" in
|
||||
[0-9]*.[0-9]* | [0-9]*.[0-9]*.[0-9]*)
|
||||
;;
|
||||
*)
|
||||
echo "::error::Invalid default-version format: '$DEFAULT_VERSION'. Expected format: X.Y or X.Y.Z (e.g., 3.12, 3.11.5)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Check for reasonable version range (prevent malicious inputs)
|
||||
major_version=$(echo "$DEFAULT_VERSION" | cut -d'.' -f1)
|
||||
@@ -68,7 +72,7 @@ runs:
|
||||
|
||||
- name: Parse Python Version
|
||||
id: parse-version
|
||||
uses: ./version-file-parser
|
||||
uses: ivuorinen/actions/version-file-parser@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
language: 'python'
|
||||
tool-versions-key: 'python'
|
||||
|
||||
@@ -65,7 +65,7 @@ runs:
|
||||
|
||||
- name: Parse Python Version
|
||||
id: parse-version
|
||||
uses: ./version-file-parser
|
||||
uses: ivuorinen/actions/version-file-parser@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
language: 'python'
|
||||
tool-versions-key: 'python'
|
||||
|
||||
@@ -43,7 +43,7 @@ runs:
|
||||
|
||||
- name: Validate Inputs
|
||||
id: validate
|
||||
uses: ./validate-inputs
|
||||
uses: ivuorinen/actions/validate-inputs@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
action: 'stale'
|
||||
token: ${{ inputs.token || github.token }}
|
||||
|
||||
@@ -78,7 +78,7 @@ runs:
|
||||
|
||||
- name: Validate Inputs
|
||||
id: validate
|
||||
uses: ./validate-inputs
|
||||
uses: ivuorinen/actions/validate-inputs@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
action-type: 'terraform-lint-fix'
|
||||
token: ${{ inputs.token || github.token }}
|
||||
@@ -270,7 +270,7 @@ runs:
|
||||
|
||||
- name: Set Git Config for Fixes
|
||||
if: ${{ fromJSON(steps.fix.outputs.fixed_count) > 0 }}
|
||||
uses: ./set-git-config
|
||||
uses: ivuorinen/actions/set-git-config@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
with:
|
||||
token: ${{ inputs.token || github.token }}
|
||||
username: ${{ inputs.username }}
|
||||
|
||||
Reference in New Issue
Block a user