feat: add version_file_path option

Using the same logic as `actions/setup-node`, we can support `asdf`, `volta` or other version management techniques.
This commit is contained in:
Don Denton 2024-11-27 23:32:44 -05:00
parent ac5bf11548
commit 6217a9d8c4
10 changed files with 222 additions and 4 deletions

View File

@ -35,6 +35,122 @@ jobs:
- name: 'Test: install'
run: pnpm install
test_version_file_asdf:
name: Test with version file (asdf)
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
pnpm:
- 4.11.1
os:
- ubuntu-latest
- macos-latest
- windows-latest
steps:
- uses: actions/checkout@v4
- name: Run the action
uses: ./
with:
version_file_path: test/.tool-versions
- name: 'Test: which'
run: which pnpm; which pnpx
- name: 'Test: install'
run: pnpm install
test_version_file_engines:
name: Test with version file (package.json engines)
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
pnpm:
- 4.11.1
os:
- ubuntu-latest
- macos-latest
- windows-latest
steps:
- uses: actions/checkout@v4
- name: Run the action
uses: ./
with:
version_file_path: test/package.engines.json
- name: 'Test: which'
run: which pnpm; which pnpx
- name: 'Test: install'
run: pnpm install
test_version_file_volta:
name: Test with version file (volta)
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
pnpm:
- 4.11.1
os:
- ubuntu-latest
- macos-latest
- windows-latest
steps:
- uses: actions/checkout@v4
- name: Run the action
uses: ./
with:
version_file_path: test/package.volta.json
- name: 'Test: which'
run: which pnpm; which pnpx
- name: 'Test: install'
run: pnpm install
test_version_file_volta_extends:
name: Test with version file (volta extends)
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
pnpm:
- 4.11.1
os:
- ubuntu-latest
- macos-latest
- windows-latest
steps:
- uses: actions/checkout@v4
- name: Run the action
uses: ./
with:
version_file_path: test/package.volta-extends.json
- name: 'Test: which'
run: which pnpm; which pnpx
- name: 'Test: install'
run: pnpm install
test_dest:
name: Test with dest

View File

@ -16,6 +16,12 @@ Version of pnpm to install.
otherwise, this field is **required** It supports npm versioning scheme, it could be an exact version (such as `6.24.1`), or a version range (such as `6`, `6.x.x`, `6.24.x`, `^6.24.1`, `*`, etc.), or `latest`.
### `version_file_path`
The `version_file_path` input accepts a path to a file containing the version of pnpm to be used. For example `.tool-versions` (if you use `asdf`), or package.json (if you use the engines property or [`volta`](https://volta.sh) instead of corepack).
The action will search for the version file relative to the repository root.
### `dest`
**Optional** Where to store pnpm files.
@ -83,6 +89,26 @@ jobs:
version: 9
```
### Install only pnpm with a version file
This works when you use `volta`, `asdf`, etc.
```yaml
on:
- push
- pull_request
jobs:
install:
runs-on: ubuntu-latest
steps:
- uses: pnpm/action-setup@v4
with:
version_file_path: ".tool-versions" # with asdf
# version_file_path: "package.json" # with volta
```
### Install only pnpm with `packageManager`
Omit `version` input to use the version in the [`packageManager` field in the `package.json`](https://nodejs.org/api/corepack.html).

View File

@ -7,6 +7,9 @@ inputs:
version:
description: Version of pnpm to install
required: false
version_file_path:
description: "Path to a version file. Eg: '.tool-versions' for asdf or 'package.json' for volta"
required: false
dest:
description: Where to store pnpm files
required: false

BIN
dist/index.js vendored

Binary file not shown.

View File

@ -4,6 +4,7 @@ import { RunInstall, parseRunInstall } from './run-install'
export interface Inputs {
readonly version?: string
readonly versionFilePath?: string
readonly dest: string
readonly runInstall: RunInstall[]
readonly packageJsonFile: string
@ -18,6 +19,7 @@ const parseInputPath = (name: string) => expandTilde(getInput(name, options))
export const getInputs = (): Inputs => ({
version: getInput('version'),
versionFilePath: getInput('version_file_path'),
dest: parseInputPath('dest'),
runInstall: parseRunInstall('run_install'),
packageJsonFile: parseInputPath('package_json_file'),

View File

@ -1,14 +1,14 @@
import { addPath, exportVariable } from '@actions/core'
import { spawn } from 'child_process'
import { rm, writeFile, mkdir } from 'fs/promises'
import { readFileSync } from 'fs'
import { readFileSync, existsSync } from 'fs'
import path from 'path'
import { execPath } from 'process'
import util from 'util'
import { Inputs } from '../inputs'
export async function runSelfInstaller(inputs: Inputs): Promise<number> {
const { version, dest, packageJsonFile, standalone } = inputs
const { version, versionFilePath, dest, packageJsonFile, standalone } = inputs
// prepare self install
await rm(dest, { recursive: true, force: true })
@ -19,7 +19,7 @@ export async function runSelfInstaller(inputs: Inputs): Promise<number> {
await writeFile(pkgJson, JSON.stringify({ private: true }))
// prepare target pnpm
const target = await readTarget({ version, packageJsonFile, standalone })
const target = await readTarget({ version, versionFilePath, packageJsonFile, standalone })
const cp = spawn(execPath, [path.join(__dirname, 'pnpm.cjs'), 'install', target, '--no-lockfile'], {
cwd: dest,
stdio: ['pipe', 'inherit', 'inherit'],
@ -37,12 +37,71 @@ export async function runSelfInstaller(inputs: Inputs): Promise<number> {
return exitCode
}
// Nearly identical to the function `actions/setup-node` uses.
// See https://github.com/actions/setup-node/blob/39370e3970a6d050c480ffad4ff0ed4d3fdee5af/src/util.ts#L8
function getPnpmVersionFromFile(versionFilePath: string) {
if (!existsSync(versionFilePath)) {
throw new Error(
`The specified pnpm version file at: ${versionFilePath} does not exist`
)
}
const contents = readFileSync(versionFilePath, 'utf8')
// Try parsing the file as a `package.json` file.
try {
const manifest = JSON.parse(contents)
// Presume package.json file.
if (typeof manifest === 'object' && !!manifest) {
// Support Volta.
// See https://docs.volta.sh/guide/understanding#managing-your-project
if (manifest.volta?.pnpm) {
return manifest.volta.pnpm as string
}
if (manifest.engines?.pnpm) {
return manifest.engines.pnpm as string
}
// Support Volta workspaces.
// See https://docs.volta.sh/advanced/workspaces
if (manifest.volta?.extends) {
const extendedFilePath = path.resolve(
path.dirname(versionFilePath),
manifest.volta.extends
)
console.info('Resolving pnpm version from ' + extendedFilePath)
return getPnpmVersionFromFile(extendedFilePath)
}
// If contents are an object, we parsed JSON
// this can happen if pnpm-version-file is a package.json
// yet contains no volta.pnpm or engines.pnpm
//
// If pnpm-version file is _not_ JSON, control flow
// will not have reached these lines.
//
// And because we've reached here, we know the contents
// *are* JSON, so no further string parsing makes sense.
return
}
} catch {
console.info('pnpm version file is not JSON file')
}
const found = contents.match(/^(?:pnpm\s+)?v?(?<version>[^\s]+)$/m)
return found?.groups?.version ?? contents.trim()
}
async function readTarget(opts: {
readonly version?: string | undefined
readonly versionFilePath?: string | undefined
readonly packageJsonFile: string
readonly standalone: boolean
}) {
const { version, packageJsonFile, standalone } = opts
const { versionFilePath, packageJsonFile, standalone } = opts
let { version } = opts
const { GITHUB_WORKSPACE } = process.env
let packageManager
@ -56,6 +115,14 @@ async function readTarget(opts: {
}
}
if (typeof versionFilePath === "string" && typeof version === "string") {
throw new Error("Multiple version determination methods specified: 'version' and 'version_file_path'. Please specify only one.")
}
if (versionFilePath) {
version = getPnpmVersionFromFile(versionFilePath)
}
if (version) {
if (
typeof packageManager === 'string' &&

1
test/.tool-versions Normal file
View File

@ -0,0 +1 @@
pnpm 4.11.1

View File

@ -0,0 +1 @@
{"engines":{"pnpm":"4.11.1"}}

View File

@ -0,0 +1 @@
{"volta":{"extends":"./package.volta.json"}}

1
test/package.volta.json Normal file
View File

@ -0,0 +1 @@
{"volta":{"pnpm":"4.11.1"}}