Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ _Actions for continuous integration steps: build, lint, and test._

#### - [Build](actions/build/README.md)

#### - [Codecov](actions/codecov/README.md)

#### - [Lint](actions/lint/README.md)

#### - [Test](actions/test/README.md)
Expand Down
37 changes: 37 additions & 0 deletions actions/codecov/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Codecov Action

Action to upload coverage to Codecov with support for container mode.

## Overview

This action handles uploading code coverage to Codecov. When running in container mode, it automatically installs required dependencies (Git, cURL, gnupg) via pkgxdev, fixes configuration issues with unexpanded environment variables, uploads coverage, and then cleans up the installed dependencies.

## Usage

```yaml
- uses: hoverkraft-tech/ci-github-nodejs/actions/codecov@main
with:
# Working directory where coverage files are located.
# Can be absolute or relative to the repository root.
#
# Default: `.`
working-directory: .

# Whether running in container mode (installs dependencies if needed)
# Default: `false`
container: "false"
```

## Inputs

| **Input** | **Description** | **Required** | **Default** |
| ----------------------- | ------------------------------------------------------------------- | ------------ | ----------- |
| **`working-directory`** | Working directory where coverage files are located. | **false** | `.` |
| **`container`** | Whether running in container mode (installs dependencies if needed) | **false** | `false` |

## Features

- **Automatic dependency management**: In container mode, automatically detects and installs missing dependencies (Git, cURL, gnupg) using pkgxdev
- **Configuration fixes**: Automatically fixes pkgxdev configuration issues with unexpanded environment variables in gpgconf.ctl and .curlrc files
- **Cleanup**: Uninstalls dependencies after Codecov upload is complete
- **OIDC support**: Uses OIDC authentication with Codecov for secure uploads
128 changes: 128 additions & 0 deletions actions/codecov/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: "Codecov"
description: "Action to upload coverage to Codecov with support for container mode"
author: hoverkraft
branding:
icon: upload-cloud
color: blue

inputs:
working-directory:
description: |
Working directory where coverage files are located.
Can be absolute or relative to the repository root.
required: false
default: "."
container:
description: "Whether running in container mode (installs dependencies if needed)"
required: false
default: "false"

runs:
using: "composite"
steps:
# Check and install dependencies for codecov in container mode
- name: Check Codecov dependencies
if: inputs.container == 'true'
id: check-codecov-deps
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
// Check which dependencies are missing
const deps = {"git": "git", "curl": "curl", "gpg": "gnupg.org"};
const missingDeps = [];

for (const [dep, pkg] of Object.entries(deps)) {
try {
await io.which(dep, true);
} catch {
missingDeps.push(pkg);
}
}

if (missingDeps.length > 0) {
core.setOutput('missing-deps', missingDeps.join(' '));
}

- name: Install missing Codecov dependencies
if: steps.check-codecov-deps.outputs.missing-deps
uses: pkgxdev/setup@f211ee4db3110b42e5a156282372527e7c1ed723 # v4.0.0
with:
+: ${{ steps.check-codecov-deps.outputs.missing-deps }}

# Fix pkgxdev gnupg's gpgconf.ctl which contains unexpanded environment variables
- name: Fix GPG configuration
if: contains(steps.check-codecov-deps.outputs.missing-deps, 'gnupg.org')
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const fs = require('node:fs');
const path = require('node:path');
const os = require('node:os');

// Find and remove the malformed gpgconf.ctl file that contains unexpanded shell variables
// The pkgxdev gnupg installs gpgconf.ctl next to the gpgconf binary
try {
const gpgconfPath = await io.which('gpgconf', false);
if (gpgconfPath) {
const gpgconfCtl = path.join(path.dirname(gpgconfPath), 'gpgconf.ctl');
if (fs.existsSync(gpgconfCtl)) {
core.info(`Removing malformed gpgconf.ctl: ${gpgconfCtl}`);
await io.rmRF(gpgconfCtl);
}
}
} catch (error) {
core.warning(`Failed to check/remove gpgconf.ctl: ${error.message}`);
}

// Ensure GNUPGHOME is set up correctly
const gnupgHome = path.join(os.homedir(), '.gnupg');
await io.mkdirP(gnupgHome);
fs.chmodSync(gnupgHome, 0o700);

# Fix pkgxdev curl's .curlrc which contains unexpanded environment variables
- name: Fix curl configuration
if: contains(steps.check-codecov-deps.outputs.missing-deps, 'curl')
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const fs = require('node:fs');
const path = require('node:path');

// Find and fix the .curlrc file that contains unexpanded shell variables like ${SSL_CERT_FILE:-...}
// The pkgxdev curl installs .curlrc next to the curl binary
try {
const curlPath = await io.which('curl', false);
if (curlPath) {
const curlrc = path.join(path.dirname(curlPath), '.curlrc');
if (fs.existsSync(curlrc)) {
core.info(`Removing malformed .curlrc: ${curlrc}`);
await io.rmRF(curlrc);
}
}
} catch (error) {
core.warning(`Failed to check/remove .curlrc: ${error.message}`);
}

- name: 📊 Upload coverage to Codecov
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
with:
directory: ${{ inputs.working-directory }}
root_dir: ${{ inputs.working-directory }}
working-directory: ${{ inputs.working-directory }}
use_oidc: true
disable_telem: true
fail_ci_if_error: false

# Uninstall pkgxdev dependencies after codecov is done
- name: Uninstall Codecov dependencies
if: always() && steps.check-codecov-deps.outputs.missing-deps
shell: bash
env:
MISSING_DEPS: ${{ steps.check-codecov-deps.outputs.missing-deps }}
run: |
if command -v pkgx &> /dev/null; then
for dep in $MISSING_DEPS; do
echo "Uninstalling $dep..."
pkgx uninstall "$dep" 2>/dev/null || true
done
fi
67 changes: 2 additions & 65 deletions actions/test/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,75 +142,12 @@ runs:
title: "Code Coverage Report"
body: ${{ steps.parse-coverage-reports.outputs.markdown }}

# Check and install dependencies for codecov in container mode
- name: Check and install Codecov dependencies
if: always() && inputs.coverage == 'codecov' && inputs.container == 'true'
id: check-codecov-deps
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
// Check which dependencies are missing
const deps = {"git": "git", "curl": "curl", "gpg": "gnupg.org"};
const missingDeps = [];

for (const [dep, pkg] of Object.entries(deps)) {
try {
await io.which(dep, true);
} catch {
missingDeps.push(pkg);
}
}

if (missingDeps.length > 0) {
core.setOutput('missing-deps', missingDeps.join(' '));
}

- name: Install missing Codecov dependencies
if: always() && steps.check-codecov-deps.outputs.missing-deps
uses: pkgxdev/setup@f211ee4db3110b42e5a156282372527e7c1ed723 # v4.0.0
with:
+: ${{ steps.check-codecov-deps.outputs.missing-deps }}

# Fix pkgxdev gnupg's gpgconf.ctl which contains unexpanded environment variables
- name: Fix GPG configuration
if: always() && contains(steps.check-codecov-deps.outputs.missing-deps, 'gnupg.org')
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const fs = require('node:fs');
const path = require('node:path');
const os = require('node:os');

// Find and remove the malformed gpgconf.ctl file that contains unexpanded shell variables
// The pkgxdev gnupg installs gpgconf.ctl next to the gpgconf binary
try {
const gpgconfPath = await io.which('gpgconf', false);
if (gpgconfPath) {
const gpgconfCtl = path.join(path.dirname(gpgconfPath), 'gpgconf.ctl');
if (fs.existsSync(gpgconfCtl)) {
core.info(`Removing malformed gpgconf.ctl: ${gpgconfCtl}`);
await io.rmRF(gpgconfCtl);
}
}
} catch (error) {
core.warning(`Failed to check/remove gpgconf.ctl: ${error.message}`);
}

// Ensure GNUPGHOME is set up correctly
const gnupgHome = path.join(os.homedir(), '.gnupg');
await io.mkdirP(gnupgHome);
fs.chmodSync(gnupgHome, 0o700);

- name: 📊 Upload coverage to Codecov
if: always() && inputs.coverage == 'codecov'
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
uses: ./self-test-action/codecov
with:
directory: ${{ inputs.working-directory }}
root_dir: ${{ inputs.working-directory }}
working-directory: ${{ inputs.working-directory }}
use_oidc: true
disable_telem: true
fail_ci_if_error: false
container: ${{ inputs.container }}

# FIXME: workaround until will be merged: https://github.com/actions/runner/pull/1684
- shell: bash
Expand Down