From 9bf8feb49295b1de0e682832caa5d5d49bc7c36b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 8 Oct 2026 09:27:41 +0000 Subject: [PATCH] fix: auto-detect .structkit.yaml with legacy .struct.yaml fallback structkit 3.3.0 renamed the default project file. The action still defaulted struct_file to .struct.yaml and passed it to validate, so repos that only have .structkit.yaml failed. When struct_file is empty, prefer .structkit.yaml, then .struct.yaml, or fail with a clear error. Explicit paths are unchanged. Co-authored-by: Kenneth Belitzky --- .github/workflows/test.yml | 111 ++++++++++++++++-- README.md | 18 +-- action.yml | 22 +++- examples/README.md | 2 +- examples/custom-structures.yml | 3 +- examples/drift-detection.yml | 2 +- examples/generate-and-commit.yml | 8 +- examples/scheduled-check.yml | 1 + examples/validate-pr.yml | 3 +- scripts/resolve-struct-file.sh | 28 +++++ .../fixtures/legacy-struct-yaml/.struct.yaml | 3 + tests/fixtures/structkit-yaml/.structkit.yaml | 3 + tests/resolve-struct-file.test.sh | 64 ++++++++++ 13 files changed, 240 insertions(+), 28 deletions(-) create mode 100755 scripts/resolve-struct-file.sh create mode 100644 tests/fixtures/legacy-struct-yaml/.struct.yaml create mode 100644 tests/fixtures/structkit-yaml/.structkit.yaml create mode 100755 tests/resolve-struct-file.test.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3a2a8f8..3092348 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: - name: Create test structure file run: | - cat > .struct.yaml << 'EOF' + cat > .structkit.yaml << 'EOF' files: - test-file.txt: content: "Hello from StructKit!" @@ -26,7 +26,7 @@ jobs: uses: ./ with: command: validate - struct_file: .struct.yaml + struct_file: .structkit.yaml test-generate-dry-run: name: Test Generate Dry Run @@ -37,7 +37,7 @@ jobs: - name: Create test structure file run: | - cat > .struct.yaml << 'EOF' + cat > .structkit.yaml << 'EOF' files: - test-output/sample.txt: content: "Generated by StructKit Action!" @@ -48,7 +48,7 @@ jobs: uses: ./ with: command: generate - struct_file: .struct.yaml + struct_file: .structkit.yaml dry_run: true - name: Check outputs @@ -66,7 +66,7 @@ jobs: - name: Create test structure file run: | - cat > .struct.yaml << 'EOF' + cat > .structkit.yaml << 'EOF' files: - generated/test.txt: content: "This file was generated!" @@ -78,7 +78,7 @@ jobs: uses: ./ with: command: generate - struct_file: .struct.yaml + struct_file: .structkit.yaml output_dir: . - name: Verify generated files @@ -96,7 +96,7 @@ jobs: - name: Create test structure file run: | - cat > .struct.yaml << 'EOF' + cat > .structkit.yaml << 'EOF' files: - version-test.txt: content: "Testing version" @@ -106,5 +106,100 @@ jobs: uses: ./ with: command: validate - struct_file: .struct.yaml + struct_file: .structkit.yaml structkit_version: "https://github.com/httpdss/structkit.git@main" + + test-validate-structkit-yaml-fixture: + name: Test Validate .structkit.yaml Fixture + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Run validate with .structkit.yaml fixture + uses: ./ + with: + command: validate + struct_file: tests/fixtures/structkit-yaml/.structkit.yaml + + test-validate-legacy-struct-yaml-fixture: + name: Test Validate Legacy .struct.yaml Fixture + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Run validate with legacy .struct.yaml fixture + uses: ./ + with: + command: validate + struct_file: tests/fixtures/legacy-struct-yaml/.struct.yaml + + test-auto-detect-structkit-yaml: + name: Test Auto-detect .structkit.yaml + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Place .structkit.yaml at repo root + run: cp tests/fixtures/structkit-yaml/.structkit.yaml .structkit.yaml + + - name: Run validate without struct_file + uses: ./ + with: + command: validate + + test-auto-detect-legacy-struct-yaml: + name: Test Auto-detect Legacy .struct.yaml + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Place legacy .struct.yaml at repo root + run: cp tests/fixtures/legacy-struct-yaml/.struct.yaml .struct.yaml + + - name: Run validate without struct_file + uses: ./ + with: + command: validate + + test-auto-detect-prefers-structkit-yaml: + name: Test Auto-detect Prefers .structkit.yaml + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Place both default files at repo root + run: | + cp tests/fixtures/structkit-yaml/.structkit.yaml .structkit.yaml + cp tests/fixtures/legacy-struct-yaml/.struct.yaml .struct.yaml + + - name: Run validate without struct_file + uses: ./ + with: + command: validate + + test-auto-detect-missing: + name: Test Auto-detect Missing Config + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Run validate without config + id: validate + continue-on-error: true + uses: ./ + with: + command: validate + + - name: Expect failure + run: | + if [[ "${{ steps.validate.outcome }}" != "failure" ]]; then + echo "Expected auto-detect to fail when neither .structkit.yaml nor .struct.yaml exists" + exit 1 + fi + echo "Missing config failed as expected" diff --git a/README.md b/README.md index 437a57e..33871e7 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ jobs: - uses: httpdss/structkit-action@v1 with: command: generate - struct_file: .struct.yaml + struct_file: .structkit.yaml output_dir: . - name: Commit changes @@ -87,11 +87,11 @@ jobs: | Input | Description | Required | Default | |-------|-------------|----------|---------| | `command` | Command to run: `validate` or `generate` | No | `validate` | -| `struct_file` | Path to the StructKit configuration file | No | `.struct.yaml` | +| `struct_file` | Path to the StructKit configuration file. Leave empty to auto-detect `.structkit.yaml`, then legacy `.struct.yaml` | No | auto-detect | | `output_dir` | Output directory for generated files | No | `.` | | `dry_run` | Run in dry-run mode (preview changes without writing) | No | `false` | | `diff` | Show diff of changes (use with dry_run for drift detection) | No | `false` | -| `no_hooks` | Disable hooks during execution (check if your StructKit version supports this flag) | No | `false` | +| `no_hooks` | Disable hooks during execution (requires StructKit >= 3.3.0) | No | `false` | | `non_interactive` | Run in non-interactive mode (check if your StructKit version supports this flag) | No | `false` | | `structkit_version` | StructKit version to install (version number, `latest`, or git URL) | No | `latest` | | `structures_path` | Path to custom structures directory | No | `''` | @@ -191,9 +191,9 @@ jobs: strategy: matrix: struct_file: - - .struct.yaml - - config/api.struct.yaml - - config/frontend.struct.yaml + - .structkit.yaml + - config/api.structkit.yaml + - config/frontend.structkit.yaml steps: - uses: actions/checkout@v7 - uses: httpdss/structkit-action@v1 @@ -220,9 +220,13 @@ If you need automatic PR creation, use the [reusable workflow](https://github.co - Python 3.x (automatically installed by the action) - GitHub Actions runner with bash support +## Configuration file + +The default project file is `.structkit.yaml`. If `struct_file` is omitted, the action uses `.structkit.yaml` when it exists, otherwise it falls back to legacy `.struct.yaml`. Set `struct_file` to an explicit path to skip auto-detection. + ## Compatibility Note -This action is designed to work with different versions of StructKit. Some command-line flags (`--no-hooks`, `--non-interactive`, `--diff`, `--dry-run`) may not be available in all versions. The action defaults to not using these flags unless explicitly enabled. Check your [StructKit version's documentation](https://github.com/httpdss/structkit) to confirm which flags are supported. +This action is designed to work with different versions of StructKit. Some command-line flags (`--no-hooks`, `--non-interactive`, `--diff`, `--dry-run`) may not be available in all versions. `--no-hooks` requires StructKit >= 3.3.0. The action defaults to not using these flags unless explicitly enabled. Check your [StructKit version's documentation](https://github.com/httpdss/structkit) to confirm which flags are supported. ## How It Works diff --git a/action.yml b/action.yml index c678c8f..16bea89 100644 --- a/action.yml +++ b/action.yml @@ -11,9 +11,9 @@ inputs: required: false default: 'validate' struct_file: - description: 'Path to the StructKit configuration file' + description: 'Path to the StructKit configuration file. Leave empty to auto-detect: .structkit.yaml if present, otherwise legacy .struct.yaml. Fails if neither exists. Explicit paths are used as-is.' required: false - default: '.struct.yaml' + default: '' output_dir: description: 'Output directory for generated files' required: false @@ -27,7 +27,7 @@ inputs: required: false default: 'false' no_hooks: - description: 'Disable hooks during execution (check if your StructKit version supports this flag)' + description: 'Disable hooks during execution (requires StructKit >= 3.3.0)' required: false default: 'false' non_interactive: @@ -84,6 +84,16 @@ outputs: runs: using: 'composite' steps: + - name: Resolve struct file + id: resolve-struct-file + shell: bash + env: + INPUT_STRUCT_FILE: ${{ inputs.struct_file }} + run: | + STRUCT_FILE="$(bash "${GITHUB_ACTION_PATH}/scripts/resolve-struct-file.sh" "${INPUT_STRUCT_FILE}")" + echo "Using struct file: ${STRUCT_FILE}" + echo "struct_file=${STRUCT_FILE}" >> "${GITHUB_OUTPUT}" + - name: Set up Python uses: actions/setup-python@v5 with: @@ -117,6 +127,8 @@ runs: - name: Run StructKit id: run-structkit shell: bash + env: + STRUCT_FILE: ${{ steps.resolve-struct-file.outputs.struct_file }} run: | set +e @@ -158,9 +170,9 @@ runs: # Add positional arguments for generate command if [[ "$COMMAND" == "generate" ]]; then - ARGS+=("${{ inputs.struct_file }}" "${{ inputs.output_dir }}") + ARGS+=("${STRUCT_FILE}" "${{ inputs.output_dir }}") elif [[ "$COMMAND" == "validate" ]]; then - ARGS+=("${{ inputs.struct_file }}") + ARGS+=("${STRUCT_FILE}") fi # Run the command diff --git a/examples/README.md b/examples/README.md index e6f6422..5c72f53 100644 --- a/examples/README.md +++ b/examples/README.md @@ -21,6 +21,6 @@ cp examples/validate-pr.yml .github/workflows/ ## Customization Tips - Replace version pins (`@v1`) with specific commit SHAs for production use -- Adjust `struct_file` paths to match your repository structure +- Adjust `struct_file` paths to match your repository structure (default is `.structkit.yaml`; legacy `.struct.yaml` is still auto-detected if you omit the input) - Configure branch names and PR settings according to your workflow - Add environment-specific secrets if using private structure repositories diff --git a/examples/custom-structures.yml b/examples/custom-structures.yml index 6098340..eb7f303 100644 --- a/examples/custom-structures.yml +++ b/examples/custom-structures.yml @@ -5,6 +5,7 @@ on: push: branches: [main] paths: + - '.structkit.yaml' - '.struct.yaml' jobs: @@ -19,7 +20,7 @@ jobs: uses: httpdss/structkit-action@v1 with: command: generate - struct_file: .struct.yaml + struct_file: .structkit.yaml structures_repository: myorg/my-custom-structures structures_repository_path: templates structures_repository_ref: v1.0.0 diff --git a/examples/drift-detection.yml b/examples/drift-detection.yml index a6a5490..7e67a8a 100644 --- a/examples/drift-detection.yml +++ b/examples/drift-detection.yml @@ -19,7 +19,7 @@ jobs: uses: httpdss/structkit-action@v1 with: command: generate - struct_file: .struct.yaml + struct_file: .structkit.yaml dry_run: true diff: true fail_on_diff: true diff --git a/examples/generate-and-commit.yml b/examples/generate-and-commit.yml index 2d2b2b7..530c43c 100644 --- a/examples/generate-and-commit.yml +++ b/examples/generate-and-commit.yml @@ -6,7 +6,7 @@ on: struct_file: description: 'Structure file to use' required: false - default: '.struct.yaml' + default: '.structkit.yaml' jobs: generate: @@ -23,21 +23,21 @@ jobs: uses: httpdss/structkit-action@v1 with: command: generate - struct_file: ${{ github.event.inputs.struct_file || '.struct.yaml' }} + struct_file: ${{ github.event.inputs.struct_file || '.structkit.yaml' }} output_dir: . - name: Create Pull Request uses: peter-evans/create-pull-request@v7 with: token: ${{ secrets.GITHUB_TOKEN }} - commit-message: "chore: regenerate structure from ${{ github.event.inputs.struct_file || '.struct.yaml' }}" + commit-message: "chore: regenerate structure from ${{ github.event.inputs.struct_file || '.structkit.yaml' }}" title: "🤖 Regenerate Project Structure" body: | ## Automated Structure Regeneration This PR was automatically generated by the StructKit Action. - **Structure File:** `${{ github.event.inputs.struct_file || '.struct.yaml' }}` + **Structure File:** `${{ github.event.inputs.struct_file || '.structkit.yaml' }}` **Workflow Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} Please review the changes before merging. diff --git a/examples/scheduled-check.yml b/examples/scheduled-check.yml index c38477b..8f5fac9 100644 --- a/examples/scheduled-check.yml +++ b/examples/scheduled-check.yml @@ -20,6 +20,7 @@ jobs: uses: httpdss/structkit-action@v1 with: command: generate + struct_file: .structkit.yaml dry_run: true diff: true fail_on_diff: true diff --git a/examples/validate-pr.yml b/examples/validate-pr.yml index 3c6ba10..adda388 100644 --- a/examples/validate-pr.yml +++ b/examples/validate-pr.yml @@ -4,6 +4,7 @@ on: pull_request: branches: [main, develop] paths: + - '**.structkit.yaml' - '**.struct.yaml' - '.github/workflows/validate-pr.yml' @@ -19,7 +20,7 @@ jobs: uses: httpdss/structkit-action@v1 with: command: validate - struct_file: .struct.yaml + struct_file: .structkit.yaml - name: Comment on PR if: failure() diff --git a/scripts/resolve-struct-file.sh b/scripts/resolve-struct-file.sh new file mode 100755 index 0000000..0172ecd --- /dev/null +++ b/scripts/resolve-struct-file.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Resolve the StructKit configuration file. +# Usage: resolve-struct-file.sh [explicit-path] +# Prints the resolved path to stdout. When no explicit path is given, +# prefers .structkit.yaml, then legacy .struct.yaml. +set -euo pipefail + +explicit="${1-}" + +if [[ -n "${explicit}" ]]; then + printf '%s\n' "${explicit}" + exit 0 +fi + +if [[ -f ".structkit.yaml" ]]; then + echo "Auto-detected struct file: .structkit.yaml" >&2 + printf '%s\n' ".structkit.yaml" + exit 0 +fi + +if [[ -f ".struct.yaml" ]]; then + echo "Auto-detected struct file: .struct.yaml (legacy)" >&2 + printf '%s\n' ".struct.yaml" + exit 0 +fi + +echo "::error::No StructKit configuration file found. Looked for .structkit.yaml and .struct.yaml in the workspace root. Set the struct_file input to an explicit path, or add one of those files." >&2 +exit 1 diff --git a/tests/fixtures/legacy-struct-yaml/.struct.yaml b/tests/fixtures/legacy-struct-yaml/.struct.yaml new file mode 100644 index 0000000..2de272f --- /dev/null +++ b/tests/fixtures/legacy-struct-yaml/.struct.yaml @@ -0,0 +1,3 @@ +files: + - fixture-legacy.txt: + content: "Hello from legacy .struct.yaml fixture!" diff --git a/tests/fixtures/structkit-yaml/.structkit.yaml b/tests/fixtures/structkit-yaml/.structkit.yaml new file mode 100644 index 0000000..f1f6f06 --- /dev/null +++ b/tests/fixtures/structkit-yaml/.structkit.yaml @@ -0,0 +1,3 @@ +files: + - fixture-structkit.txt: + content: "Hello from .structkit.yaml fixture!" diff --git a/tests/resolve-struct-file.test.sh b/tests/resolve-struct-file.test.sh new file mode 100755 index 0000000..fd0631f --- /dev/null +++ b/tests/resolve-struct-file.test.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# Local tests for scripts/resolve-struct-file.sh +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +SCRIPT="${ROOT}/scripts/resolve-struct-file.sh" +FIXTURE_STRUCTKIT="${ROOT}/tests/fixtures/structkit-yaml/.structkit.yaml" +FIXTURE_LEGACY="${ROOT}/tests/fixtures/legacy-struct-yaml/.struct.yaml" + +tmpdir="$(mktemp -d)" +trap 'rm -rf "${tmpdir}"' EXIT +cd "${tmpdir}" + +fail() { + echo "FAIL: $*" >&2 + exit 1 +} + +assert_eq() { + local expected="$1" + local actual="$2" + local label="$3" + if [[ "${expected}" != "${actual}" ]]; then + fail "${label}: expected '${expected}', got '${actual}'" + fi + echo "PASS: ${label}" +} + +# Explicit path is returned unchanged, even if the file does not exist. +assert_eq "custom/path.structkit.yaml" \ + "$("${SCRIPT}" "custom/path.structkit.yaml")" \ + "explicit path is used as-is" + +assert_eq "${FIXTURE_STRUCTKIT}" \ + "$("${SCRIPT}" "${FIXTURE_STRUCTKIT}")" \ + "explicit fixture path is used as-is" + +# Neither default file exists. +if "${SCRIPT}" >/dev/null 2>"${tmpdir}/missing.err"; then + fail "expected failure when neither default file exists" +fi +if ! grep -q "No StructKit configuration file found" "${tmpdir}/missing.err"; then + fail "missing-file error should mention both default names" +fi +echo "PASS: missing both default files fails with a clear message" + +# Legacy only. +cp "${FIXTURE_LEGACY}" .struct.yaml +assert_eq ".struct.yaml" "$("${SCRIPT}")" "legacy .struct.yaml is detected" + +# Prefer .structkit.yaml when both exist. +cp "${FIXTURE_STRUCTKIT}" .structkit.yaml +assert_eq ".structkit.yaml" "$("${SCRIPT}")" "prefers .structkit.yaml when both exist" + +# Current name only. +rm -f .struct.yaml +assert_eq ".structkit.yaml" "$("${SCRIPT}")" ".structkit.yaml is detected" + +# Explicit value wins over auto-detect. +assert_eq ".struct.yaml" \ + "$("${SCRIPT}" ".struct.yaml")" \ + "explicit .struct.yaml is used even if .structkit.yaml exists" + +echo "All resolve-struct-file tests passed."