├── .gitattributes ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ └── test.yaml ├── .gitignore ├── renovate.json ├── run.sh ├── .editorconfig ├── src ├── outputs │ └── index.ts ├── utils │ └── index.ts ├── install-pnpm │ ├── index.ts │ └── run.ts ├── index.ts ├── pnpm-store-prune │ └── index.ts ├── inputs │ ├── index.ts │ └── run-install.ts └── pnpm-install │ └── index.ts ├── tsconfig.json ├── package.json ├── action.yml ├── LICENSE.md ├── README.md └── pnpm-lock.yaml /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | /dist/index.js binary 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: 2 | - https://opencollective.com/pnpm 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | /dist/* 4 | !/dist/index.js 5 | !/dist/pnpm.cjs 6 | !/dist/worker.js 7 | tmp 8 | temp 9 | *.tmp 10 | *.temp 11 | tmp.* 12 | temp.* 13 | .pnpm-store 14 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "pinVersions": false, 4 | "extends": [ 5 | "config:base" 6 | ], 7 | "ignoreDeps": [ 8 | "ajv" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # shellcheck disable=SC2155,SC2088 3 | export HOME="$(pwd)" 4 | export INPUT_VERSION=4.11.1 5 | export INPUT_DEST='~/pnpm.temp' 6 | export INPUT_RUN_INSTALL=null 7 | export INPUT_standalone=false 8 | exec node dist/index.js 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | end_of_line = lf 3 | insert_final_newline = true 4 | trim_trailing_whitespace = true 5 | 6 | [*.js,*.ts] 7 | indent_style = space 8 | indent_size = 2 9 | 10 | [Makefile] 11 | indent_style = tab 12 | tab_width = 4 13 | 14 | [*.gitattributes] 15 | indent_style = tab 16 | tab_width = 16 17 | -------------------------------------------------------------------------------- /src/outputs/index.ts: -------------------------------------------------------------------------------- 1 | import { setOutput, addPath } from '@actions/core' 2 | import { Inputs } from '../inputs' 3 | import { getBinDest } from '../utils' 4 | 5 | export function setOutputs(inputs: Inputs) { 6 | const binDest = getBinDest(inputs) 7 | addPath(binDest) 8 | setOutput('dest', inputs.dest) 9 | setOutput('bin_dest', binDest) 10 | } 11 | 12 | export default setOutputs 13 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import process from 'process' 3 | import { Inputs } from '../inputs' 4 | 5 | export const getBinDest = (inputs: Inputs): string => path.join(inputs.dest, 'node_modules', '.bin') 6 | 7 | export const patchPnpmEnv = (inputs: Inputs): NodeJS.ProcessEnv => ({ 8 | ...process.env, 9 | PATH: getBinDest(inputs) + path.delimiter + process.env.PATH, 10 | }) 11 | -------------------------------------------------------------------------------- /src/install-pnpm/index.ts: -------------------------------------------------------------------------------- 1 | import { setFailed, startGroup, endGroup } from '@actions/core' 2 | import { Inputs } from '../inputs' 3 | import runSelfInstaller from './run' 4 | 5 | export { runSelfInstaller } 6 | 7 | export async function install(inputs: Inputs) { 8 | startGroup('Running self-installer...') 9 | const status = await runSelfInstaller(inputs) 10 | endGroup() 11 | if (status) { 12 | return setFailed(`Something went wrong, self-installer exits with code ${status}`) 13 | } 14 | } 15 | 16 | export default install 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "compilerOptions": { 4 | "target": "ES2022", 5 | "module": "Node16", 6 | "resolveJsonModule": true, 7 | "lib": [ 8 | "ES2023" 9 | ], 10 | "outDir": "./dist/tsc", 11 | "preserveConstEnums": true, 12 | "incremental": false, 13 | "declaration": true, 14 | "sourceMap": true, 15 | "importHelpers": false, 16 | "strict": true, 17 | "pretty": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noImplicitReturns": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "esModuleInterop": true, 23 | "experimentalDecorators": true, 24 | "emitDecoratorMetadata": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { setFailed, saveState, getState } from '@actions/core' 2 | import getInputs from './inputs' 3 | import installPnpm from './install-pnpm' 4 | import setOutputs from './outputs' 5 | import pnpmInstall from './pnpm-install' 6 | import pruneStore from './pnpm-store-prune' 7 | 8 | async function main() { 9 | const inputs = getInputs() 10 | const isPost = getState('is_post') 11 | if (isPost === 'true') return pruneStore(inputs) 12 | saveState('is_post', 'true') 13 | await installPnpm(inputs) 14 | console.log('Installation Completed!') 15 | setOutputs(inputs) 16 | pnpmInstall(inputs) 17 | } 18 | 19 | main().catch(error => { 20 | console.error(error) 21 | setFailed(error) 22 | }) 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "build:ncc": "ncc build --minify --no-source-map-register --no-cache dist/tsc/index.js --out dist/", 5 | "build": "tsc && pnpm run build:ncc", 6 | "start": "pnpm run build && sh ./run.sh", 7 | "update-pnpm-dist": "pnpm install && cp ./node_modules/pnpm/dist/pnpm.cjs ./dist/pnpm.cjs && cp ./node_modules/pnpm/dist/worker.js ./dist/worker.js" 8 | }, 9 | "dependencies": { 10 | "@actions/core": "^1.10.1", 11 | "@types/expand-tilde": "^2.0.2", 12 | "@types/node": "^20.11.5", 13 | "@types/node-fetch": "^2.6.11", 14 | "expand-tilde": "^2.0.2", 15 | "yaml": "^2.3.4", 16 | "zod": "^3.22.4" 17 | }, 18 | "devDependencies": { 19 | "@vercel/ncc": "^0.38.1", 20 | "pnpm": "^8.14.3", 21 | "typescript": "^5.3.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/pnpm-store-prune/index.ts: -------------------------------------------------------------------------------- 1 | import { warning, startGroup, endGroup } from '@actions/core' 2 | import { spawnSync } from 'child_process' 3 | import { Inputs } from '../inputs' 4 | import { patchPnpmEnv } from '../utils' 5 | 6 | export function pruneStore(inputs: Inputs) { 7 | if (inputs.runInstall.length === 0) { 8 | console.log('Pruning is unnecessary.') 9 | return 10 | } 11 | 12 | startGroup('Running pnpm store prune...') 13 | const { error, status } = spawnSync('pnpm', ['store', 'prune'], { 14 | stdio: 'inherit', 15 | shell: true, 16 | env: patchPnpmEnv(inputs), 17 | }) 18 | endGroup() 19 | 20 | if (error) { 21 | warning(error) 22 | return 23 | } 24 | 25 | if (status) { 26 | warning(`command pnpm store prune exits with code ${status}`) 27 | return 28 | } 29 | } 30 | 31 | export default pruneStore 32 | -------------------------------------------------------------------------------- /src/inputs/index.ts: -------------------------------------------------------------------------------- 1 | import { getBooleanInput, getInput, InputOptions } from '@actions/core' 2 | import expandTilde from 'expand-tilde' 3 | import { RunInstall, parseRunInstall } from './run-install' 4 | 5 | export interface Inputs { 6 | readonly version?: string 7 | readonly dest: string 8 | readonly runInstall: RunInstall[] 9 | readonly packageJsonFile: string 10 | readonly standalone: boolean 11 | } 12 | 13 | const options: InputOptions = { 14 | required: true, 15 | } 16 | 17 | const parseInputPath = (name: string) => expandTilde(getInput(name, options)) 18 | 19 | export const getInputs = (): Inputs => ({ 20 | version: getInput('version'), 21 | dest: parseInputPath('dest'), 22 | runInstall: parseRunInstall('run_install'), 23 | packageJsonFile: parseInputPath('package_json_file'), 24 | standalone: getBooleanInput('standalone'), 25 | }) 26 | 27 | export default getInputs 28 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: Setup pnpm 2 | description: Install pnpm package manager 3 | branding: 4 | icon: package 5 | color: orange 6 | inputs: 7 | version: 8 | description: Version of pnpm to install 9 | required: false 10 | dest: 11 | description: Where to store pnpm files 12 | required: false 13 | default: ~/setup-pnpm 14 | run_install: 15 | description: If specified, run `pnpm install` 16 | required: false 17 | default: 'null' 18 | package_json_file: 19 | description: File path to the package.json to read "packageManager" configuration 20 | required: false 21 | default: 'package.json' 22 | standalone: 23 | description: When set to true, @pnpm/exe, which is a Node.js bundled package, will be installed, enabling using pnpm without Node.js. 24 | required: false 25 | default: 'false' 26 | outputs: 27 | dest: 28 | description: Expanded path of inputs#dest 29 | bin_dest: 30 | description: Location of `pnpm` and `pnpx` command 31 | runs: 32 | using: node20 33 | main: dist/index.js 34 | post: dist/index.js 35 | -------------------------------------------------------------------------------- /src/pnpm-install/index.ts: -------------------------------------------------------------------------------- 1 | import { setFailed, startGroup, endGroup } from '@actions/core' 2 | import { spawnSync } from 'child_process' 3 | import { Inputs } from '../inputs' 4 | import { patchPnpmEnv } from '../utils' 5 | 6 | export function runPnpmInstall(inputs: Inputs) { 7 | const env = patchPnpmEnv(inputs) 8 | 9 | for (const options of inputs.runInstall) { 10 | const args = ['install'] 11 | if (options.recursive) args.unshift('recursive') 12 | if (options.args) args.push(...options.args) 13 | 14 | const cmdStr = ['pnpm', ...args].join(' ') 15 | startGroup(`Running ${cmdStr}...`) 16 | 17 | const { error, status } = spawnSync('pnpm', args, { 18 | stdio: 'inherit', 19 | cwd: options.cwd, 20 | shell: true, 21 | env, 22 | }) 23 | 24 | endGroup() 25 | 26 | if (error) { 27 | setFailed(error) 28 | continue 29 | } 30 | 31 | if (status) { 32 | setFailed(`Command ${cmdStr} (cwd: ${options.cwd}) exits with status ${status}`) 33 | continue 34 | } 35 | } 36 | } 37 | 38 | export default runPnpmInstall 39 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | 3 | Copyright © 2020 Hoàng Văn Khải 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/inputs/run-install.ts: -------------------------------------------------------------------------------- 1 | import { getInput, error } from '@actions/core' 2 | import * as yaml from 'yaml' 3 | import { z, ZodError } from 'zod' 4 | 5 | const RunInstallSchema = z.object({ 6 | recursive: z.boolean().optional(), 7 | cwd: z.string().optional(), 8 | args: z.array(z.string()).optional(), 9 | }) 10 | 11 | const RunInstallInputSchema = z.union([ 12 | z.null(), 13 | z.boolean(), 14 | RunInstallSchema, 15 | z.array(RunInstallSchema), 16 | ]) 17 | 18 | export type RunInstallInput = z.infer 19 | export type RunInstall = z.infer 20 | 21 | export function parseRunInstall(inputName: string): RunInstall[] { 22 | const input = getInput(inputName, { required: true }) 23 | const parsedInput: unknown = yaml.parse(input) 24 | 25 | try { 26 | const result: RunInstallInput = RunInstallInputSchema.parse(parsedInput) 27 | if (!result) return [] 28 | if (result === true) return [{ recursive: true }] 29 | if (Array.isArray(result)) return result 30 | return [result] 31 | } catch (exception: unknown) { 32 | error(`Error for input "${inputName}" = ${input}`) 33 | 34 | if (exception instanceof ZodError) { 35 | error(`Errors: ${exception.errors}`) 36 | } else { 37 | error(`Exception: ${exception}`) 38 | } 39 | process.exit(1) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/install-pnpm/run.ts: -------------------------------------------------------------------------------- 1 | import { addPath, exportVariable } from '@actions/core' 2 | import { spawn } from 'child_process' 3 | import { rm, writeFile, mkdir } from 'fs/promises' 4 | import { readFileSync } from 'fs' 5 | import path from 'path' 6 | import { execPath } from 'process' 7 | import util from 'util' 8 | import { Inputs } from '../inputs' 9 | 10 | export async function runSelfInstaller(inputs: Inputs): Promise { 11 | const { version, dest, packageJsonFile, standalone } = inputs 12 | 13 | // prepare self install 14 | await rm(dest, { recursive: true, force: true }) 15 | // create dest directory after removal 16 | await mkdir(dest, { recursive: true }) 17 | const pkgJson = path.join(dest, 'package.json') 18 | // we have ensured the dest directory exists, we can write the file directly 19 | await writeFile(pkgJson, JSON.stringify({ private: true })) 20 | 21 | // prepare target pnpm 22 | const target = await readTarget({ version, packageJsonFile, standalone }) 23 | const cp = spawn(execPath, [path.join(__dirname, 'pnpm.cjs'), 'install', target, '--no-lockfile'], { 24 | cwd: dest, 25 | stdio: ['pipe', 'inherit', 'inherit'], 26 | }) 27 | 28 | const exitCode = await new Promise((resolve, reject) => { 29 | cp.on('error', reject) 30 | cp.on('close', resolve) 31 | }) 32 | if (exitCode === 0) { 33 | const pnpmHome = path.join(dest, 'node_modules/.bin') 34 | addPath(pnpmHome) 35 | exportVariable('PNPM_HOME', pnpmHome) 36 | } 37 | return exitCode 38 | } 39 | 40 | async function readTarget(opts: { 41 | readonly version?: string | undefined 42 | readonly packageJsonFile: string 43 | readonly standalone: boolean 44 | }) { 45 | const { version, packageJsonFile, standalone } = opts 46 | const { GITHUB_WORKSPACE } = process.env 47 | 48 | let packageManager 49 | 50 | if (GITHUB_WORKSPACE) { 51 | try { 52 | ({ packageManager } = JSON.parse(readFileSync(path.join(GITHUB_WORKSPACE, packageJsonFile), 'utf8'))) 53 | } catch (error: unknown) { 54 | // Swallow error if package.json doesn't exist in root 55 | if (!util.types.isNativeError(error) || !('code' in error) || error.code !== 'ENOENT') throw error 56 | } 57 | } 58 | 59 | if (version) { 60 | if ( 61 | typeof packageManager === 'string' && 62 | packageManager.replace('pnpm@', '') !== version 63 | ) { 64 | throw new Error(`Multiple versions of pnpm specified: 65 | - version ${version} in the GitHub Action config with the key "version" 66 | - version ${packageManager} in the package.json with the key "packageManager" 67 | Remove one of these versions to avoid version mismatch errors like ERR_PNPM_BAD_PM_VERSION`) 68 | } 69 | 70 | return `${ standalone ? '@pnpm/exe' : 'pnpm' }@${version}` 71 | } 72 | 73 | if (!GITHUB_WORKSPACE) { 74 | throw new Error(`No workspace is found. 75 | If you've intended to let pnpm/action-setup read preferred pnpm version from the "packageManager" field in the package.json file, 76 | please run the actions/checkout before pnpm/action-setup. 77 | Otherwise, please specify the pnpm version in the action configuration.`) 78 | } 79 | 80 | if (typeof packageManager !== 'string') { 81 | throw new Error(`No pnpm version is specified. 82 | Please specify it by one of the following ways: 83 | - in the GitHub Action config with the key "version" 84 | - in the package.json with the key "packageManager"`) 85 | } 86 | 87 | if (!packageManager.startsWith('pnpm@')) { 88 | throw new Error('Invalid packageManager field in package.json') 89 | } 90 | 91 | if (standalone) { 92 | return packageManager.replace('pnpm@', '@pnpm/exe@') 93 | } 94 | 95 | return packageManager 96 | } 97 | 98 | export default runSelfInstaller 99 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test Action 2 | 3 | on: 4 | - push 5 | - pull_request 6 | - workflow_dispatch 7 | 8 | jobs: 9 | test_default_inputs: 10 | name: Test with default inputs 11 | 12 | runs-on: ${{ matrix.os }} 13 | 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | pnpm: 18 | - 4.11.1 19 | os: 20 | - ubuntu-latest 21 | - macos-latest 22 | - windows-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | 27 | - name: Run the action 28 | uses: ./ 29 | with: 30 | version: 4.11.1 31 | 32 | - name: 'Test: which' 33 | run: which pnpm; which pnpx 34 | 35 | - name: 'Test: install' 36 | run: pnpm install 37 | 38 | test_dest: 39 | name: Test with dest 40 | 41 | runs-on: ${{ matrix.os }} 42 | 43 | strategy: 44 | fail-fast: false 45 | matrix: 46 | pnpm: 47 | - 4.11.1 48 | os: 49 | - ubuntu-latest 50 | - macos-latest 51 | - windows-latest 52 | 53 | steps: 54 | - uses: actions/checkout@v4 55 | 56 | - name: Run the action 57 | uses: ./ 58 | with: 59 | version: 4.11.1 60 | dest: ~/test/pnpm 61 | 62 | - name: 'Test: which' 63 | run: which pnpm && which pnpx 64 | 65 | - name: 'Test: install' 66 | run: pnpm install 67 | 68 | test_standalone: 69 | name: Test with standalone 70 | 71 | runs-on: ${{ matrix.os }} 72 | 73 | strategy: 74 | fail-fast: false 75 | matrix: 76 | os: 77 | - ubuntu-latest 78 | - macos-latest 79 | - windows-latest 80 | 81 | standalone: 82 | - true 83 | - false 84 | 85 | steps: 86 | - uses: actions/checkout@v4 87 | 88 | - name: Run the action 89 | uses: ./ 90 | with: 91 | version: 7.0.0 92 | standalone: ${{ matrix.standalone }} 93 | 94 | - name: install Node.js 95 | uses: actions/setup-node@v4 96 | with: 97 | # pnpm@7.0.0 is not compatible with Node.js 12 98 | node-version: 12.22.12 99 | 100 | - name: 'Test: which (pnpm)' 101 | run: which pnpm 102 | 103 | - name: 'Test: which (pnpx)' 104 | if: matrix.standalone == false 105 | run: which pnpx 106 | 107 | - name: 'Test: install when standalone is true' 108 | if: matrix.standalone 109 | run: pnpm install 110 | 111 | - name: 'Test: install when standalone is false' 112 | if: matrix.standalone == false 113 | # Since the default shell on windows runner is pwsh, we specify bash explicitly 114 | shell: bash 115 | run: | 116 | if pnpm install; then 117 | echo "pnpm install should fail" 118 | exit 1 119 | else 120 | echo "pnpm install failed as expected" 121 | fi 122 | 123 | test_run_install: 124 | name: 'Test with run_install (${{ matrix.run_install.name }}, ${{ matrix.os }})' 125 | 126 | runs-on: ${{ matrix.os }} 127 | 128 | strategy: 129 | fail-fast: false 130 | matrix: 131 | pnpm: 132 | - 4.11.1 133 | os: 134 | - ubuntu-latest 135 | - macos-latest 136 | - windows-latest 137 | run_install: 138 | - name: 'null' 139 | value: 'null' 140 | - name: 'empty object' 141 | value: '{}' 142 | - name: 'recursive' 143 | value: | 144 | recursive: true 145 | - name: 'global' 146 | value: | 147 | args: 148 | - --global 149 | - --global-dir=./pnpm-global 150 | - npm 151 | - yarn 152 | - pnpm 153 | - name: 'array' 154 | value: | 155 | - {} 156 | - recursive: true 157 | - args: 158 | - --global 159 | - --global-dir=./pnpm-global 160 | - npm 161 | - yarn 162 | - pnpm 163 | 164 | steps: 165 | - uses: actions/checkout@v4 166 | 167 | - name: Run the action 168 | uses: ./ 169 | with: 170 | version: 4.11.1 171 | run_install: ${{ matrix.run_install.value }} 172 | 173 | - name: 'Test: which' 174 | run: which pnpm; which pnpx 175 | 176 | - name: 'Test: install' 177 | run: pnpm install 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > ## :warning: Upgrade from v2! 2 | > 3 | > The v2 version of this action [has stopped working](https://github.com/pnpm/action-setup/issues/135) with newer Node.js versions. Please, upgrade to the latest version to fix any issues. 4 | 5 | # Setup pnpm 6 | 7 | Install pnpm package manager. 8 | 9 | ## Inputs 10 | 11 | ### `version` 12 | 13 | Version of pnpm to install. 14 | 15 | **Optional** when there is a [`packageManager` field in the `package.json`](https://nodejs.org/api/corepack.html). 16 | 17 | 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`. 18 | 19 | ### `dest` 20 | 21 | **Optional** Where to store pnpm files. 22 | 23 | ### `run_install` 24 | 25 | **Optional** (_default:_ `null`) If specified, run `pnpm install`. 26 | 27 | If `run_install` is either `null` or `false`, pnpm will not install any npm package. 28 | 29 | If `run_install` is `true`, pnpm will install dependencies recursively. 30 | 31 | If `run_install` is a YAML string representation of either an object or an array, pnpm will execute every install commands. 32 | 33 | #### `run_install.recursive` 34 | 35 | **Optional** (_type:_ `boolean`, _default:_ `false`) Whether to use `pnpm recursive install`. 36 | 37 | #### `run_install.cwd` 38 | 39 | **Optional** (_type:_ `string`) Working directory when run `pnpm [recursive] install`. 40 | 41 | #### `run_install.args` 42 | 43 | **Optional** (_type:_ `string[]`) Additional arguments after `pnpm [recursive] install`, e.g. `[--frozen-lockfile, --strict-peer-dependencies]`. 44 | 45 | ### `package_json_file` 46 | 47 | **Optional** (_type:_ `string`, _default:_ `package.json`) File path to the `package.json` to read "packageManager" configuration. 48 | 49 | ### `standalone` 50 | 51 | **Optional** (_type:_ `boolean`, _default:_ `false`) When set to true, [@pnpm/exe](https://www.npmjs.com/package/@pnpm/exe), which is a Node.js bundled package, will be installed, enabling using `pnpm` without Node.js. 52 | 53 | This is useful when you want to use a incompatible pair of Node.js and pnpm. 54 | 55 | ## Outputs 56 | 57 | ### `dest` 58 | 59 | Expanded path of inputs#dest. 60 | 61 | ### `bin_dest` 62 | 63 | Location of `pnpm` and `pnpx` command. 64 | 65 | ## Usage example 66 | 67 | ### Install only pnpm without `packageManager` 68 | 69 | This works when the repo either doesn't have a `package.json` or has a `package.json` but it doesn't specify `packageManager`. 70 | 71 | ```yaml 72 | on: 73 | - push 74 | - pull_request 75 | 76 | jobs: 77 | install: 78 | runs-on: ubuntu-latest 79 | 80 | steps: 81 | - uses: pnpm/action-setup@v4 82 | with: 83 | version: 8 84 | ``` 85 | 86 | ### Install only pnpm with `packageManager` 87 | 88 | Omit `version` input to use the version in the [`packageManager` field in the `package.json`](https://nodejs.org/api/corepack.html). 89 | 90 | ```yaml 91 | on: 92 | - push 93 | - pull_request 94 | 95 | jobs: 96 | install: 97 | runs-on: ubuntu-latest 98 | 99 | steps: 100 | - uses: pnpm/action-setup@v4 101 | ``` 102 | 103 | ### Install pnpm and a few npm packages 104 | 105 | ```yaml 106 | on: 107 | - push 108 | - pull_request 109 | 110 | jobs: 111 | install: 112 | runs-on: ubuntu-latest 113 | 114 | steps: 115 | - uses: actions/checkout@v4 116 | 117 | - uses: pnpm/action-setup@v4 118 | with: 119 | version: 8 120 | run_install: | 121 | - recursive: true 122 | args: [--frozen-lockfile, --strict-peer-dependencies] 123 | - args: [--global, gulp, prettier, typescript] 124 | ``` 125 | 126 | ### Use cache to reduce installation time 127 | 128 | ```yaml 129 | on: 130 | - push 131 | - pull_request 132 | 133 | jobs: 134 | cache-and-install: 135 | runs-on: ubuntu-latest 136 | 137 | steps: 138 | - name: Checkout 139 | uses: actions/checkout@v4 140 | 141 | - uses: pnpm/action-setup@v4 142 | name: Install pnpm 143 | with: 144 | version: 8 145 | run_install: false 146 | 147 | - name: Install Node.js 148 | uses: actions/setup-node@v4 149 | with: 150 | node-version: 20 151 | cache: 'pnpm' 152 | 153 | - name: Install dependencies 154 | run: pnpm install 155 | ``` 156 | 157 | **Note:** You don't need to run `pnpm store prune` at the end; post-action has already taken care of that. 158 | 159 | ## Notes 160 | 161 | This action does not setup Node.js for you, use [actions/setup-node](https://github.com/actions/setup-node) yourself. 162 | 163 | ## License 164 | 165 | [MIT](https://github.com/pnpm/action-setup/blob/master/LICENSE.md) © [Hoàng Văn Khải](https://github.com/KSXGitHub/) 166 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | dependencies: 8 | '@actions/core': 9 | specifier: ^1.10.1 10 | version: 1.10.1 11 | '@types/expand-tilde': 12 | specifier: ^2.0.2 13 | version: 2.0.2 14 | '@types/node': 15 | specifier: ^20.11.5 16 | version: 20.11.5 17 | '@types/node-fetch': 18 | specifier: ^2.6.11 19 | version: 2.6.11 20 | expand-tilde: 21 | specifier: ^2.0.2 22 | version: 2.0.2 23 | yaml: 24 | specifier: ^2.3.4 25 | version: 2.3.4 26 | zod: 27 | specifier: ^3.22.4 28 | version: 3.22.4 29 | 30 | devDependencies: 31 | '@vercel/ncc': 32 | specifier: ^0.38.1 33 | version: 0.38.1 34 | pnpm: 35 | specifier: ^8.14.3 36 | version: 8.14.3 37 | typescript: 38 | specifier: ^5.3.3 39 | version: 5.3.3 40 | 41 | packages: 42 | 43 | /@actions/core@1.10.1: 44 | resolution: {integrity: sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==} 45 | dependencies: 46 | '@actions/http-client': 2.2.0 47 | uuid: 8.3.2 48 | dev: false 49 | 50 | /@actions/http-client@2.2.0: 51 | resolution: {integrity: sha512-q+epW0trjVUUHboliPb4UF9g2msf+w61b32tAkFEwL/IwP0DQWgbCMM0Hbe3e3WXSKz5VcUXbzJQgy8Hkra/Lg==} 52 | dependencies: 53 | tunnel: 0.0.6 54 | undici: 5.28.3 55 | dev: false 56 | 57 | /@fastify/busboy@2.1.0: 58 | resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==} 59 | engines: {node: '>=14'} 60 | dev: false 61 | 62 | /@types/expand-tilde@2.0.2: 63 | resolution: {integrity: sha512-wlsMYiapmIR4Eq/Z0qysN8xaDMjSkO6AIDNFx9oxgWGeKVA1jU+NzwPRZErBNP5z6/dx6QNkNpKglBGPO9OkTA==} 64 | dev: false 65 | 66 | /@types/node-fetch@2.6.11: 67 | resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} 68 | dependencies: 69 | '@types/node': 20.11.5 70 | form-data: 4.0.0 71 | dev: false 72 | 73 | /@types/node@20.11.5: 74 | resolution: {integrity: sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==} 75 | dependencies: 76 | undici-types: 5.26.5 77 | dev: false 78 | 79 | /@vercel/ncc@0.38.1: 80 | resolution: {integrity: sha512-IBBb+iI2NLu4VQn3Vwldyi2QwaXt5+hTyh58ggAMoCGE6DJmPvwL3KPBWcJl1m9LYPChBLE980Jw+CS4Wokqxw==} 81 | hasBin: true 82 | dev: true 83 | 84 | /asynckit@0.4.0: 85 | resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} 86 | dev: false 87 | 88 | /combined-stream@1.0.8: 89 | resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} 90 | engines: {node: '>= 0.8'} 91 | dependencies: 92 | delayed-stream: 1.0.0 93 | dev: false 94 | 95 | /delayed-stream@1.0.0: 96 | resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} 97 | engines: {node: '>=0.4.0'} 98 | dev: false 99 | 100 | /expand-tilde@2.0.2: 101 | resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} 102 | engines: {node: '>=0.10.0'} 103 | dependencies: 104 | homedir-polyfill: 1.0.3 105 | dev: false 106 | 107 | /form-data@4.0.0: 108 | resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} 109 | engines: {node: '>= 6'} 110 | dependencies: 111 | asynckit: 0.4.0 112 | combined-stream: 1.0.8 113 | mime-types: 2.1.35 114 | dev: false 115 | 116 | /homedir-polyfill@1.0.3: 117 | resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} 118 | engines: {node: '>=0.10.0'} 119 | dependencies: 120 | parse-passwd: 1.0.0 121 | dev: false 122 | 123 | /mime-db@1.52.0: 124 | resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} 125 | engines: {node: '>= 0.6'} 126 | dev: false 127 | 128 | /mime-types@2.1.35: 129 | resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} 130 | engines: {node: '>= 0.6'} 131 | dependencies: 132 | mime-db: 1.52.0 133 | dev: false 134 | 135 | /parse-passwd@1.0.0: 136 | resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} 137 | engines: {node: '>=0.10.0'} 138 | dev: false 139 | 140 | /pnpm@8.14.3: 141 | resolution: {integrity: sha512-w+2A61g74+K37zHrlri5z6oFA+XUTscXUUEgtRh7L5M3NukDjFGlojrVgnkLpB1Kt4RhjIn6fiNl9mZWhdYS7g==} 142 | engines: {node: '>=16.14'} 143 | hasBin: true 144 | dev: true 145 | 146 | /tunnel@0.0.6: 147 | resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} 148 | engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} 149 | dev: false 150 | 151 | /typescript@5.3.3: 152 | resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} 153 | engines: {node: '>=14.17'} 154 | hasBin: true 155 | dev: true 156 | 157 | /undici-types@5.26.5: 158 | resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} 159 | dev: false 160 | 161 | /undici@5.28.3: 162 | resolution: {integrity: sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==} 163 | engines: {node: '>=14.0'} 164 | dependencies: 165 | '@fastify/busboy': 2.1.0 166 | dev: false 167 | 168 | /uuid@8.3.2: 169 | resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} 170 | hasBin: true 171 | dev: false 172 | 173 | /yaml@2.3.4: 174 | resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} 175 | engines: {node: '>= 14'} 176 | dev: false 177 | 178 | /zod@3.22.4: 179 | resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} 180 | dev: false 181 | --------------------------------------------------------------------------------