diff --git a/.github/workflows/__test-action-get-package-manager.yml b/.github/workflows/__test-action-get-package-manager.yml
index 609ba43..17458fc 100644
--- a/.github/workflows/__test-action-get-package-manager.yml
+++ b/.github/workflows/__test-action-get-package-manager.yml
@@ -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:
diff --git a/.github/workflows/continuous-integration.md b/.github/workflows/continuous-integration.md
index c58cfee..d6d4178 100644
--- a/.github/workflows/continuous-integration.md
+++ b/.github/workflows/continuous-integration.md
@@ -1,6 +1,6 @@
-# GitHub Reusable Workflow: NodeJS Continuous Integration
+# GitHub Reusable Workflow: Node.js Continuous Integration

@@ -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
@@ -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
. | **false** | **string** | `typescript` |
| **`dependency-review`** | Enable dependency review scan. See . | **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** | `.` |
diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index 4810d18..538d308 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -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:
@@ -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"
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..1883ca0
--- /dev/null
+++ b/AGENTS.md
@@ -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).
diff --git a/README.md b/README.md
index 3caae47..0a917f0 100644
--- a/README.md
+++ b/README.md
@@ -5,25 +5,88 @@
[](#license)
[](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
@@ -34,5 +97,10 @@ Opinionated GitHub Actions and workflows for continuous integration in Node.js c
## License
-📝 Copyright © 2023 [Hoverkraft ](https://hoverkraft.cloud).
-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/).
diff --git a/actions/dependencies-cache/action.yml b/actions/dependencies-cache/action.yml
index 6566293..56d1bff 100644
--- a/actions/dependencies-cache/action.yml
+++ b/actions/dependencies-cache/action.yml
@@ -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: "."
@@ -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
diff --git a/actions/get-package-manager/action.yml b/actions/get-package-manager/action.yml
index e2f2f79..e5bb902 100644
--- a/actions/get-package-manager/action.yml
+++ b/actions/get-package-manager/action.yml
@@ -1,5 +1,5 @@
name: "Get package manager"
-description: "Action to detect the package manager used. Supports Yarn and npm"
+description: "Action to detect the package manager used. Supports Yarn, pnpm, and npm"
author: Hoverkraft
branding:
icon: package
@@ -7,7 +7,9 @@ branding:
inputs:
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: "."
@@ -29,62 +31,94 @@ runs:
using: "composite"
steps:
- id: get-package-manager
- shell: bash
- working-directory: ${{ inputs.working-directory }}
- run: |
- echo "::debug::::Running in working directory: $(pwd)";
-
- # Check if the package manager is set in package.json
- PACKAGE_MANAGER_NAME=""
- if [ -f "package.json" ]; then
- PACKAGE_MANAGER=$(jq -r '.packageManager//empty' package.json)
- if [ -n "$PACKAGE_MANAGER" ]; then
- # Extract the package manager and version from the packageManager field
- PACKAGE_MANAGER_NAME=$(echo "$PACKAGE_MANAGER" | cut -d'@' -f1)
- fi
- fi
-
- echo "::debug::Package manager from package.json: $PACKAGE_MANAGER_NAME";
-
- if [ -z "$PACKAGE_MANAGER_NAME" ]; then
- if [ -f "yarn.lock" ]; then
- PACKAGE_MANAGER_NAME="yarn"
- elif [ -f "pnpm-lock.yaml" ]; then
- PACKAGE_MANAGER_NAME="pnpm"
- elif [ -f "package-lock.json" ]; then
- PACKAGE_MANAGER_NAME="npm"
- fi
-
- if [ -z "$PACKAGE_MANAGER_NAME" ]; then
- echo "::error ::Unable to detect package manager";
- exit 1;
- fi
- echo "::debug::Package manager from lock files: $PACKAGE_MANAGER_NAME";
- fi
-
- echo "package-manager=$PACKAGE_MANAGER_NAME" >> "$GITHUB_OUTPUT";
-
- case "$PACKAGE_MANAGER_NAME" in
- yarn)
- echo "cache-dependency-path=**/yarn.lock" >> "$GITHUB_OUTPUT";
- echo "install-command=yarn install --frozen-lockfile" >> "$GITHUB_OUTPUT";
- echo "run-script-command=yarn" >> "$GITHUB_OUTPUT";
- exit 0;
- ;;
- pnpm)
- echo "cache-dependency-path=**/pnpm-lock.yaml" >> "$GITHUB_OUTPUT";
- echo "install-command=pnpm install --frozen-lockfile" >> "$GITHUB_OUTPUT";
- echo "run-script-command=pnpm" >> "$GITHUB_OUTPUT";
- exit 0;
- ;;
- npm)
- echo "cache-dependency-path=**/package-lock.json" >> "$GITHUB_OUTPUT";
- echo "install-command=npm ci" >> "$GITHUB_OUTPUT";
- echo "run-script-command=npm run" >> "$GITHUB_OUTPUT";
- exit 0;
- ;;
- *)
- echo "::error ::Package manager $PACKAGE_MANAGER_NAME is not supported";
- exit 1;
- ;;
- esac
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ WORKING_DIRECTORY: ${{ inputs.working-directory }}
+ 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);
+
+ let packageManagerName = '';
+ const packageJsonPath = path.join(workingDirectory, 'package.json');
+
+ if (fs.existsSync(packageJsonPath)) {
+ try {
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
+ const packageManager = packageJson?.packageManager;
+
+ if (typeof packageManager === 'string' && packageManager.trim() !== '') {
+ packageManagerName = packageManager.split('@')[0];
+ }
+ } catch (error) {
+ core.warning(`Failed to parse package.json: ${error.message}`);
+ }
+ }
+
+ core.debug(`Package manager from package.json: ${packageManagerName}`);
+
+ if (!packageManagerName) {
+ const lockFiles = [
+ { file: 'yarn.lock', name: 'yarn' },
+ { file: 'pnpm-lock.yaml', name: 'pnpm' },
+ { file: 'package-lock.json', name: 'npm' },
+ ];
+
+ const detectedLockFile = lockFiles.find(({ file }) => fs.existsSync(path.join(workingDirectory, file)));
+
+ if (!detectedLockFile) {
+ core.setFailed('Unable to detect package manager');
+ return;
+ }
+
+ packageManagerName = detectedLockFile.name;
+ core.debug(`Package manager from lock files: ${packageManagerName}`);
+ }
+
+ const relativeWorkingDirectory = path.relative(process.env.GITHUB_WORKSPACE, workingDirectory) || '.';
+
+ const packageManagerConfig = {
+ yarn: {
+ cacheDependencyPath: `${relativeWorkingDirectory}/**/yarn.lock`,
+ installCommand: 'yarn install --frozen-lockfile',
+ runScriptCommand: 'yarn',
+ },
+ pnpm: {
+ cacheDependencyPath: `${relativeWorkingDirectory}/**/pnpm-lock.yaml`,
+ installCommand: 'pnpm install --frozen-lockfile',
+ runScriptCommand: 'pnpm',
+ },
+ npm: {
+ cacheDependencyPath: `${relativeWorkingDirectory}/**/package-lock.json`,
+ installCommand: 'npm ci',
+ runScriptCommand: 'npm run',
+ },
+ };
+
+ const managerConfig = packageManagerConfig[packageManagerName];
+
+ if (!managerConfig) {
+ core.setFailed(`Package manager ${packageManagerName} is not supported`);
+ return;
+ }
+
+ core.setOutput('package-manager', packageManagerName);
+ core.setOutput('cache-dependency-path', managerConfig.cacheDependencyPath);
+ core.setOutput('install-command', managerConfig.installCommand);
+ core.setOutput('run-script-command', managerConfig.runScriptCommand);
+ # jscpd:ignore-end
diff --git a/actions/has-installed-dependencies/action.yml b/actions/has-installed-dependencies/action.yml
index 11c02dc..f8e82b9 100644
--- a/actions/has-installed-dependencies/action.yml
+++ b/actions/has-installed-dependencies/action.yml
@@ -10,7 +10,9 @@ inputs:
description: "The dependencies to check."
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: "."
@@ -39,8 +41,28 @@ runs:
- id: has-dependencies
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ WORKING_DIRECTORY: ${{ inputs.working-directory }}
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 dependenciesPatterns = {
storybook: /@storybook\/[-a-z]+/,
nx: /@nx\/[-a-z]+/,
@@ -97,7 +119,7 @@ runs:
const parseDependencies = async (command) => {
const { stdout } = await exec.getExecOutput(command, undefined, {
- cwd: `${{ inputs.working-directory }}` || undefined,
+ cwd: workingDirectory,
});
if (stdout === '') {
@@ -145,3 +167,4 @@ runs:
}
core.setOutput('installed-dependencies', hasDependencies);
+ # jscpd:ignore-end
diff --git a/actions/setup-node/action.yml b/actions/setup-node/action.yml
index 21706bc..11ac5b5 100644
--- a/actions/setup-node/action.yml
+++ b/actions/setup-node/action.yml
@@ -11,7 +11,9 @@ inputs:
required: false
default: ""
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: "."
@@ -33,33 +35,98 @@ runs:
working-directory: ${{ inputs.working-directory }}
- id: get-node-version-file
- shell: bash
- working-directory: ${{ inputs.working-directory }}
- run: |
- SUPPORTED_FILES=(".nvmrc" ".node-version")
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ WORKING_DIRECTORY: ${{ inputs.working-directory }}
+ with:
+ # jscpd:ignore-start
+ script: |
+ const path = require('node:path');
+ const fs = require('node:fs');
+
+ 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);
- # Check if any of the supported files exist
- for file in "${SUPPORTED_FILES[@]}"; do
- if [ -f "$file" ]; then
- NODE_VERSION_FILE=$(realpath -s --relative-to="$GITHUB_WORKSPACE" "$(pwd)/$file")
- echo "node-version-file=$NODE_VERSION_FILE" >> $GITHUB_OUTPUT
- exit 0
- fi
- done
+ const candidates = ['.nvmrc', '.node-version'];
+
+ for (const fileName of candidates) {
+ const candidatePath = path.resolve(workingDirectory, fileName);
+
+ if (!fs.existsSync(candidatePath)) {
+ continue;
+ }
+
+ const relativePath = path.relative(process.env.GITHUB_WORKSPACE, candidatePath);
+ core.debug(`Found Node version file: ${relativePath}`);
+ core.setOutput('node-version-file', relativePath);
+ return;
+ }
+
+ core.debug('No Node version file found in supported list');
+ # jscpd:ignore-end
# FIXME: workaround until will be merged: https://github.com/actions/setup-node/pull/901
- id: get-pnpm-version
if: steps.get-package-manager.outputs.package-manager == 'pnpm'
- shell: bash
- working-directory: ${{ inputs.working-directory }}
- run: |
- if [ -f "package.json" ]; then
- if [ -n "$(jq -r '.packageManager//empty' package.json)" ]; then
- # pnpm/action-setup supports "packageManager" field in package.json
- exit 0
- fi
- fi
- echo "pnpm-version=latest" >> "$GITHUB_OUTPUT"
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ WORKING_DIRECTORY: ${{ inputs.working-directory }}
+ with:
+ script: |
+ const path = require('node:path');
+ const fs = require('node:fs');
+
+ 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);
+
+ let packageJsonPath = path.join(workingDirectory, 'package.json');
+ if (!fs.existsSync(packageJsonPath)) {
+ core.debug(`package.json not found in working directory "${workingDirectory}"; defaulting pnpm version to latest`);
+ core.setOutput('pnpm-version', 'latest');
+ return;
+ }
+ packageJsonPath = path.resolve(packageJsonPath);
+
+ let packageJson;
+ try {
+ const fileContent = fs.readFileSync(packageJsonPath, 'utf8');
+ packageJson = JSON.parse(fileContent);
+ } catch (error) {
+ core.setFailed(`Unable to read ${packageJsonPath} to determine pnpm version: ${error.message}`);
+ return;
+ }
+
+ const packageManagerField = (packageJson?.packageManager ?? '').trim();
+
+ if (packageManagerField) {
+ core.debug('package.json defines packageManager; pnpm/action-setup will use it');
+ return;
+ }
+
+ core.debug('packageManager field missing; defaulting pnpm version to latest');
+ core.setOutput('pnpm-version', 'latest');
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
if: steps.get-package-manager.outputs.package-manager == 'pnpm'