--- # yaml-language-server: $schema=https://www.schemastore.org/github-workflow.json name: CI on: push: branches: [main, master] pull_request: branches: [main, master] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: read-all jobs: test: name: ๐Ÿงช Test Suite runs-on: ubuntu-latest timeout-minutes: 15 permissions: actions: read contents: read issues: write pull-requests: write strategy: matrix: node-version: [22, 24] fail-fast: false steps: - name: Checkout Repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Node.js ${{ matrix.node-version }} uses: actions/setup-node@7c12f8017d5436eb855f1ed4399f037a36fbd9e8 # v5.2.1 with: node-version: ${{ matrix.node-version }} - name: Cache Node.js dependencies uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 id: cache-npm with: path: | ~/.npm ~/.yarn ~/.cache/yarn ~/.pnpm-store ~/.cache/pnpm node_modules/.cache key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json', '**/yarn.lock', '**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-node-${{ matrix.node-version }}- ${{ runner.os }}-node- - name: Install Dependencies run: npm ci || { echo "โŒ npm install failed"; npm install; } shell: bash - name: Cache npx store uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 id: cache-npx with: path: | ~/.npm/_npx ~/.cache/yarn/global ~/.local/share/pnpm/global key: ${{ runner.os }}-npx-${{ hashFiles('package.json') }} - name: Cache Generated Grammar uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 id: cache-grammar with: path: | src/parser.c src/tree_sitter/ binding.gyp key: ${{ runner.os }}-grammar-${{ hashFiles('grammar.js', 'package.json') }} - name: Generate Grammar if: steps.cache-grammar.outputs.cache-hit != 'true' run: npm run generate shell: bash - name: Cache Built Parser uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 id: cache-parser with: path: | src/ node_modules/ key: ${{ runner.os }}-parser-${{ matrix.node-version }}-${{ hashFiles('src/parser.c', 'binding.gyp', 'package.json') }} - name: Build Parser if: steps.cache-parser.outputs.cache-hit != 'true' run: npm run build shell: bash - name: Test Parser with Sample Code run: | # Ensure parser is built npm run build cat << 'EOF' > test_sample.shellspec #!/usr/bin/env shellspec Describe 'Calculator' Include ./lib/calculator.sh Before 'setup_calculator' After 'cleanup_calculator' Context 'when adding numbers' It 'adds two positive numbers' When call add 2 3 The output should eq 5 End It 'handles zero' When call add 0 5 The output should eq 5 End End Context 'when input is invalid' Skip if "validation not implemented" ! command -v validate It 'handles empty input' When call add "" "" The status should be failure End End End It 'works without describe block' When call echo "test" The output should eq "test" End EOF npx tree-sitter parse test_sample.shellspec --quiet || { echo "โŒ Parser failed on sample ShellSpec code" exit 1 } echo "โœ… Parser successfully handled sample code" shell: bash lint: name: ๐Ÿงน Code Quality runs-on: ubuntu-latest timeout-minutes: 15 permissions: contents: read issues: write pull-requests: write steps: - name: Checkout Repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Node.js uses: actions/setup-node@7c12f8017d5436eb855f1ed4399f037a36fbd9e8 # v5.2.1 with: node-version: 24 - name: Cache Node.js dependencies uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: path: | ~/.npm ~/.yarn ~/.cache/yarn ~/.pnpm-store ~/.cache/pnpm node_modules/.cache key: ${{ runner.os }}-node-24-${{ hashFiles('**/package-lock.json', '**/yarn.lock', '**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-node-24- ${{ runner.os }}-node- - name: Install Dependencies run: npm ci || { echo "โŒ npm install failed"; npm install; } shell: bash - name: ๐Ÿงน Run Linter uses: ivuorinen/actions/pr-lint@22e6add79fabcca4bf5761452a51e4fa0207e155 # 25.9.8 coverage: name: ๐Ÿ“Š Test Coverage runs-on: ubuntu-latest timeout-minutes: 15 needs: test permissions: actions: read contents: read issues: write pull-requests: write steps: - name: Checkout Repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Node.js uses: actions/setup-node@7c12f8017d5436eb855f1ed4399f037a36fbd9e8 # v5.2.1 with: node-version: 24 - name: Cache Node.js dependencies uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: path: | ~/.npm ~/.yarn ~/.cache/yarn ~/.pnpm-store ~/.cache/pnpm node_modules/.cache key: ${{ runner.os }}-node-24-${{ hashFiles('**/package-lock.json', '**/yarn.lock', '**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-node-24- ${{ runner.os }}-node- - name: Install Dependencies run: npm ci || { echo "โŒ npm install failed"; npm install; } - name: Test Coverage Analysis id: coverage run: | echo "## Test Coverage Report" > coverage_report.md echo "" >> coverage_report.md # Run tests and capture output with exit code set +e # Don't exit on test failure TEST_OUTPUT=$(npm test 2>&1) TEST_EXIT=$? set -e # Re-enable exit on error TOTAL_TESTS=$(echo "$TEST_OUTPUT" | grep -cE "^\s+[0-9]+\." || echo "0") PASSING_TESTS=$(echo "$TEST_OUTPUT" | grep -cE "^\s+[0-9]+\. โœ“" || echo "0") FAILING_TESTS=$(echo "$TEST_OUTPUT" | grep -cE "^\s+[0-9]+\. โœ—" || echo "0") { echo "- **Total Tests:** $TOTAL_TESTS" echo "- **Passing:** $PASSING_TESTS โœ…" echo "- **Failing:** $FAILING_TESTS โŒ" } >> coverage_report.md if [ "$TOTAL_TESTS" -gt 0 ]; then COVERAGE_PERCENT=$(( (PASSING_TESTS * 100) / TOTAL_TESTS )) echo "- **Coverage:** $COVERAGE_PERCENT%" >> coverage_report.md else COVERAGE_PERCENT=0 fi { echo "" echo "### Test Files" } >> coverage_report.md echo "$TEST_OUTPUT" | grep -E "^[[:space:]]+[A-Za-z0-9._/\\-]+:" | sed 's/^/- /' >> coverage_report.md cat coverage_report.md # Set outputs { echo "total-tests=$TOTAL_TESTS" echo "passing-tests=$PASSING_TESTS" echo "coverage-percent=$COVERAGE_PERCENT" } >> "$GITHUB_OUTPUT" # Validate test coverage requirements if [ "${TOTAL_TESTS:-0}" -lt 55 ]; then echo "โŒ Expected at least 55 tests, found ${TOTAL_TESTS:-0}" exit 1 fi if [ "${FAILING_TESTS:-0}" -gt 0 ] || [ "$TEST_EXIT" -ne 0 ]; then echo "โŒ Found ${FAILING_TESTS:-0} failing tests or test runner failed (exit code: $TEST_EXIT)" exit 1 fi echo "โœ… Test coverage is acceptable: $PASSING_TESTS/$TOTAL_TESTS tests passing ($COVERAGE_PERCENT%)" shell: bash