> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Demo Custom Transformer Code Coverage catchError

> Demonstrates a custom transformer that converts Jenkins catchError wrapping a code coverage step into GitHub Actions steps preserving continue on error behavior and HTML artifact upload

In this lesson we build a custom transformer to convert a Jenkins `catchError` wrapper used in a Code Coverage stage into equivalent GitHub Actions steps. Jenkins's `catchError` lets a failing step continue the pipeline while setting a specific build or stage result. When migrating to GitHub Actions we want the workflow to run the same coverage command, continue on error so downstream jobs still run, and preserve HTML artifacts for later inspection.

This article walks through the original Jenkins stage, the parsed AST-like representation the importer prints in dry-run, the Ruby transformer implementation, the importer dry-run invocation and output, the generated GitHub Actions job snippet, and the run results.

## Jenkinsfile snippet (Code Coverage stage — original)

```groovy theme={null}
stage('Code Coverage') {
    agent {
        docker {
            image 'node:24'
            args '-u root:root'
        }
    }
    steps {
        catchError(buildResult: 'SUCCESS', message: 'Oops! it will be fixed in future releases', stageResult: 'UNSTABLE') {
            sh 'npm run coverage'
        }
        publishHTML([
            allowMissing: true,
            alwaysLinkToLastBuild: true,
            keepAll: true,
            reportDir: 'coverage/lcov-report',
            reportFiles: 'index.html',
            reportName: 'Code Coverage'
        ])
    }
}
```

## What the importer sees (AST-like JSON)

Run the importer in dry-run mode to print parsed identifiers and their arguments. In this case the importer detected a `catchError` node whose child is a `sh` invocation of the coverage command.

Sample parsed representation for `catchError` (converted to valid JSON for clarity):

```json theme={null}
{
  "name": "catchError",
  "arguments": [
    {
      "key": "buildResult",
      "value": { "isLiteral": true, "value": "SUCCESS" }
    },
    {
      "key": "message",
      "value": { "isLiteral": true, "value": "Oops! it will be fixed in future releases" }
    },
    {
      "key": "stageResult",
      "value": { "isLiteral": true, "value": "UNSTABLE" }
    }
  ],
  "children": [
    {
      "name": "sh",
      "arguments": [
        {
          "key": "script",
          "value": { "isLiteral": true, "value": "npm run coverage" }
        }
      ]
    }
  ]
}
```

## Approach — desired mapping to GitHub Actions

Goal: mirror Jenkins behavior so coverage failures do not block the pipeline, but HTML reports are still produced and uploaded.

Steps:

* Extract the inner coverage command from `item.dig("children", 0, "arguments", 0, "value", "value")`.
* Fall back to `npm run coverage` if the command is not found.
* Emit two GitHub Actions steps:
  1. Install dependencies: `npm install --no-audit`.
  2. Run coverage: set `continue-on-error: true` so the workflow continues even if the command exits non‑zero.
* Let the existing importer conversion handle `publishHTML` → `actions/upload-artifact@v4` (artifact upload of the coverage HTML).

## Transformer implementation (helper-transformer.rb)

The transformer below is added to the importer's custom transformers so that `catchError` nodes are converted into a lightweight sequence of GitHub Actions steps.

```ruby theme={null}
# Skip transforming certain nodes already handled elsewhere
transform "retry" do |item|
  next nil if item.dig("arguments", 0, "value", "value") == 2
end

transform "junit" do |item|
  # Skip transformation if junit result file is already handled (example)
  next nil if item.dig("arguments", 0, "value", "value") == "test-results.xml"
end

transform "catchError" do |item|
  # If this catchError wraps "npm test" (inside a sh child), skip
  next nil if item.dig("children", 0, "arguments", 0, "value", "value") == "npm test"

  # Extract coverage command from children -> sh -> arguments -> script
  coverage_cmd = item.dig("children", 0, "arguments", 0, "value", "value") || "npm run coverage"

  [
    {
      "name" => "Install dependencies",
      "run" => "npm install --no-audit",
      "shell" => "bash"
    },
    {
      "name" => "Check Code Coverage",
      "continue-on-error" => true,
      "run" => coverage_cmd,
      "shell" => "bash"
    }
  ]
end
```

## Running the importer dry-run

Use the `gh actions-importer` tool in `dry-run` mode to test extraction and transformation without writing the final workflows.

```bash theme={null}
gh actions-importer dry-run jenkins \
  --source-url http://139.84.149.83:8080/job/ci-pipeline-poll-scm/ \
  --output-dir tmp/dry-run \
  --custom-transformers helper-transformer.rb
```

Example (abridged) dry-run output showing the parsed `catchError` and the output file produced:

```text theme={null}
JSON for identifier: {"name"=>"catchError", "arguments"=>[{"key"=>"buildResult","value"=>{"isLiteral"=>true,"value"=>"SUCCESS"}},{"key"=>"message","value"=>{"isLiteral"=>true,"value"=>"Oops. It will be fixed in future releases"}},{"key"=>"stageResult","value"=>{"isLiteral"=>true,"value"=>"UNSTABLE"}},{"children"=>[{"name"=>"sh","arguments"=>[{"key"=>"script","value"=>{"isLiteral"=>true,"value"=>"npm run coverage"}}]}]}]}
Output file(s):
  tmp/dry-run/ci-pipeline-poll-scm/.github/workflows/ci-pipeline-poll-scm.yml
```

## Resulting GitHub Actions job (excerpt)

The generated Code Coverage job includes checkout, dependency installation, the coverage step with `continue-on-error: true`, and artifact upload for the HTML report.

```yaml theme={null}
jobs:
  Code_Coverage:
    name: Code Coverage
    runs-on:
      - ubuntu-latest
    container:
      image: node:24
    needs: Unit_Testing
    steps:
      - name: checkout
        uses: actions/checkout@v4.1.0
      - name: Install dependencies
        run: npm install --no-audit
        shell: bash
      - name: Check Code Coverage
        continue-on-error: true
        run: npm run coverage
        shell: bash
      - name: Upload Artifacts
        uses: actions/upload-artifact@v4.1.0
        with:
          if-no-files-found: ignore
          name: Code Coverage HTML Report
          path: coverage/lcov-report
```

## Pull request diff (abridged)

The transformer replaced the untranslated `catchError` block in the PR diff with two explicit steps that install dependencies and run the coverage command with `continue-on-error`.

```diff theme={null}
@@ -105,20 +105,13 @@ jobs:
   steps:
   - name: checkout
     uses: actions/checkout@v4.1.0
-#  # This item has no matching transformer
-#
-#  - catchError:
-#    - key: buildResult
-#      value:
-#        isLiteral: true
-#        value: SUCCESS
-#    - key: message
-#      value:
-#        isLiteral: true
-#        value: Oops! it will be fixed in future releases
-#    - key: stageResult
-#      value:
-#        isLiteral: true
-#        value: UNSTABLE
+  - name: Install dependencies
+    run: npm install --no-audit
+    shell: bash
+  - name: Check Code Coverage
+    continue-on-error: true
+    run: npm run coverage
+    shell: bash
   - name: Upload Artifacts
     uses: actions/upload-artifact@v4.1.0
```

## What happened when the workflow ran

* A new workflow run was triggered after merging the PR.
* The Code Coverage job executed the two transformed steps:
  * The `npm install --no-audit` step completed.
  * The `npm run coverage` step failed the coverage threshold (exit code non-zero).
* Because `continue-on-error: true` was set, the job continued and downstream jobs also ran.
* The coverage HTML report was uploaded as an artifact and is available for download from the run.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/iRlNGmU-DLlt2Ghu/images/Migrating-Jenkins-Pipelines-to-GitHub-Actions/Automate-Migration-From-Jenkins-to-GitHub-Actions/Demo-Custom-Transformer-Code-Coverage-catchError/github-actions-workflow-annotations-artifacts.jpg?fit=max&auto=format&n=iRlNGmU-DLlt2Ghu&q=85&s=58137dfba42dd2dd1be653b9dbd7033e" alt="A screenshot of a GitHub Actions workflow run showing the pipeline summary on the left, a visualization of jobs at the top, and an Annotations panel listing errors (Code Coverage failed and two canceled runs). Below is an Artifacts list with reports like &#x22;Code Coverage HTML Report&#x22; and &#x22;Dependency Check HTML Report.&#x22;" width="1920" height="1080" data-path="images/Migrating-Jenkins-Pipelines-to-GitHub-Actions/Automate-Migration-From-Jenkins-to-GitHub-Actions/Demo-Custom-Transformer-Code-Coverage-catchError/github-actions-workflow-annotations-artifacts.jpg" />
</Frame>

You can download the artifact (Code Coverage HTML Report) and inspect it locally:

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/iRlNGmU-DLlt2Ghu/images/Migrating-Jenkins-Pipelines-to-GitHub-Actions/Automate-Migration-From-Jenkins-to-GitHub-Actions/Demo-Custom-Transformer-Code-Coverage-catchError/windows-file-explorer-code-coverage-report.jpg?fit=max&auto=format&n=iRlNGmU-DLlt2Ghu&q=85&s=f549541173eb3970afe5d1084d866e87" alt="A dark‑theme Windows File Explorer window showing the contents of a &#x22;Code Coverage HTML Report.zip&#x22; with a list of HTML, CSS, JS and PNG files (e.g., app.js.html, base.css, index.html). The left navigation pane shows drives and folders and the right preview area says &#x22;Select a file to preview.&#x22;" width="1920" height="1080" data-path="images/Migrating-Jenkins-Pipelines-to-GitHub-Actions/Automate-Migration-From-Jenkins-to-GitHub-Actions/Demo-Custom-Transformer-Code-Coverage-catchError/windows-file-explorer-code-coverage-report.jpg" />
</Frame>

## Code coverage failure (abridged)

This is an example of the coverage output that caused the coverage command to exit non-zero:

```console theme={null}
11 passing (825ms)

ERROR: Coverage for lines (79.06%) does not meet global threshold (90%)
-------------------------------------------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------------------------------------------------
All files | 79.54 | 33.33 | 70 | 79.06 |
app.js | 79.54 | 33.33 | 70 | 79.06 | 23,49-50,58,62-67
-------------------------------------------------------
Error: Process completed with exit code 1.
```

<Callout icon="lightbulb" color="#1CB2FE">
  Using `continue-on-error: true` in the converted step mirrors Jenkins `catchError` semantics: it allows the workflow to continue and still upload artifacts even when the coverage command fails. Note that GitHub Actions will mark that step as successful for continuation, so consider also surfacing the failure via annotations, test result upload, or a PR comment if you want to preserve visibility of the failure state.
</Callout>

## Mapping summary

| Jenkins element                        |                                                               GitHub Actions equivalent | Notes                                                                                                   |
| -------------------------------------- | --------------------------------------------------------------------------------------: | ------------------------------------------------------------------------------------------------------- |
| `catchError { sh 'npm run coverage' }` | Two steps: `npm install --no-audit` + `npm run coverage` with `continue-on-error: true` | Extracts inner `sh` command and wraps in `continue-on-error` to emulate Jenkins behavior.               |
| `publishHTML`                          |                                                            `actions/upload-artifact@v4` | Importer handles this conversion automatically and uploads coverage HTML (e.g. `coverage/lcov-report`). |

Example of a JSON-like transformer output is represented in the `helper-transformer.rb` implementation above.

## References

* Jenkins `catchError` step documentation: [https://www.jenkins.io/doc/pipeline/steps/workflow-basic-steps/#catcherror-catch-error-mark-build-as-failed-until-unstable](https://www.jenkins.io/doc/pipeline/steps/workflow-basic-steps/#catcherror-catch-error-mark-build-as-failed-until-unstable)
* GitHub Actions `continue-on-error`: [https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob\_idstepscontinue-on-error](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepscontinue-on-error)
* actions/upload-artifact: [https://github.com/actions/upload-artifact](https://github.com/actions/upload-artifact)

## Summary

* We added a custom transformer that:
  * Detects a `catchError` node and extracts the inner shell command (defaults to `npm run coverage`).
  * Emits two GitHub Actions steps: an installation step and a coverage step with `continue-on-error: true`.
* The importer still converts `publishHTML` to `actions/upload-artifact` automatically, preserving HTML reports as artifacts.
* This keeps the pipeline behavior consistent: coverage failures do not block downstream jobs while reports remain available for inspection.

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/migrating-jenkins-pipelines-to-github-actions/module/3b5e500f-482a-4860-9f2c-d5f9fbc95159/lesson/0044ba0c-5480-4569-b99b-4b4a61d94af3" />
</CardGroup>
