Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 4 additions & 4 deletions .github/workflows/__test-action-get-package-manager.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,28 @@ jobs:
- working-directory: tests/npm
package-manager: npm
lock-file: package-lock.json
cache-dependency-path: "**/package-lock.json"
cache-dependency-path: "tests/npm/**/package-lock.json"
install-command: npm ci
run-script-command: npm run

- working-directory: tests/pnpm
package-manager: pnpm
lock-file: pnpm-lock.yaml
cache-dependency-path: "**/pnpm-lock.yaml"
cache-dependency-path: "tests/pnpm/**/pnpm-lock.yaml"
install-command: pnpm install --frozen-lockfile
run-script-command: pnpm

- working-directory: tests/pnpm-package-manager
package-manager: pnpm
lock-file: pnpm-lock.yaml
cache-dependency-path: "**/pnpm-lock.yaml"
cache-dependency-path: "tests/pnpm-package-manager/**/pnpm-lock.yaml"
install-command: pnpm install --frozen-lockfile
run-script-command: pnpm

- working-directory: tests/yarn
package-manager: yarn
lock-file: yarn.lock
cache-dependency-path: "**/yarn.lock"
cache-dependency-path: "tests/yarn/**/yarn.lock"
install-command: yarn install --frozen-lockfile
run-script-command: yarn
steps:
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/continuous-integration.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!-- header:start -->

# GitHub Reusable Workflow: NodeJS Continuous Integration
# GitHub Reusable Workflow: Node.js Continuous Integration

<div align="center">
<img src="https://opengraph.githubassets.com/d404625773c747748dc7d301c22e3486f68a45a9b6ecf6dcbbc8827f4cf9ccf8/hoverkraft-tech/ci-github-nodejs" width="60px" align="center" alt="NodeJS Continuous Integration" />
Expand All @@ -23,7 +23,7 @@

## Overview

Workflow to performs continuous integration steps agains a NodeJs project:
Workflow to performs continuous integration steps agains a Node.js project:

- CodeQL analysis
- Linting
Expand Down Expand Up @@ -99,13 +99,13 @@ jobs:

| **Input** | **Description** | **Required** | **Type** | **Default** |
| ----------------------- | ----------------------------------------------------------------------------------------- | ------------ | ----------- | ------------ |
| **`build`** | Build parameters. Must be a string or a json object. | **false** | **string** | `build` |
| **`build`** | Build parameters. Must be a string or a JSON object. | **false** | **string** | `build` |
| **`checks`** | Optional flag to enable check steps. | **false** | **boolean** | `true` |
| **`lint`** | Optional flag to enable linting. | **false** | **boolean** | `true` |
| **`code-ql`** | Code QL analysis language. See <https://github.com/github/codeql-action>. | **false** | **string** | `typescript` |
| **`dependency-review`** | Enable dependency review scan. See <https://github.com/actions/dependency-review-action>. | **false** | **boolean** | `true` |
| **`test`** | Optional flag to enable test. | **false** | **boolean** | `true` |
| **`coverage`** | Specifify code coverage reporter. Supported values: 'codecov'. | **false** | **string** | `codecov` |
| **`coverage`** | Specifify code coverage reporter. Supported values: 'Codecov'. | **false** | **string** | `codecov` |
| **`working-directory`** | Working directory where the dependencies are installed. | **false** | **string** | `.` |

<!-- inputs:end -->
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Workflow to performs continuous integration steps agains a NodeJs project:
# Workflow to performs continuous integration steps agains a Node.js project:
#
# - CodeQL analysis
# - Linting
# - Build
# - Test

name: NodeJS Continuous Integration
name: Node.js Continuous Integration

on:
workflow_call:
Expand Down Expand Up @@ -41,7 +41,7 @@ on:
required: false
default: true
coverage:
description: "Specifify code coverage reporter. Supported values: 'codecov'."
description: "Specifify code coverage reporter. Supported values: `codecov`."
type: string
required: false
default: "codecov"
Expand Down
32 changes: 32 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# AGENTS.md — agent instructions and operational contract

This file is written for automated coding agents (for example: Copilot coding agents). It exists to provide a concise operational contract and guardrails for agents working in this repository. It is not the canonical source for design or style rules. Those live in the developer documentation linked below.

## Organization-wide guidelines (required)

- Follow the prioritized shared instructions in [hoverkraft-tech/.github/AGENTS.md](https://github.com/hoverkraft-tech/.github/blob/main/AGENTS.md) before working in this repository.

## Quick Start

This project is a collection of **opinionated GitHub Actions** and **reusable workflows** tailored for Node.js continuous integration pipelines. For comprehensive documentation, see the main [README.md](README.md).

### Key Sections to Reference

- **[Overview](README.md#overview)** – Project purpose and scope
- **[Actions](README.md#actions)** – Catalog of available actions by category
- **[Reusable Workflows](README.md#reusable-workflows)** – Orchestration workflows for Node.js CI
- **[Development Workflow](README.md#development-workflow)** – Commands and conventions for local development
- **[Contributing](README.md#contributing)** – Guidelines for contributing to the project

## Agent-Specific Development Patterns

### Critical Workflow Knowledge

```bash
# Essential commands for development
make lint # Run Super Linter (dockerized)
make lint-fix # Auto-fix linting issues
gh act -W .github/workflows/__test-workflow-continuous-integration.yml # Optional: exercise reusable workflows locally
```

For detailed documentation on each action and workflow, refer to the individual readme files linked in the main [README.md](README.md).
86 changes: 77 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,88 @@
[![License](https://img.shields.io/badge/License-MIT-blue)](#license)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)

Opinionated GitHub Actions and workflows for continuous integration in Node.js context
Opinionated GitHub Actions and reusable workflows for Node.js continuous integration pipelines.

---

## Overview

This repository centralizes the Hoverkraft toolkit for building, testing, and shipping Node.js projects on GitHub. It bundles:

- Composite actions that detect project tooling, manage dependencies, and bootstrap runtimes.
- Reusable workflows that apply those actions to deliver consistent CI pipelines across repositories.

## Actions

### - [Get package manager](actions/get-package-manager/README.md)
### Dependencies

_Actions dedicated to caching and validating Node.js dependencies._

#### - [Dependencies cache](actions/dependencies-cache/README.md)

#### - [Has installed dependencies](actions/has-installed-dependencies/README.md)

### Environment setup

### - [Has installed dependencies](actions/has-installed-dependencies/README.md)
_Actions focused on discovering and preparing the Node.js environment._

### - [Setup node](actions/setup-node/README.md)
#### - [Get package manager](actions/get-package-manager/README.md)

## Workflows
#### - [Setup node](actions/setup-node/README.md)

### - [Continuous Integration](.github/workflows/continuous-integration.md)
## Reusable Workflows

### Continuous Integration

- [Continuous Integration](.github/workflows/continuous-integration.md) — documentation for the reusable Node.js CI workflow.

## Contributing

👍 If you wish to contribute to this project, please read the [CONTRIBUTING.md](CONTRIBUTING.md) file, PRs are Welcome !
Contributions are welcome! Please review the [contributing guidelines](CONTRIBUTING.md) before opening a PR.

### Action Structure Pattern

All actions follow a consistent layout:

```text
actions/{category}/{action-name}/
├── action.yml # Action definition with inputs/outputs
├── README.md # Usage documentation and examples
└── index.js / scripts # Optional Node.js helpers (when required)
```

### Development Standards

#### Action Definition Standards

1. **Consistent branding**: Use `author: hoverkraft` with `color: blue` and a meaningful `icon`.
2. **Pinned dependencies**: Reference third-party actions via exact SHAs to guarantee reproducibility.
3. **Input validation**: Validate critical inputs early within composite steps or supporting scripts.
4. **Idempotent steps**: Ensure actions can run multiple times without leaving residual state in the workspace.
5. **Multi-platform support**: Test actions in both `ubuntu-latest` and `windows-latest` runners when applicable.
6. **Cross-platform compatibility**: Uses `actions/github-script` steps for cross-platform compatibility. Avoid `run` steps.
7. **Logging**: Use structured logs with clear prefixes (`[build-image]`, `[helm-test-chart]`, …) to simplify debugging.
8. **Security**: Avoid shell interpolation with untrusted inputs; prefer parameterized commands or `set -euo pipefail` wrappers.

#### File Conventions

- **Tests**: Located in `tests/` with fixtures for container builds and chart-testing scenarios.
- **Workflows**: Reusable definitions live in `.github/workflows/`; internal/private workflows are prefixed with `__`.

#### JavaScript Development Patterns

- Encapsulate reusable logic in modules under the action directory (for example, `actions/my-action/index.js`).
- Prefer async/await with explicit error handling when interacting with the GitHub API or filesystem.
- Centralize environment variable parsing and validation to keep composite YAML lean.

### Development Workflow

#### Linting & Testing

```bash
make lint # Run the dockerized Super Linter
make lint-fix # Attempt auto-fixes for lint findings
```

## Author

Expand All @@ -34,5 +97,10 @@ Opinionated GitHub Actions and workflows for continuous integration in Node.js c

## License

📝 Copyright © 2023 [Hoverkraft <contact@hoverkraft.cloud>](https://hoverkraft.cloud).<br />
This project is [MIT](LICENSE) licensed.
This project is licensed under the MIT License.

SPDX-License-Identifier: MIT

Copyright © 2023 [Hoverkraft](https://hoverkraft.cloud).

For more details, see the [license](http://choosealicense.com/licenses/mit/).
107 changes: 77 additions & 30 deletions actions/dependencies-cache/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ inputs:
description: "List of dependencies for which the cache should be managed."
required: true
working-directory:
description: "Working directory where the dependencies are installed."
description: |
Working directory where the dependencies are installed.
Can be absolute or relative to the repository root.
required: false
default: "."

Expand Down Expand Up @@ -75,35 +77,80 @@ runs:
- name: ♻️ Get Jest cache dir
id: jest-cache-dir-path
if: fromJson(steps.has-installed-dependencies.outputs.installed-dependencies).jest == true
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
case "${{ steps.get-package-manager.outputs.package-manager }}" in
npm)
JEST_CONFIG=$(${{ steps.get-package-manager.outputs.package-manager }} exec jest -- --showConfig)
;;
*)
JEST_CONFIG=$(${{ steps.get-package-manager.outputs.package-manager }} jest --showConfig)
;;
esac

if [ -z "$JEST_CONFIG" ]; then
echo "::error::Unable to get Jest config"
exit 1
fi

echo "::debug::Jest config: $JEST_CONFIG"

JEST_CACHE_DIR=$(echo "$JEST_CONFIG" | grep -oP '(?<="cacheDirectory": ")[^"]+(?=")')

if [ -z "$JEST_CACHE_DIR" ]; then
echo "::error ::Unable to get Jest cache directory from config: $JEST_CONFIG"
exit 1
fi

echo "::debug::Jest cache directory: $JEST_CACHE_DIR"

echo "dir=$JEST_CACHE_DIR" >> "$GITHUB_OUTPUT"
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
WORKING_DIRECTORY: ${{ inputs.working-directory }}
PACKAGE_MANAGER: ${{ steps.get-package-manager.outputs.package-manager }}
with:
# jscpd:ignore-start
script: |
const fs = require('node:fs');
const path = require('node:path');

let workingDirectory = process.env.WORKING_DIRECTORY || '.';
if (!path.isAbsolute(workingDirectory)) {
workingDirectory = path.join(process.env.GITHUB_WORKSPACE, workingDirectory);
}

if (!fs.existsSync(workingDirectory)) {
core.setFailed(`The specified working directory does not exist: ${workingDirectory}`);
return;
}

workingDirectory = path.resolve(workingDirectory);
core.debug(`Running in working directory: ${workingDirectory}`);
process.chdir(workingDirectory);

const packageManager = process.env.PACKAGE_MANAGER;
if (!packageManager) {
core.setFailed('Unable to determine package manager');
return;
}
core.debug(`Using package manager: ${packageManager}`);

const commandArgs = packageManager === 'npm'
? ['exec', 'jest', '--', '--showConfig']
: ['jest', '--showConfig'];

let execResult;
try {
execResult = await exec.getExecOutput(packageManager, commandArgs, { cwd: workingDirectory });
} catch (error) {
core.setFailed(`Unable to get Jest config: ${error.message}`);
return;
}

if (execResult.exitCode !== 0) {
const errorMessage = execResult.stderr?.trim() || execResult.stdout?.trim();
core.setFailed(`Unable to get Jest config (exit code ${execResult.exitCode}): ${errorMessage}`);
return;
}

const jestConfigRaw = execResult.stdout.trim();

if (!jestConfigRaw) {
core.setFailed('Unable to get Jest config');
return;
}

core.debug(`Jest config: ${jestConfigRaw}`);

// Find cacheDirectory in the config with regex
const cacheDirMatch = jestConfigRaw.match(/"cacheDirectory"\s*:\s*"([^"]+)"/);
if (!cacheDirMatch || cacheDirMatch.length < 2) {
core.setFailed('Unable to find cacheDirectory in Jest config');
return;
}

let jestCacheDir = cacheDirMatch[1];
if (!path.isAbsolute(jestCacheDir)) {
jestCacheDir = path.join(workingDirectory, jestCacheDir);
}
jestCacheDir = path.resolve(jestCacheDir);

core.debug(`Jest cache directory: ${jestCacheDir}`);
core.setOutput('dir', jestCacheDir);
# jscpd:ignore-end

- name: ♻️ Test cache
if: steps.jest-cache-dir-path.outputs.dir
Expand Down
Loading
Loading