@node-core/utils 5.16.2 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -26
- package/bin/ncu-config.js +24 -5
- package/components/git/v8.js +22 -2
- package/lib/auth.js +63 -40
- package/lib/config.js +76 -12
- package/lib/landing_session.js +2 -2
- package/lib/pr_checker.js +18 -18
- package/lib/promote_release.js +39 -24
- package/lib/session.js +19 -7
- package/lib/update-v8/deps.js +88 -0
- package/lib/update-v8/index.js +9 -0
- package/lib/update-v8/majorUpdate.js +1 -68
- package/lib/update-v8/util.js +7 -2
- package/lib/verbosity.js +3 -0
- package/lib/voting_session.js +0 -1
- package/npm-shrinkwrap.json +422 -321
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -76,18 +76,29 @@ Optionally, if you want to grant write access so `git-node` can write comments:
|
|
|
76
76
|
|
|
77
77
|
You can also edit the permission of existing tokens later.
|
|
78
78
|
|
|
79
|
-
After the token is generated,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
79
|
+
After the token is generated, you can give it to NCU using:
|
|
80
|
+
|
|
81
|
+
<details open name="set-token"><summary>With encryption (Recommended)</summary>
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
ncu-config set username your_github_username
|
|
85
|
+
# Do not provide the token in the CLI, `ncu-config` will prompt you for it.
|
|
86
|
+
ncu-config set -x token
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Note: Encryption is available only if you have `gpg` setup on your machine.
|
|
90
|
+
|
|
91
|
+
</details>
|
|
92
|
+
|
|
93
|
+
<details name="set-token"><summary>Without encryption</summary>
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
ncu-config set username your_github_username
|
|
97
|
+
# Do not provide the token in the CLI, `ncu-config` will prompt you for it.
|
|
98
|
+
ncu-config set token
|
|
87
99
|
```
|
|
88
100
|
|
|
89
|
-
|
|
90
|
-
recommended to leave your tokens in your command line history.
|
|
101
|
+
</details>
|
|
91
102
|
|
|
92
103
|
### Setting up Jenkins credentials
|
|
93
104
|
|
|
@@ -108,27 +119,24 @@ To obtain the Jenkins API token
|
|
|
108
119
|
`~/.ncurc.gpg` or `$XDG_CONFIG_HOME/ncurc.gpg`) with `jenkins_token` as key,
|
|
109
120
|
like this:
|
|
110
121
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
"jenkins_token": "your_jenkins_token"
|
|
116
|
-
}
|
|
122
|
+
<details open name="set-jenkins-token"><summary>With encryption (recommended)</summary>
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
ncu-config set -x jenkins_token
|
|
117
126
|
```
|
|
118
127
|
|
|
119
|
-
|
|
128
|
+
Note: Encryption is available only if you have `gpg` setup on your machine.
|
|
120
129
|
|
|
121
|
-
|
|
122
|
-
|
|
130
|
+
</details>
|
|
131
|
+
<details name="set-jenkins-token"><summary>Without encryption</summary>
|
|
132
|
+
|
|
133
|
+
```sh
|
|
134
|
+
ncu-config set jenkins_token
|
|
135
|
+
```
|
|
123
136
|
|
|
124
|
-
|
|
125
|
-
$ gpg --default-recipient-self --encrypt ~/.ncurc
|
|
126
|
-
$ rm ~/.ncurc
|
|
127
|
-
```
|
|
137
|
+
</details>
|
|
128
138
|
|
|
129
|
-
|
|
130
|
-
node-core-utils will invoke `gpg` that may ask you to decrypt it using
|
|
131
|
-
your default key via pinentry.
|
|
139
|
+
### Protecting your credentials
|
|
132
140
|
|
|
133
141
|
Put the following entries into your
|
|
134
142
|
[global `gitignore` file](https://git-scm.com/docs/git-config#Documentation/git-config.txt-coreexcludesFile)
|
package/bin/ncu-config.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import * as readline from 'node:readline/promises';
|
|
4
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
5
|
+
|
|
3
6
|
import yargs from 'yargs';
|
|
4
7
|
import { hideBin } from 'yargs/helpers';
|
|
5
8
|
|
|
6
9
|
import {
|
|
7
|
-
getConfig, updateConfig, GLOBAL_CONFIG, PROJECT_CONFIG, LOCAL_CONFIG
|
|
10
|
+
getConfig, updateConfig, GLOBAL_CONFIG, PROJECT_CONFIG, LOCAL_CONFIG,
|
|
11
|
+
encryptValue
|
|
8
12
|
} from '../lib/config.js';
|
|
9
13
|
import { setVerbosityFromEnv } from '../lib/verbosity.js';
|
|
10
14
|
|
|
@@ -13,10 +17,15 @@ setVerbosityFromEnv();
|
|
|
13
17
|
const args = yargs(hideBin(process.argv))
|
|
14
18
|
.completion('completion')
|
|
15
19
|
.command({
|
|
16
|
-
command: 'set <key> <value>',
|
|
20
|
+
command: 'set <key> [<value>]',
|
|
17
21
|
desc: 'Set a config variable',
|
|
18
22
|
builder: (yargs) => {
|
|
19
23
|
yargs
|
|
24
|
+
.option('encrypt', {
|
|
25
|
+
describe: 'Store the value encrypted using gpg',
|
|
26
|
+
alias: 'x',
|
|
27
|
+
type: 'boolean'
|
|
28
|
+
})
|
|
20
29
|
.positional('key', {
|
|
21
30
|
describe: 'key of the configuration',
|
|
22
31
|
type: 'string'
|
|
@@ -61,8 +70,6 @@ const args = yargs(hideBin(process.argv))
|
|
|
61
70
|
.conflicts('global', 'project')
|
|
62
71
|
.help();
|
|
63
72
|
|
|
64
|
-
const argv = args.parse();
|
|
65
|
-
|
|
66
73
|
function getConfigType(argv) {
|
|
67
74
|
if (argv.global) {
|
|
68
75
|
return { configName: 'global', configType: GLOBAL_CONFIG };
|
|
@@ -73,9 +80,19 @@ function getConfigType(argv) {
|
|
|
73
80
|
return { configName: 'local', configType: LOCAL_CONFIG };
|
|
74
81
|
}
|
|
75
82
|
|
|
76
|
-
function setHandler(argv) {
|
|
83
|
+
async function setHandler(argv) {
|
|
77
84
|
const { configName, configType } = getConfigType(argv);
|
|
78
85
|
const config = getConfig(configType);
|
|
86
|
+
if (!argv.value) {
|
|
87
|
+
const rl = readline.createInterface({ input, output });
|
|
88
|
+
argv.value = await rl.question('What value do you want to set? ');
|
|
89
|
+
rl.close();
|
|
90
|
+
} else if (argv.encrypt) {
|
|
91
|
+
console.warn('Passing sensitive config value via the shell is discouraged');
|
|
92
|
+
}
|
|
93
|
+
if (argv.encrypt) {
|
|
94
|
+
argv.value = await encryptValue(argv.value);
|
|
95
|
+
}
|
|
79
96
|
console.log(
|
|
80
97
|
`Updating ${configName} configuration ` +
|
|
81
98
|
`[${argv.key}]: ${config[argv.key]} -> ${argv.value}`);
|
|
@@ -96,6 +113,8 @@ function listHandler(argv) {
|
|
|
96
113
|
}
|
|
97
114
|
}
|
|
98
115
|
|
|
116
|
+
const argv = await args.parse();
|
|
117
|
+
|
|
99
118
|
if (!['get', 'set', 'list'].includes(argv._[0])) {
|
|
100
119
|
args.showHelp();
|
|
101
120
|
}
|
package/components/git/v8.js
CHANGED
|
@@ -2,12 +2,12 @@ import path from 'node:path';
|
|
|
2
2
|
|
|
3
3
|
import logSymbols from 'log-symbols';
|
|
4
4
|
|
|
5
|
-
import { minor, major, backport } from '../../lib/update-v8/index.js';
|
|
5
|
+
import { minor, major, backport, deps } from '../../lib/update-v8/index.js';
|
|
6
6
|
import { defaultBaseDir } from '../../lib/update-v8/constants.js';
|
|
7
7
|
import { checkCwd } from '../../lib/update-v8/common.js';
|
|
8
8
|
import { forceRunAsync } from '../../lib/run.js';
|
|
9
9
|
|
|
10
|
-
export const command = 'v8 [major|minor|backport]';
|
|
10
|
+
export const command = 'v8 [major|minor|backport|deps]';
|
|
11
11
|
export const describe = 'Update or patch the V8 engine';
|
|
12
12
|
|
|
13
13
|
export function builder(yargs) {
|
|
@@ -26,6 +26,11 @@ export function builder(yargs) {
|
|
|
26
26
|
describe: 'Bump the NODE_MODULE_VERSION constant',
|
|
27
27
|
default: true
|
|
28
28
|
});
|
|
29
|
+
yargs.option('concurrent', {
|
|
30
|
+
type: 'boolean',
|
|
31
|
+
describe: 'Update dependencies concurrently',
|
|
32
|
+
default: true,
|
|
33
|
+
});
|
|
29
34
|
}
|
|
30
35
|
})
|
|
31
36
|
.command({
|
|
@@ -64,6 +69,19 @@ export function builder(yargs) {
|
|
|
64
69
|
});
|
|
65
70
|
}
|
|
66
71
|
})
|
|
72
|
+
.command({
|
|
73
|
+
command: 'deps',
|
|
74
|
+
desc: 'Update V8 dependencies from the DEPS file',
|
|
75
|
+
handler,
|
|
76
|
+
builder: (yargs) => {
|
|
77
|
+
yargs
|
|
78
|
+
.option('concurrent', {
|
|
79
|
+
type: 'boolean',
|
|
80
|
+
describe: 'Update dependencies concurrently',
|
|
81
|
+
default: true,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
})
|
|
67
85
|
.demandCommand(1, 'Please provide a valid command')
|
|
68
86
|
.option('node-dir', {
|
|
69
87
|
describe: 'Directory of a Node.js clone',
|
|
@@ -126,6 +144,8 @@ export function handler(argv) {
|
|
|
126
144
|
return major(options);
|
|
127
145
|
case 'backport':
|
|
128
146
|
return backport(options);
|
|
147
|
+
case 'deps':
|
|
148
|
+
return deps(options);
|
|
129
149
|
}
|
|
130
150
|
})
|
|
131
151
|
.catch((err) => {
|
package/lib/auth.js
CHANGED
|
@@ -3,7 +3,7 @@ import { ClientRequest } from 'node:http';
|
|
|
3
3
|
|
|
4
4
|
import ghauth from 'ghauth';
|
|
5
5
|
|
|
6
|
-
import { getMergedConfig, getNcurcPath } from './config.js';
|
|
6
|
+
import { clearCachedConfig, encryptValue, getMergedConfig, getNcurcPath } from './config.js';
|
|
7
7
|
|
|
8
8
|
export default lazy(auth);
|
|
9
9
|
|
|
@@ -60,67 +60,90 @@ function encode(name, token) {
|
|
|
60
60
|
return Buffer.from(`${name}:${token}`).toString('base64');
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
function setOwnProperty(target, key, value) {
|
|
64
|
+
return Object.defineProperty(target, key, {
|
|
65
|
+
__proto__: null,
|
|
66
|
+
configurable: true,
|
|
67
|
+
enumerable: true,
|
|
68
|
+
value
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
63
72
|
// TODO: support jenkins only...or not necessary?
|
|
64
73
|
// TODO: make this a class with dependency (CLI) injectable for testing
|
|
65
74
|
async function auth(
|
|
66
75
|
options = { github: true },
|
|
67
76
|
githubAuth = ghauth) {
|
|
68
|
-
const result = {
|
|
77
|
+
const result = {
|
|
78
|
+
get github() {
|
|
79
|
+
let username;
|
|
80
|
+
let token;
|
|
81
|
+
try {
|
|
82
|
+
({ username, token } = getMergedConfig());
|
|
83
|
+
} catch (e) {
|
|
84
|
+
// Ignore error and prompt
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
check(username, token);
|
|
88
|
+
const github = encode(username, token);
|
|
89
|
+
setOwnProperty(result, 'github', github);
|
|
90
|
+
return github;
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
get jenkins() {
|
|
94
|
+
const { username, jenkins_token } = getMergedConfig();
|
|
95
|
+
if (!username || !jenkins_token) {
|
|
96
|
+
errorExit(
|
|
97
|
+
'Get your Jenkins API token in https://ci.nodejs.org/me/security ' +
|
|
98
|
+
'and run the following command to add it to your ncu config: ' +
|
|
99
|
+
'ncu-config --global set -x jenkins_token'
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
check(username, jenkins_token);
|
|
103
|
+
const jenkins = encode(username, jenkins_token);
|
|
104
|
+
setOwnProperty(result, 'jenkins', jenkins);
|
|
105
|
+
return jenkins;
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
get h1() {
|
|
109
|
+
const { h1_username, h1_token } = getMergedConfig();
|
|
110
|
+
check(h1_username, h1_token);
|
|
111
|
+
const h1 = encode(h1_username, h1_token);
|
|
112
|
+
setOwnProperty(result, 'h1', h1);
|
|
113
|
+
return h1;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
69
116
|
if (options.github) {
|
|
70
|
-
let
|
|
71
|
-
let token;
|
|
117
|
+
let config;
|
|
72
118
|
try {
|
|
73
|
-
|
|
74
|
-
} catch
|
|
75
|
-
|
|
119
|
+
config = getMergedConfig();
|
|
120
|
+
} catch {
|
|
121
|
+
config = {};
|
|
76
122
|
}
|
|
77
|
-
|
|
78
|
-
if (!username || !token) {
|
|
123
|
+
if (!Object.hasOwn(config, 'token') || !Object.hasOwn(config, 'username')) {
|
|
79
124
|
process.stdout.write(
|
|
80
125
|
'If this is your first time running this command, ' +
|
|
81
126
|
'follow the instructions to create an access token' +
|
|
82
127
|
'. If you prefer to create it yourself on Github, ' +
|
|
83
128
|
'see https://github.com/nodejs/node-core-utils/blob/main/README.md.\n');
|
|
84
129
|
const credentials = await tryCreateGitHubToken(githubAuth);
|
|
85
|
-
username = credentials.user;
|
|
86
|
-
|
|
130
|
+
const username = credentials.user;
|
|
131
|
+
let token;
|
|
132
|
+
try {
|
|
133
|
+
token = await encryptValue(credentials.token);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
console.warn('Failed encrypt token, storing unencrypted instead');
|
|
136
|
+
token = credentials.token;
|
|
137
|
+
}
|
|
87
138
|
const json = JSON.stringify({ username, token }, null, 2);
|
|
88
139
|
fs.writeFileSync(getNcurcPath(), json, {
|
|
89
140
|
mode: 0o600 /* owner read/write */
|
|
90
141
|
});
|
|
91
142
|
// Try again reading the file
|
|
92
|
-
(
|
|
143
|
+
clearCachedConfig();
|
|
93
144
|
}
|
|
94
|
-
check(username, token);
|
|
95
|
-
result.github = encode(username, token);
|
|
96
145
|
}
|
|
97
146
|
|
|
98
|
-
if (options.jenkins) {
|
|
99
|
-
const { username, jenkins_token } = getMergedConfig();
|
|
100
|
-
if (!username || !jenkins_token) {
|
|
101
|
-
errorExit(
|
|
102
|
-
'Get your Jenkins API token in https://ci.nodejs.org/me/configure ' +
|
|
103
|
-
'and run the following command to add it to your ncu config: ' +
|
|
104
|
-
'ncu-config --global set jenkins_token TOKEN'
|
|
105
|
-
);
|
|
106
|
-
};
|
|
107
|
-
check(username, jenkins_token);
|
|
108
|
-
result.jenkins = encode(username, jenkins_token);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (options.h1) {
|
|
112
|
-
const { h1_username, h1_token } = getMergedConfig();
|
|
113
|
-
if (!h1_username || !h1_token) {
|
|
114
|
-
errorExit(
|
|
115
|
-
'Get your HackerOne API token in ' +
|
|
116
|
-
'https://docs.hackerone.com/organizations/api-tokens.html ' +
|
|
117
|
-
'and run the following command to add it to your ncu config: ' +
|
|
118
|
-
'ncu-config --global set h1_token TOKEN or ' +
|
|
119
|
-
'ncu-config --global set h1_username USERNAME'
|
|
120
|
-
);
|
|
121
|
-
};
|
|
122
|
-
result.h1 = encode(h1_username, h1_token);
|
|
123
|
-
}
|
|
124
147
|
return result;
|
|
125
148
|
}
|
|
126
149
|
|
package/lib/config.js
CHANGED
|
@@ -4,6 +4,7 @@ import os from 'node:os';
|
|
|
4
4
|
import { readJson, writeJson } from './file.js';
|
|
5
5
|
import { existsSync, mkdtempSync, rmSync } from 'node:fs';
|
|
6
6
|
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { forceRunAsync, runSync } from './run.js';
|
|
7
8
|
|
|
8
9
|
export const GLOBAL_CONFIG = Symbol('globalConfig');
|
|
9
10
|
export const PROJECT_CONFIG = Symbol('projectConfig');
|
|
@@ -18,32 +19,95 @@ export function getNcurcPath() {
|
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
let mergedConfig;
|
|
23
|
+
export function getMergedConfig(dir, home, additional) {
|
|
24
|
+
if (mergedConfig == null) {
|
|
25
|
+
const globalConfig = getConfig(GLOBAL_CONFIG, home);
|
|
26
|
+
const projectConfig = getConfig(PROJECT_CONFIG, dir);
|
|
27
|
+
const localConfig = getConfig(LOCAL_CONFIG, dir);
|
|
28
|
+
mergedConfig = Object.assign(globalConfig, projectConfig, localConfig, additional);
|
|
29
|
+
}
|
|
30
|
+
return mergedConfig;
|
|
26
31
|
};
|
|
32
|
+
export function clearCachedConfig() {
|
|
33
|
+
mergedConfig = null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function encryptValue(input) {
|
|
37
|
+
console.warn('Spawning gpg to encrypt the config value');
|
|
38
|
+
return forceRunAsync(
|
|
39
|
+
process.env.GPG_BIN || 'gpg',
|
|
40
|
+
['--default-recipient-self', '--encrypt', '--armor'],
|
|
41
|
+
{
|
|
42
|
+
captureStdout: true,
|
|
43
|
+
ignoreFailure: false,
|
|
44
|
+
input
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function setOwnProperty(target, key, value) {
|
|
50
|
+
return Object.defineProperty(target, key, {
|
|
51
|
+
__proto__: null,
|
|
52
|
+
configurable: true,
|
|
53
|
+
enumerable: true,
|
|
54
|
+
value
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function addEncryptedPropertyGetter(target, key, input) {
|
|
58
|
+
if (input?.startsWith?.('-----BEGIN PGP MESSAGE-----\n')) {
|
|
59
|
+
return Object.defineProperty(target, key, {
|
|
60
|
+
__proto__: null,
|
|
61
|
+
configurable: true,
|
|
62
|
+
get() {
|
|
63
|
+
// Using an error object to get a stack trace in debug mode.
|
|
64
|
+
const warn = new Error(
|
|
65
|
+
`The config value for ${key} is encrypted, spawning gpg to decrypt it...`
|
|
66
|
+
);
|
|
67
|
+
console.warn(setOwnProperty(warn, 'name', 'Warning'));
|
|
68
|
+
const value = runSync(process.env.GPG_BIN || 'gpg', ['--decrypt'], { input });
|
|
69
|
+
setOwnProperty(target, key, value);
|
|
70
|
+
return value;
|
|
71
|
+
},
|
|
72
|
+
set(newValue) {
|
|
73
|
+
if (!addEncryptedPropertyGetter(target, key, newValue)) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
'Refusing to override an encrypted value with a non-encrypted one. ' +
|
|
76
|
+
'Please use an encrypted one, or delete the config key first.'
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
27
83
|
|
|
28
|
-
export function getConfig(configType, dir) {
|
|
84
|
+
export function getConfig(configType, dir, raw = false) {
|
|
29
85
|
const configPath = getConfigPath(configType, dir);
|
|
30
86
|
const encryptedConfigPath = configPath + '.gpg';
|
|
31
87
|
if (existsSync(encryptedConfigPath)) {
|
|
32
88
|
console.warn('Encrypted config detected, spawning gpg to decrypt it...');
|
|
33
89
|
const { status, stdout } =
|
|
34
|
-
spawnSync('gpg', ['--decrypt', encryptedConfigPath]);
|
|
90
|
+
spawnSync(process.env.GPG_BIN || 'gpg', ['--decrypt', encryptedConfigPath]);
|
|
35
91
|
if (status === 0) {
|
|
36
92
|
return JSON.parse(stdout.toString('utf-8'));
|
|
37
93
|
}
|
|
38
94
|
}
|
|
39
95
|
try {
|
|
40
|
-
|
|
96
|
+
const json = readJson(configPath);
|
|
97
|
+
if (!raw) {
|
|
98
|
+
// Raw config means encrypted values are returned as is.
|
|
99
|
+
// Otherwise we install getters to decrypt them when accessed.
|
|
100
|
+
for (const [key, val] of Object.entries(json)) {
|
|
101
|
+
addEncryptedPropertyGetter(json, key, val);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return json;
|
|
41
105
|
} catch (cause) {
|
|
42
106
|
throw new Error('Unable to parse config file ' + configPath, { cause });
|
|
43
107
|
}
|
|
44
108
|
};
|
|
45
109
|
|
|
46
|
-
|
|
110
|
+
function getConfigPath(configType, dir) {
|
|
47
111
|
switch (configType) {
|
|
48
112
|
case GLOBAL_CONFIG:
|
|
49
113
|
return getNcurcPath();
|
|
@@ -61,7 +125,7 @@ export function getConfigPath(configType, dir) {
|
|
|
61
125
|
}
|
|
62
126
|
};
|
|
63
127
|
|
|
64
|
-
|
|
128
|
+
function writeConfig(configType, obj, dir) {
|
|
65
129
|
const configPath = getConfigPath(configType, dir);
|
|
66
130
|
const encryptedConfigPath = configPath + '.gpg';
|
|
67
131
|
if (existsSync(encryptedConfigPath)) {
|
|
@@ -69,7 +133,7 @@ export function writeConfig(configType, obj, dir) {
|
|
|
69
133
|
const tmpFile = path.join(tmpDir, 'config.json');
|
|
70
134
|
try {
|
|
71
135
|
writeJson(tmpFile, obj);
|
|
72
|
-
const { status } = spawnSync('gpg',
|
|
136
|
+
const { status } = spawnSync(process.env.GPG_BIN || 'gpg',
|
|
73
137
|
['--default-recipient-self', '--yes', '--encrypt', '--output', encryptedConfigPath, tmpFile]
|
|
74
138
|
);
|
|
75
139
|
if (status !== 0) {
|
|
@@ -85,7 +149,7 @@ export function writeConfig(configType, obj, dir) {
|
|
|
85
149
|
};
|
|
86
150
|
|
|
87
151
|
export function updateConfig(configType, obj, dir) {
|
|
88
|
-
const config = getConfig(configType, dir);
|
|
152
|
+
const config = getConfig(configType, dir, true);
|
|
89
153
|
writeConfig(configType, Object.assign(config, obj), dir);
|
|
90
154
|
};
|
|
91
155
|
|
package/lib/landing_session.js
CHANGED
|
@@ -464,8 +464,8 @@ export default class LandingSession extends Session {
|
|
|
464
464
|
const url = `https://github.com/${owner}/${repo}/pull/${prid}`;
|
|
465
465
|
cli.log(`2. Post "Landed in ${willBeLanded}" in ${url}`);
|
|
466
466
|
if (isGhAvailable()) {
|
|
467
|
-
cli.log(` gh pr comment ${
|
|
468
|
-
cli.log(` gh pr close ${
|
|
467
|
+
cli.log(` gh pr comment ${url} --body "Landed in ${willBeLanded}"`);
|
|
468
|
+
cli.log(` gh pr close ${url}`);
|
|
469
469
|
}
|
|
470
470
|
}
|
|
471
471
|
|
package/lib/pr_checker.js
CHANGED
|
@@ -19,7 +19,6 @@ const { FROM_COMMENT, FROM_REVIEW_COMMENT } = REVIEW_SOURCES;
|
|
|
19
19
|
|
|
20
20
|
const SECOND = 1000;
|
|
21
21
|
const MINUTE = SECOND * 60;
|
|
22
|
-
const HOUR = MINUTE * 60;
|
|
23
22
|
|
|
24
23
|
const WAIT_TIME_MULTI_APPROVAL = 24 * 2;
|
|
25
24
|
const WAIT_TIME_SINGLE_APPROVAL = 24 * 7;
|
|
@@ -196,10 +195,18 @@ export default class PRChecker {
|
|
|
196
195
|
|
|
197
196
|
const createTime = new Date(this.pr.createdAt);
|
|
198
197
|
const msFromCreateTime = now.getTime() - createTime.getTime();
|
|
199
|
-
const minutesFromCreateTime =
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
const
|
|
198
|
+
const minutesFromCreateTime = msFromCreateTime / MINUTE;
|
|
199
|
+
const timeLeftMulti = this.waitTimeMultiApproval * 60 - minutesFromCreateTime;
|
|
200
|
+
const timeLeftSingle = this.waitTimeSingleApproval * 60 - minutesFromCreateTime;
|
|
201
|
+
const timeToText = (time, liaison_word = undefined) => {
|
|
202
|
+
let unity = 'minute';
|
|
203
|
+
if (time > 59) {
|
|
204
|
+
unity = 'hour';
|
|
205
|
+
time /= 60;
|
|
206
|
+
}
|
|
207
|
+
time = Math.ceil(time);
|
|
208
|
+
return `${time} ${liaison_word ? liaison_word + ' ' : ''}${unity}${time === 1 ? '' : 's'}`;
|
|
209
|
+
};
|
|
203
210
|
|
|
204
211
|
if (approved.length >= 2) {
|
|
205
212
|
if (isFastTracked || isCodeAndLearn) {
|
|
@@ -208,15 +215,9 @@ export default class PRChecker {
|
|
|
208
215
|
if (timeLeftMulti < 0) {
|
|
209
216
|
return true;
|
|
210
217
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
cli.error(`This PR needs to wait ${timeLeftMins} ` +
|
|
215
|
-
`more minutes to land${fastTrackAppendix}`);
|
|
216
|
-
return false;
|
|
217
|
-
}
|
|
218
|
-
cli.error(`This PR needs to wait ${timeLeftMulti} more ` +
|
|
219
|
-
`hours to land${fastTrackAppendix}`);
|
|
218
|
+
cli.error(
|
|
219
|
+
`This PR needs to wait ${timeToText(timeLeftMulti, 'more')} to land${fastTrackAppendix}`
|
|
220
|
+
);
|
|
220
221
|
return false;
|
|
221
222
|
}
|
|
222
223
|
|
|
@@ -224,10 +225,9 @@ export default class PRChecker {
|
|
|
224
225
|
if (timeLeftSingle < 0) {
|
|
225
226
|
return true;
|
|
226
227
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
fastTrackAppendix);
|
|
228
|
+
cli.error(`This PR needs to wait ${timeToText(timeLeftSingle, 'more')} to land (or ${
|
|
229
|
+
timeToText(timeLeftMulti < 0 || isFastTracked ? 0 : timeLeftMulti)
|
|
230
|
+
} if there is one more approval)${fastTrackAppendix}`);
|
|
231
231
|
return false;
|
|
232
232
|
}
|
|
233
233
|
}
|
package/lib/promote_release.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { pipeline } from 'node:stream/promises';
|
|
3
5
|
import semver from 'semver';
|
|
4
6
|
import * as gst from 'git-secure-tag';
|
|
5
7
|
|
|
@@ -196,31 +198,44 @@ export default class ReleasePromotion extends Session {
|
|
|
196
198
|
|
|
197
199
|
async verifyTagSignature(version) {
|
|
198
200
|
const { cli } = this;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
201
|
+
|
|
202
|
+
cli.startSpinner('Downloading active releasers keyring from nodejs/release-keys...');
|
|
203
|
+
const [keyRingStream, [GNUPGHOME, keyRingFd]] = await Promise.all([
|
|
204
|
+
fetch('https://github.com/nodejs/release-keys/raw/HEAD/gpg-only-active-keys/pubring.kbx'),
|
|
205
|
+
fs.mkdtemp(path.join(tmpdir(), 'ncu-'))
|
|
206
|
+
.then(async d => [d, await fs.open(path.join(d, 'pubring.kbx'), 'w')]),
|
|
207
|
+
]);
|
|
208
|
+
if (!keyRingStream.ok) throw new Error('Failed to download keyring', { cause: keyRingStream });
|
|
209
|
+
await pipeline(keyRingStream.body, keyRingFd.createWriteStream());
|
|
210
|
+
cli.stopSpinner('Active releasers keyring stored in temp directory');
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
await forceRunAsync(
|
|
214
|
+
'git', ['--no-pager',
|
|
215
|
+
'verify-tag',
|
|
216
|
+
`v${version}`
|
|
217
|
+
], {
|
|
218
|
+
ignoreFailure: false,
|
|
219
|
+
spawnArgs: { env: { ...process.env, GNUPGHOME } },
|
|
220
|
+
});
|
|
221
|
+
cli.ok('git tag signature verified');
|
|
222
|
+
} catch (cause) {
|
|
223
|
+
cli.error('git was not able to verify the tag');
|
|
224
|
+
cli.warn('This means that either the tag was signed with the wrong key,');
|
|
225
|
+
cli.warn('or that nodejs/release-keys contains outdated information.');
|
|
226
|
+
cli.warn('The release should not proceed.');
|
|
227
|
+
if (!await cli.prompt('Do you want to proceed anyway?', { defaultAnswer: false })) {
|
|
228
|
+
if (await cli.prompt('Do you want to delete the local tag?')) {
|
|
229
|
+
await forceRunAsync('git', ['tag', '-d', `v${version}`]);
|
|
230
|
+
} else {
|
|
231
|
+
cli.info(`Run 'git tag -d v${version}' to remove the local tag.`);
|
|
232
|
+
}
|
|
233
|
+
throw new Error('Aborted', { cause });
|
|
214
234
|
}
|
|
215
|
-
|
|
216
|
-
cli.
|
|
217
|
-
|
|
218
|
-
cli.
|
|
219
|
-
}
|
|
220
|
-
cli.info(`If that doesn't sound right, consider removing the tag (git tag -d v${version
|
|
221
|
-
}), check your local config, and start the process over.`);
|
|
222
|
-
if (!await cli.prompt('Do you want to proceed anyway?', { defaultAnswer: false })) {
|
|
223
|
-
throw new Error('Aborted');
|
|
235
|
+
} finally {
|
|
236
|
+
cli.startSpinner('Cleaning up temp files');
|
|
237
|
+
await fs.rm(GNUPGHOME, { force: true, recursive: true });
|
|
238
|
+
cli.stopSpinner('Temp files removed');
|
|
224
239
|
}
|
|
225
240
|
}
|
|
226
241
|
|