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

on:
push:
branches:
- main
pull_request:
workflow_dispatch:
inputs:
configure-nodejs-ref:
description: Ref in pwrdrvr/configure-nodejs to validate
default: main
required: false
type: string

env:
CONFIGURE_NODEJS_REF: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.configure-nodejs-ref || 'main' }}

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

- name: Checkout configure-nodejs
uses: actions/checkout@v6
with:
repository: pwrdrvr/configure-nodejs
ref: ${{ env.CONFIGURE_NODEJS_REF }}
path: configure-nodejs

- uses: actions/setup-node@v6
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

- name: Checkout configure-nodejs
uses: actions/checkout@v6
with:
repository: pwrdrvr/configure-nodejs
ref: ${{ env.CONFIGURE_NODEJS_REF }}
path: configure-nodejs

- name: Configure Node.js
id: configure-nodejs
uses: ./configure-nodejs
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

- name: Checkout configure-nodejs
uses: actions/checkout@v6
with:
repository: pwrdrvr/configure-nodejs
ref: ${{ env.CONFIGURE_NODEJS_REF }}
path: configure-nodejs

- name: Configure Node.js
id: configure-nodejs
uses: ./configure-nodejs
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
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/
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# configure-nodejs-test

Dogfood and validation harness for [`pwrdrvr/configure-nodejs`](https://github.com/pwrdrvr/configure-nodejs).

The workflow in this repository checks out `pwrdrvr/configure-nodejs` at a configurable ref, runs unit tests against its helper scripts, and exercises npm, pnpm, and Yarn fixtures through the action entrypoint.
7 changes: 7 additions & 0 deletions fixtures/npm-basic/check.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import pc from 'picocolors';

if (typeof pc.green !== 'function') {
throw new Error('Expected picocolors.green to be available after npm install.');
}

console.log(pc.green('npm fixture dependency loaded'));
19 changes: 19 additions & 0 deletions fixtures/npm-basic/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions fixtures/npm-basic/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "npm-basic-fixture",
"private": true,
"type": "module",
"dependencies": {
"picocolors": "1.1.1"
}
}
7 changes: 7 additions & 0 deletions fixtures/pnpm-basic/check.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import pc from 'picocolors';

if (typeof pc.green !== 'function') {
throw new Error('Expected picocolors.green to be available after pnpm install.');
}

console.log(pc.green('pnpm fixture dependency loaded'));
9 changes: 9 additions & 0 deletions fixtures/pnpm-basic/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "pnpm-basic-fixture",
"private": true,
"type": "module",
"packageManager": "pnpm@10.12.1",
"dependencies": {
"picocolors": "1.1.1"
}
}
22 changes: 22 additions & 0 deletions fixtures/pnpm-basic/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions fixtures/yarn-basic/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
7 changes: 7 additions & 0 deletions fixtures/yarn-basic/check.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import pc from 'picocolors';

if (typeof pc.green !== 'function') {
throw new Error('Expected picocolors.green to be available after yarn install.');
}

console.log(pc.green('yarn fixture dependency loaded'));
9 changes: 9 additions & 0 deletions fixtures/yarn-basic/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "yarn-basic-fixture",
"private": true,
"type": "module",
"packageManager": "yarn@4.6.0",
"dependencies": {
"picocolors": "1.1.1"
}
}
21 changes: 21 additions & 0 deletions fixtures/yarn-basic/yarn.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!

__metadata:
version: 8
cacheKey: 10c0

"picocolors@npm:1.1.1":
version: 1.1.1
resolution: "picocolors@npm:1.1.1"
checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58
languageName: node
linkType: hard

"yarn-basic-fixture@workspace:.":
version: 0.0.0-use.local
resolution: "yarn-basic-fixture@workspace:."
dependencies:
picocolors: "npm:1.1.1"
languageName: unknown
linkType: soft
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "@pwrdrvr/configure-nodejs-test",
"private": true,
"type": "module",
"scripts": {
"test": "node --test"
}
}
109 changes: 109 additions & 0 deletions test/resolve-cache-paths.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'fs';
import os from 'os';
import path from 'path';

import {
buildCachePaths,
buildResult,
buildWorkingDirectoryKey,
normalizeCacheKeySuffix,
resolveWorkingDirectory,
} from '../configure-nodejs/scripts/resolve-cache-paths.mjs';

function withTempDir(callback) {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'configure-nodejs-paths-'));

try {
callback(tempDir);
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
}

test('resolveWorkingDirectory preserves root-relative usage', () => {
withTempDir((tempDir) => {
const result = resolveWorkingDirectory(tempDir, '.');

assert.equal(result.workingDirectory, '.');
assert.equal(result.absoluteWorkingDirectory, tempDir);
});
});

test('resolveWorkingDirectory resolves nested directories relative to the repository root', () => {
withTempDir((tempDir) => {
fs.mkdirSync(path.join(tempDir, 'fixtures', 'npm-basic'), { recursive: true });

const result = resolveWorkingDirectory(tempDir, 'fixtures/npm-basic');

assert.equal(result.workingDirectory, 'fixtures/npm-basic');
assert.match(result.absoluteWorkingDirectory, /fixtures\/npm-basic$/);
});
});

test('resolveWorkingDirectory rejects paths that escape the repository root', () => {
withTempDir((tempDir) => {
assert.throws(
() => resolveWorkingDirectory(tempDir, '../outside'),
/resolves outside the repository root/,
);
});
});

test('resolveWorkingDirectory rejects missing paths', () => {
withTempDir((tempDir) => {
assert.throws(
() => resolveWorkingDirectory(tempDir, 'missing'),
/does not exist/,
);
});
});

test('buildWorkingDirectoryKey generates a stable root key and nested slug', () => {
assert.equal(buildWorkingDirectoryKey('.'), 'root');
assert.match(
buildWorkingDirectoryKey('fixtures/yarn-basic'),
/^fixtures__yarn-basic-[a-f0-9]{8}$/,
);
});

test('normalizeCacheKeySuffix preserves safe values and normalizes separators', () => {
assert.equal(normalizeCacheKeySuffix('fixture-tests'), 'fixture-tests');
assert.equal(normalizeCacheKeySuffix(' fixture tests / ci '), 'fixture-tests-ci');
assert.equal(normalizeCacheKeySuffix(' '), '');
});

test('buildCachePaths scopes cache globs to the resolved working directory', () => {
assert.deepEqual(buildCachePaths('.'), [
'node_modules',
'**/node_modules',
'!node_modules/.cache',
'!**/node_modules/.cache',
]);

assert.deepEqual(buildCachePaths('fixtures/pnpm-basic'), [
'fixtures/pnpm-basic/node_modules',
'fixtures/pnpm-basic/**/node_modules',
'!fixtures/pnpm-basic/node_modules/.cache',
'!fixtures/pnpm-basic/**/node_modules/.cache',
]);
});

test('buildResult combines normalized working-directory and cache metadata', () => {
withTempDir((tempDir) => {
fs.mkdirSync(path.join(tempDir, 'fixtures', 'npm-basic'), { recursive: true });

const result = buildResult({
cwd: tempDir,
workingDirectory: 'fixtures/npm-basic',
cacheKeySuffix: 'fixture tests',
});

assert.equal(result.workingDirectory, 'fixtures/npm-basic');
assert.match(result.workingDirectoryKey, /^fixtures__npm-basic-[a-f0-9]{8}$/);
assert.equal(result.cachePaths[0], 'fixtures/npm-basic/node_modules');
assert.equal(result.cacheKeySuffix, 'fixture-tests');
assert.equal(result.cacheKeySuffixSegment, '-fixture-tests');
});
});
Loading