Skip to content

Commit af8b571

Browse files
authored
feat: a command for installing Node.js (pnpm#3620)
The following command will install Node.js (inspired by [poetry](https://python-poetry.org/docs/managing-environments/)): ``` pnpm env use --global 16.5.0 ```
1 parent 320482c commit af8b571

File tree

22 files changed

+233
-69
lines changed

22 files changed

+233
-69
lines changed

.changeset/twelve-rice-compare.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pnpm/plugin-commands-env": patch
3+
---
4+
5+
New command added: `pnpm env use --global <version>`. This command installs the specified Node.js version globally.

.changeset/wise-needles-fix.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pnpm/config": patch
3+
---
4+
5+
pnpm should always have write access to its home directory

packages/config/src/dirs.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,21 @@ export function getStateDir (
3939
}
4040
return path.join(os.homedir(), '.pnpm-state')
4141
}
42+
43+
export function getDataDir (
44+
opts: {
45+
env: NodeJS.ProcessEnv
46+
platform: string
47+
}
48+
) {
49+
if (opts.env.XDG_DATA_HOME) {
50+
return path.join(opts.env.XDG_DATA_HOME, 'pnpm')
51+
}
52+
if (opts.platform !== 'win32' && opts.platform !== 'darwin') {
53+
return path.join(os.homedir(), '.local/share/pnpm')
54+
}
55+
if (opts.env.LOCALAPPDATA) {
56+
return path.join(opts.env.LOCALAPPDATA, 'pnpm')
57+
}
58+
return path.join(os.homedir(), '.pnpm')
59+
}

packages/config/src/index.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import realpathMissing from 'realpath-missing'
1313
import whichcb from 'which'
1414
import getScopeRegistries, { normalizeRegistry } from './getScopeRegistries'
1515
import findBestGlobalPrefix from './findBestGlobalPrefix'
16-
import { getCacheDir, getStateDir } from './dirs'
16+
import { getCacheDir, getDataDir, getStateDir } from './dirs'
1717
import {
1818
Config,
1919
ConfigWithDeprecatedSettings,
@@ -427,19 +427,7 @@ export default async (
427427
pnpmConfig.noProxy = pnpmConfig['noproxy'] ?? getProcessEnv('no_proxy')
428428
}
429429
pnpmConfig.enablePnp = pnpmConfig['nodeLinker'] === 'pnp'
430-
if (process['pkg'] != null) {
431-
// If the pnpm CLI was bundled by vercel/pkg then we cannot use the js path for npm_execpath
432-
// because in that case the js is in a virtual filesystem inside the executor.
433-
// Instead, we use the path to the exe file.
434-
pnpmConfig.pnpmExecPath = process.execPath
435-
pnpmConfig.pnpmHomeDir = path.dirname(pnpmConfig.pnpmExecPath)
436-
} else if (require.main != null) {
437-
pnpmConfig.pnpmExecPath = require.main.filename
438-
pnpmConfig.pnpmHomeDir = path.dirname(pnpmConfig.pnpmExecPath)
439-
} else {
440-
pnpmConfig.pnpmExecPath = process.cwd()
441-
pnpmConfig.pnpmHomeDir = process.cwd()
442-
}
430+
pnpmConfig.pnpmHomeDir = getDataDir(process)
443431

444432
if (opts.checkUnknownSetting) {
445433
const settingKeys = Object.keys({

packages/config/test/dirs.test.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os from 'os'
22
import path from 'path'
3-
import { getCacheDir, getStateDir } from '../lib/dirs'
3+
import { getCacheDir, getDataDir, getStateDir } from '../lib/dirs'
44

55
test('getCacheDir()', () => {
66
expect(getCacheDir({
@@ -55,3 +55,30 @@ test('getStateDir()', () => {
5555
platform: 'win32',
5656
})).toBe(path.join(os.homedir(), '.pnpm-state'))
5757
})
58+
59+
test('getDataDir()', () => {
60+
expect(getDataDir({
61+
env: {
62+
XDG_DATA_HOME: '/home/foo/data',
63+
},
64+
platform: 'linux',
65+
})).toBe(path.join('/home/foo/data', 'pnpm'))
66+
expect(getDataDir({
67+
env: {},
68+
platform: 'linux',
69+
})).toBe(path.join(os.homedir(), '.local/share/pnpm'))
70+
expect(getDataDir({
71+
env: {},
72+
platform: 'darwin',
73+
})).toBe(path.join(os.homedir(), '.pnpm'))
74+
expect(getDataDir({
75+
env: {
76+
LOCALAPPDATA: '/localappdata',
77+
},
78+
platform: 'win32',
79+
})).toBe(path.join('/localappdata', 'pnpm'))
80+
expect(getDataDir({
81+
env: {},
82+
platform: 'win32',
83+
})).toBe(path.join(os.homedir(), '.pnpm'))
84+
})
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# @pnpm/plugin-commands-nvm
1+
# @pnpm/plugin-commands-env
22

33
## 0.2.8
44

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
# @pnpm/plugin-commands-nvm
1+
# @pnpm/plugin-commands-env
22

33
> pnpm commands for managing Node.js
44
5-
[![npm version](https://img.shields.io/npm/v/@pnpm/plugin-commands-nvm.svg)](https://www.npmjs.com/package/@pnpm/plugin-commands-nvm)
5+
[![npm version](https://img.shields.io/npm/v/@pnpm/plugin-commands-env.svg)](https://www.npmjs.com/package/@pnpm/plugin-commands-env)
66

77
## Installation
88

99
```sh
10-
<pnpm|npm|yarn> add @pnpm/plugin-commands-nvm
10+
<pnpm|npm|yarn> add @pnpm/plugin-commands-env
1111
```
1212

1313
## License
File renamed without changes.
Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "@pnpm/plugin-commands-nvm",
2+
"name": "@pnpm/plugin-commands-env",
33
"version": "0.2.8",
44
"description": "pnpm commands for managing Node.js",
55
"main": "lib/index.js",
@@ -18,31 +18,37 @@
1818
"prepublishOnly": "pnpm run compile",
1919
"compile": "rimraf lib tsconfig.tsbuildinfo && tsc --build && pnpm run lint -- --fix"
2020
},
21-
"repository": "https://github.com/pnpm/pnpm/blob/master/packages/plugin-commands-nvm",
21+
"repository": "https://github.com/pnpm/pnpm/blob/master/packages/plugin-commands-env",
2222
"keywords": [
2323
"pnpm",
24-
"nvm"
24+
"env"
2525
],
2626
"license": "MIT",
2727
"bugs": {
2828
"url": "https://github.com/pnpm/pnpm/issues"
2929
},
30-
"homepage": "https://github.com/pnpm/pnpm/blob/master/packages/plugin-commands-nvm#readme",
30+
"homepage": "https://github.com/pnpm/pnpm/blob/master/packages/plugin-commands-env#readme",
3131
"dependencies": {
32+
"@pnpm/cli-utils": "workspace:0.6.13",
3233
"@pnpm/config": "workspace:12.4.3",
34+
"@pnpm/error": "workspace:2.0.0",
3335
"@pnpm/fetch": "workspace:4.0.2",
3436
"@pnpm/package-store": "workspace:12.0.12",
3537
"@pnpm/store-path": "^5.0.0",
3638
"@pnpm/tarball-fetcher": "workspace:9.3.4",
39+
"@zkochan/cmd-shim": "^5.1.3",
3740
"adm-zip": "^0.5.5",
3841
"load-json-file": "^6.2.0",
3942
"rename-overwrite": "^4.0.0",
43+
"render-help": "^1.0.1",
4044
"tempy": "^1.0.0",
4145
"write-json-file": "^4.3.0"
4246
},
4347
"funding": "https://opencollective.com/pnpm",
4448
"devDependencies": {
4549
"@pnpm/prepare": "workspace:0.0.26",
46-
"@types/adm-zip": "^0.4.34"
50+
"@types/adm-zip": "^0.4.34",
51+
"execa": "^5.0.0",
52+
"path-name": "^1.0.0"
4753
}
4854
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import path from 'path'
2+
import { docsUrl } from '@pnpm/cli-utils'
3+
import PnpmError from '@pnpm/error'
4+
import cmdShim from '@zkochan/cmd-shim'
5+
import renderHelp from 'render-help'
6+
import { getNodeDir, NvmNodeCommandOptions } from './node'
7+
8+
export function rcOptionsTypes () {
9+
return {}
10+
}
11+
12+
export function cliOptionsTypes () {
13+
return {
14+
global: Boolean,
15+
}
16+
}
17+
18+
export const commandNames = ['env']
19+
20+
export function help () {
21+
return renderHelp({
22+
description: 'Install and use the specified version of Node.js',
23+
descriptionLists: [
24+
{
25+
title: 'Options',
26+
27+
list: [
28+
{
29+
description: 'Installs Node.js globally',
30+
name: '--global',
31+
shortAlias: '-g',
32+
},
33+
],
34+
},
35+
],
36+
url: docsUrl('env'),
37+
usages: [
38+
'pnpm env use --global <version>',
39+
],
40+
})
41+
}
42+
43+
export async function handler (opts: NvmNodeCommandOptions, params: string[]) {
44+
if (params.length === 0) {
45+
throw new PnpmError('ENV_NO_SUBCOMMAND', 'Please specify the subcommand')
46+
}
47+
switch (params[0]) {
48+
case 'use': {
49+
if (!opts.global) {
50+
throw new PnpmError('NOT_IMPLEMENTED_YET', '"pnpm env use <version>" can only be used with the "--global" option currently')
51+
}
52+
const nodeDir = await getNodeDir({
53+
...opts,
54+
useNodeVersion: params[1],
55+
})
56+
const src = path.join(nodeDir, process.platform === 'win32' ? 'node.exe' : 'node')
57+
const dest = path.join(opts.bin, 'node')
58+
await cmdShim(src, dest)
59+
return `Node.js ${params[1]} is activated
60+
${dest} -> ${src}`
61+
}
62+
default: {
63+
throw new PnpmError('ENV_UNKNOWN_SUBCOMMAND', 'This subcommand is not known')
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)