Skip to content

Commit 1822dff

Browse files
authored
feat: support reading config from package.json (#3196)
`npm@11` has deprecated setting arbitrary config values (npm/cli#8153), which was the previously recommended place for users to set node-gyp related config. The new recommendation from npm is to use the `config` object in package.json for this (npm/cli#8153 (comment)). I think it makes sense to follow npm's recommendation here. When a user sets a value in `package.json#config` it will be set in the environment by npm with the prefix `npm_package_config_<KEY>`. I think it makes sense to allow prescribe that they keys be prefixed with `node_gyp_`. So as an example: **package.json** ```json { "config": { "node_gyp_devdir": "/tmp/.gyp" } } ``` Then in `node-gyp` we can read the env var `npm_package_config_node_gyp_devdir`. If a user wants to set a global value, they can set that env var on their system. Fixes #3156
1 parent 0773615 commit 1822dff

File tree

3 files changed

+73
-27
lines changed

3 files changed

+73
-27
lines changed

README.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,25 +235,44 @@ Some additional resources for Node.js native addons and writing `gyp` configurat
235235

236236
## Configuration
237237

238+
### package.json
239+
240+
Use the `config` object in your package.json with each key in the form `node_gyp_OPTION_NAME`. Any of the command
241+
options listed above can be set (dashes in option names should be replaced by underscores).
242+
243+
For example, to set `devdir` equal to `/tmp/.gyp`, your package.json would contain this:
244+
245+
```json
246+
{
247+
"config": {
248+
"node_gyp_devdir": "/tmp/.gyp"
249+
}
250+
}
251+
```
252+
238253
### Environment variables
239254

240-
Use the form `npm_config_OPTION_NAME` for any of the command options listed
255+
Use the form `npm_package_config_node_gyp_OPTION_NAME` for any of the command options listed
241256
above (dashes in option names should be replaced by underscores).
242257

243258
For example, to set `devdir` equal to `/tmp/.gyp`, you would:
244259

245260
Run this on Unix:
246261

247262
```bash
248-
export npm_config_devdir=/tmp/.gyp
263+
export npm_package_config_node_gyp_devdir=/tmp/.gyp
249264
```
250265

251266
Or this on Windows:
252267

253268
```console
254-
set npm_config_devdir=c:\temp\.gyp
269+
set npm_package_config_node_gyp_devdir=c:\temp\.gyp
255270
```
256271

272+
Note that in versions of npm before v11 it was possible to use the prefix `npm_config_` for
273+
environement variables. This was deprecated in npm@11 and will be removed in npm@12 so it
274+
is recommened to convert your environment variables to the above format.
275+
257276
### `npm` configuration for npm versions before v9
258277

259278
Use the form `OPTION_NAME` for any of the command options listed above.

lib/node-gyp.js

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -122,31 +122,41 @@ class Gyp extends EventEmitter {
122122
}
123123

124124
// support for inheriting config env variables from npm
125+
// npm will set environment variables in the following forms:
126+
// - `npm_config_<key>` for values from npm's own config. Setting arbitrary
127+
// options on npm's config was deprecated in npm v11 but node-gyp still
128+
// supports it for backwards compatibility.
129+
// See https://github.com/nodejs/node-gyp/issues/3156
130+
// - `npm_package_config_node_gyp_<key>` for values from the `config` object
131+
// in package.json. This is the preferred way to set options for node-gyp
132+
// since npm v11. The `node_gyp_` prefix is used to avoid conflicts with
133+
// other tools.
134+
// The `npm_package_config_node_gyp_` prefix will take precedence over
135+
// `npm_config_` keys.
125136
const npmConfigPrefix = 'npm_config_'
126-
Object.keys(process.env).forEach((name) => {
127-
if (name.indexOf(npmConfigPrefix) !== 0) {
128-
return
129-
}
130-
const val = process.env[name]
131-
if (name === npmConfigPrefix + 'loglevel') {
132-
log.logger.level = val
133-
} else {
137+
const npmPackageConfigPrefix = 'npm_package_config_node_gyp_'
138+
139+
const configEnvKeys = Object.keys(process.env)
140+
.filter((k) => k.startsWith(npmConfigPrefix) || k.startsWith(npmPackageConfigPrefix))
141+
// sort so that npm_package_config_node_gyp_ keys come last and will override
142+
.sort((a) => a.startsWith(npmConfigPrefix) ? -1 : 1)
143+
144+
for (const key of configEnvKeys) {
134145
// add the user-defined options to the config
135-
name = name.substring(npmConfigPrefix.length)
136-
// gyp@741b7f1 enters an infinite loop when it encounters
137-
// zero-length options so ensure those don't get through.
138-
if (name) {
146+
const name = key.startsWith(npmConfigPrefix)
147+
? key.substring(npmConfigPrefix.length)
148+
: key.substring(npmPackageConfigPrefix.length)
149+
// gyp@741b7f1 enters an infinite loop when it encounters
150+
// zero-length options so ensure those don't get through.
151+
if (name) {
139152
// convert names like force_process_config to force-process-config
140-
if (name.includes('_')) {
141-
name = name.replace(/_/g, '-')
142-
}
143-
this.opts[name] = val
144-
}
153+
this.opts[name.replaceAll('_', '-')] = process.env[key]
145154
}
146-
})
155+
}
147156

148157
if (this.opts.loglevel) {
149158
log.logger.level = this.opts.loglevel
159+
delete this.opts.loglevel
150160
}
151161
log.resume()
152162
}

test/test-options.js

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,48 @@
33
const { describe, it } = require('mocha')
44
const assert = require('assert')
55
const gyp = require('../lib/node-gyp')
6+
const log = require('../lib/log')
67

78
describe('options', function () {
89
it('options in environment', () => {
910
// `npm test` dumps a ton of npm_config_* variables in the environment.
1011
Object.keys(process.env)
11-
.filter((key) => /^npm_config_/.test(key))
12+
.filter((key) => /^npm_config_/i.test(key) || /^npm_package_config_node_gyp_/i.test(key))
1213
.forEach((key) => { delete process.env[key] })
1314

1415
// in some platforms, certain keys are stubborn and cannot be removed
1516
const keys = Object.keys(process.env)
16-
.filter((key) => /^npm_config_/.test(key))
17+
.filter((key) => /^npm_config_/i.test(key) || /^npm_package_config_node_gyp_/i.test(key))
1718
.map((key) => key.substring('npm_config_'.length))
18-
.concat('argv', 'x')
19+
20+
// Environment variables with the following prefixes should be added to opts.
21+
// - `npm_config_` for npm versions before v11.
22+
// - `npm_package_config_node_gyp_` for npm versions 11 and later.
1923

2024
// Zero-length keys should get filtered out.
2125
process.env.npm_config_ = '42'
26+
process.env.npm_package_config_node_gyp_ = '42'
2227
// Other keys should get added.
28+
process.env.npm_package_config_node_gyp_foo = '42'
2329
process.env.npm_config_x = '42'
24-
// Except loglevel.
25-
process.env.npm_config_loglevel = 'debug'
30+
process.env.npm_config_y = '41'
31+
// Package config should take precedence over npm_config_ keys.
32+
process.env.npm_package_config_node_gyp_y = '42'
33+
// loglevel does not get added to opts but will change the logger's level.
34+
process.env.npm_config_loglevel = 'silly'
2635

2736
const g = gyp()
37+
38+
assert.strictEqual(log.logger.level.id, 'info')
39+
2840
g.parseArgv(['rebuild']) // Also sets opts.argv.
2941

30-
assert.deepStrictEqual(Object.keys(g.opts).sort(), keys.sort())
42+
assert.strictEqual(log.logger.level.id, 'silly')
43+
44+
assert.deepStrictEqual(Object.keys(g.opts).sort(), [...keys, 'argv', 'x', 'y', 'foo'].sort())
45+
assert.strictEqual(g.opts['x'], '42')
46+
assert.strictEqual(g.opts['y'], '42')
47+
assert.strictEqual(g.opts['foo'], '42')
3148
})
3249

3350
it('options with spaces in environment', () => {

0 commit comments

Comments
 (0)