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
126 changes: 126 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
name: CI

on:
push:
branches:
- main
pull_request:

jobs:
unit:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- uses: ./
with:
node-version: 22.x

- name: Run unit tests
run: npm test

fixture-lookup:
name: Fixture Lookup (${{ matrix.package-manager }})
needs: unit
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- package-manager: npm
fixture: fixtures/npm-basic
install-command: npm ci
lockfile-name: package-lock.json
- package-manager: pnpm
fixture: fixtures/pnpm-basic
install-command: pnpm install --frozen-lockfile
lockfile-name: pnpm-lock.yaml
- package-manager: yarn
fixture: fixtures/yarn-basic
install-command: yarn install --immutable
lockfile-name: yarn.lock
steps:
- uses: actions/checkout@v6
with:
sparse-checkout: |
action.yml
scripts
${{ matrix.fixture }}
sparse-checkout-cone-mode: false

- name: Configure Node.js
id: configure-nodejs
uses: ./
with:
working-directory: ${{ matrix.fixture }}
cache-key-suffix: fixture-tests
lookup-only: "true"

- name: Assert resolved action outputs
shell: bash
run: |
test "${{ steps.configure-nodejs.outputs.package-manager }}" = "${{ matrix.package-manager }}"
test "${{ steps.configure-nodejs.outputs.install-command }}" = "${{ matrix.install-command }}"
test "${{ steps.configure-nodejs.outputs.working-directory }}" = "${{ matrix.fixture }}"
test "${{ steps.configure-nodejs.outputs.lockfile-name }}" = "${{ matrix.lockfile-name }}"

- name: Assert lookup-only behavior
working-directory: ${{ matrix.fixture }}
shell: bash
run: |
if [ "${{ steps.configure-nodejs.outputs.cache-hit }}" = "true" ]; then
test ! -d node_modules
else
test -d node_modules
node check.mjs
fi

fixture-validate:
name: Fixture Validate (${{ matrix.package-manager }})
needs: fixture-lookup
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- package-manager: npm
fixture: fixtures/npm-basic
install-command: npm ci
lockfile-name: package-lock.json
- package-manager: pnpm
fixture: fixtures/pnpm-basic
install-command: pnpm install --frozen-lockfile
lockfile-name: pnpm-lock.yaml
- package-manager: yarn
fixture: fixtures/yarn-basic
install-command: yarn install --immutable
lockfile-name: yarn.lock
steps:
- uses: actions/checkout@v6
with:
sparse-checkout: |
action.yml
scripts
${{ matrix.fixture }}
sparse-checkout-cone-mode: false

- name: Configure Node.js
id: configure-nodejs
uses: ./
with:
working-directory: ${{ matrix.fixture }}
cache-key-suffix: fixture-tests

- name: Assert restore behavior
shell: bash
run: |
test "${{ steps.configure-nodejs.outputs.package-manager }}" = "${{ matrix.package-manager }}"
test "${{ steps.configure-nodejs.outputs.install-command }}" = "${{ matrix.install-command }}"
test "${{ steps.configure-nodejs.outputs.working-directory }}" = "${{ matrix.fixture }}"
test "${{ steps.configure-nodejs.outputs.lockfile-name }}" = "${{ matrix.lockfile-name }}"
test "${{ steps.configure-nodejs.outputs.cache-hit }}" = "true"

- name: Validate installed dependency
working-directory: ${{ matrix.fixture }}
run: node check.mjs
68 changes: 68 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Release

on:
push:
tags:
- "v*.*.*"

jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Update major version tag
uses: actions/github-script@v8
env:
VERSION_TAG: ${{ github.ref_name }}
with:
script: |
const versionTag = process.env.VERSION_TAG;
const match = versionTag.match(/^v(\d+)\.\d+\.\d+(?:[-+].+)?$/);

if (!match) {
core.setFailed(`Expected a semantic version tag like v1.2.3, got ${versionTag}`);
return;
}

const majorTag = `v${match[1]}`;
const owner = context.repo.owner;
const repo = context.repo.repo;
const sha = context.sha;

try {
await github.rest.git.getRef({
owner,
repo,
ref: `tags/${majorTag}`,
});

await github.rest.git.updateRef({
owner,
repo,
ref: `tags/${majorTag}`,
sha,
force: true,
});
} catch (error) {
if (error.status !== 404) {
throw error;
}

await github.rest.git.createRef({
owner,
repo,
ref: `refs/tags/${majorTag}`,
sha,
});
}

- name: Publish GitHub release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
prerelease: ${{ contains(github.ref_name, '-') }}
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules/
.DS_Store

fixtures/*/node_modules/
fixtures/*/.pnp.*
fixtures/*/.yarn/install-state.gz
fixtures/*/.yarn/cache/
123 changes: 123 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# configure-nodejs

`configure-nodejs` is a composite GitHub Action for repositories that want one step to:

- install Node.js
- detect npm, pnpm, or Yarn from `package.json` and lockfiles
- enable Corepack when the package manager needs it
- restore a `node_modules`-oriented dependency cache
- install dependencies on cache miss

The action is designed to be shared as `pwrdrvr/configure-nodejs@v1` and to work for both repository-root projects and subdirectory projects in monorepos.

## Usage

### Root project

```yaml
steps:
- uses: actions/checkout@v6
- uses: pwrdrvr/configure-nodejs@v1
with:
node-version: 22.x
```

### Subdirectory project

```yaml
steps:
- uses: actions/checkout@v6
- uses: pwrdrvr/configure-nodejs@v1
with:
node-version: 22.x
working-directory: apps/web
```

### Cache lookup without restore

```yaml
steps:
- uses: actions/checkout@v6
- id: configure-nodejs
uses: pwrdrvr/configure-nodejs@v1
with:
lookup-only: "true"

- if: steps.configure-nodejs.outputs.cache-hit == 'true'
run: echo "Dependency cache is available"
```

## Inputs

| Input | Default | Description |
| --- | --- | --- |
| `node-version` | `22.x` | Node.js version to install with `actions/setup-node` |
| `package-manager` | `""` | Explicit override for `npm`, `pnpm`, or `yarn` |
| `working-directory` | `"."` | Repository-relative directory containing `package.json` and the lockfile |
| `cache-key-suffix` | `""` | Optional suffix appended to the dependency cache key when you want to namespace cache entries |
| `lookup-only` | `"false"` | When `true`, only checks whether the cache exists and skips downloading it |

## Outputs

| Output | Description |
| --- | --- |
| `package-manager` | Resolved package manager |
| `package-manager-version` | Version from `package.json#packageManager` when available |
| `manager-cache-key` | Manager-specific cache-key segment |
| `lockfile-name` | Detected lockfile name |
| `lockfile-path` | Lockfile path relative to the working directory |
| `lockfile-sha` | SHA256 hash of the lockfile |
| `install-command` | Install command used on cache miss |
| `working-directory` | Normalized working directory |
| `working-directory-key` | Cache-key-safe working-directory identifier |
| `cache-hit` | `true` when the dependency cache entry exists for the computed key |

## Package-manager detection

Resolution order:

1. explicit `package-manager` input
2. `package.json#packageManager`
3. supported lockfiles in the working directory

The action fails fast when it finds multiple supported lockfiles in the same working directory.

## Caching behavior

The cache key includes:

- `node-version`
- runner OS and architecture
- normalized working directory
- resolved package manager and version
- lockfile SHA

The cache paths are scoped to the configured `working-directory`, which keeps fixture directories and subdirectory apps isolated from each other.

## Yarn support boundary

The first release targets Yarn repositories that install into `node_modules`.

- Yarn 2+ uses `yarn install --immutable`
- Yarn 1 uses `yarn install --frozen-lockfile`
- the fixture coverage in this repository uses Yarn 4 with `nodeLinker: node-modules`

Plug'n'Play-specific caching is intentionally out of scope for `v1`.

## Development

This repository includes three end-to-end fixtures under `fixtures/`:

- `fixtures/npm-basic`
- `fixtures/pnpm-basic`
- `fixtures/yarn-basic`

The CI workflow runs local unit tests plus end-to-end matrix coverage for those fixtures, followed by cache-lookup verification in a second matrix job.

## Releases

Tag a semantic version such as `v1.0.0` to trigger the release workflow. The workflow creates or updates the floating major tag like `v1` and publishes a GitHub release with generated notes.

## License

[MIT](LICENSE)
Loading