Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support volta, asdf, and package.json engines for declaring version #151

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
116 changes: 116 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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).
Expand Down
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions dist/index.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/inputs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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'),
Expand Down
75 changes: 71 additions & 4 deletions src/install-pnpm/run.ts
Original file line number Diff line number Diff line change
@@ -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 })
Expand All @@ -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'],
Expand All @@ -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
Expand All @@ -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' &&
Expand Down
1 change: 1 addition & 0 deletions test/.tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pnpm 4.11.1
1 change: 1 addition & 0 deletions test/package.engines.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"engines":{"pnpm":"4.11.1"}}
1 change: 1 addition & 0 deletions test/package.volta-extends.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"volta":{"extends":"./package.volta.json"}}
1 change: 1 addition & 0 deletions test/package.volta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"volta":{"pnpm":"4.11.1"}}