--- # yaml-language-server: $schema=https://www.schemastore.org/github-workflow.json name: CI on: push: branches: [main, master] pull_request: branches: [main, master] merge_group: 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 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@dfa6ed13c88fe79b56c4ffc79a42db2e8d2b15a5 # v4.2.0 id: cache-npm with: path: ~/.npm key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} 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 Tree-sitter CLI uses: actions/cache@dfa6ed13c88fe79b56c4ffc79a42db2e8d2b15a5 # v4.2.0 id: cache-tree-sitter with: path: ~/.npm/_npx key: ${{ runner.os }}-tree-sitter-cli-${{ hashFiles('package.json') }} - name: Install Tree-sitter CLI run: npm install -g tree-sitter-cli shell: bash - name: Cache Generated Grammar uses: actions/cache@dfa6ed13c88fe79b56c4ffc79a42db2e8d2b15a5 # v4.2.0 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@dfa6ed13c88fe79b56c4ffc79a42db2e8d2b15a5 # v4.2.0 id: cache-parser with: path: | build/ 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: | 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 tree-sitter parse --language=shellspec 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 steps: - name: Checkout Repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Node.js 24 uses: actions/setup-node@7c12f8017d5436eb855f1ed4399f037a36fbd9e8 # v5.2.1 with: node-version: 24 - name: Cache Node.js dependencies uses: actions/cache@dfa6ed13c88fe79b56c4ffc79a42db2e8d2b15a5 # v4.2.0 with: path: ~/.npm key: ${{ runner.os }}-node-24-${{ hashFiles('**/package-lock.json') }} 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 steps: - name: Checkout Repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Node.js 24 uses: actions/setup-node@7c12f8017d5436eb855f1ed4399f037a36fbd9e8 # v5.2.1 with: node-version: 24 - name: Cache Node.js dependencies uses: actions/cache@dfa6ed13c88fe79b56c4ffc79a42db2e8d2b15a5 # v4.2.0 with: path: ~/.npm key: ${{ runner.os }}-node-24-${{ hashFiles('**/package-lock.json') }} 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