Skip to content

Commit f485450

Browse files
committed
feat: add TFA to rollout and fix pushdir order
1 parent 4dfdc0b commit f485450

File tree

10 files changed

+84
-30
lines changed

10 files changed

+84
-30
lines changed

packages/cli-core/ConsoleUtilities.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ exports.step = async (text, fn, skip) => {
1111
}
1212

1313
try {
14-
await fn();
14+
await fn(spinner);
1515
spinner.succeed();
1616
} catch (err) {
1717
spinner.fail(err.message);

packages/rollout/command.js

Lines changed: 63 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,6 @@ async function maybeRollbackGit(tag, skipGit, skipVersion) {
3131
);
3232
}
3333

34-
async function bumpVersion(pkg, publishDir, cwd) {
35-
const json = pkg;
36-
const pkgPath = path.join(cwd, 'package.json');
37-
await writeJson(pkgPath, json);
38-
39-
if (publishDir) {
40-
await createAltPublishDir({ outDir: publishDir });
41-
}
42-
}
43-
4434
async function getNextVersion(version, currentVersion, preid) {
4535
const patch = semver.inc(currentVersion, 'patch');
4636
const minor = semver.inc(currentVersion, 'minor');
@@ -112,6 +102,57 @@ async function getNextVersion(version, currentVersion, preid) {
112102
}
113103
}
114104

105+
async function npmPublish(
106+
spinner,
107+
pkgJson,
108+
options,
109+
otpMessage = 'Enter one time password:',
110+
) {
111+
const { publishDir, otp, isPublic, tag } = options;
112+
const args = ['publish'];
113+
114+
if (publishDir) {
115+
args.push(publishDir, '--ignore-scripts');
116+
117+
// We run the lifecycle scripts manually to ensure they run in
118+
// the package root, not the publish dir
119+
await runLifecycle('prepublish', pkgJson);
120+
await runLifecycle('prepare', pkgJson);
121+
await runLifecycle('prepublishOnly', pkgJson);
122+
123+
// do this after lifecycle scripts in case they clean the publishDir
124+
await createAltPublishDir({ outDir: publishDir });
125+
}
126+
if (otp) {
127+
args.push('--otp', otp);
128+
}
129+
130+
if (tag !== 'latest') {
131+
args.push('--tag', tag);
132+
}
133+
134+
if (isPublic != null) {
135+
args.push('--access', isPublic ? 'public' : 'restricted');
136+
}
137+
138+
try {
139+
return execa('npm', args, { stdio: [0, 1, 'pipe'] });
140+
} catch (err) {
141+
if (err.stderr.includes('OTP')) {
142+
spinner.stop();
143+
const nextOtp = await PromptUtilities.input(otpMessage);
144+
spinner.start();
145+
return npmPublish(
146+
spinner,
147+
pkgJson,
148+
{ ...options, otp: nextOtp },
149+
'One time password was incorrect, try again:',
150+
);
151+
}
152+
throw err;
153+
}
154+
}
155+
115156
exports.command = '$0 [nextVersion]';
116157

117158
exports.describe = 'Publish a new version';
@@ -129,8 +170,12 @@ exports.builder = _ =>
129170
type: 'bool',
130171
default: false,
131172
})
173+
.option('otp', {
174+
type: 'string',
175+
})
132176
.option('publish-dir', {
133177
type: 'string',
178+
describe: 'Provide a two-factor authentication code for publishing',
134179
})
135180
.option('allow-branch', {
136181
group: 'Command Options:',
@@ -172,6 +217,7 @@ exports.handler = async ({
172217
skipVersion,
173218
allowBranch,
174219
publishDir,
220+
otp,
175221
public: isPublic,
176222
}) => {
177223
const cwd = process.cwd();
@@ -209,7 +255,11 @@ exports.handler = async ({
209255
`Bumping version to: ${chalk.bold(nextVersion)} (${chalk.dim(
210256
`was ${pkg.version}`,
211257
)})`,
212-
() => bumpVersion({ ...pkg, version: nextVersion }, publishDir, cwd),
258+
() =>
259+
writeJson(path.join(cwd, 'package.json'), {
260+
...pkg,
261+
version: nextVersion,
262+
}),
213263
);
214264
}
215265

@@ -238,24 +288,8 @@ exports.handler = async ({
238288
try {
239289
await ConsoleUtilities.step(
240290
'Publishing to npm',
241-
async () => {
242-
const args = ['publish'];
243-
if (publishDir) {
244-
args.push(publishDir);
245-
// We run the lifecycle scripts manually to ensure they run in
246-
// the package root, not the publish dir
247-
await runLifecycle('prepublish', pkg);
248-
await runLifecycle('prepare', pkg);
249-
await runLifecycle('prepublishOnly', pkg);
250-
}
251-
if (tag !== 'latest') {
252-
args.push('--tag', tag);
253-
}
254-
255-
if (isPublic != null) {
256-
args.push('--access', isPublic ? 'public' : 'restricted');
257-
}
258-
await execa('npm', args, { stdio: [0, 1, 'pipe'] });
291+
async spinner => {
292+
await npmPublish(spinner, pkg, { otp, publishDir, isPublic, tag });
259293

260294
try {
261295
if (publishDir) {

packages/rollout/test/fixtures/LICENSE

Whitespace-only changes.

packages/rollout/test/fixtures/ReAdMe.MD

Whitespace-only changes.

packages/rollout/test/fixtures/lib/LICENSE

Whitespace-only changes.

packages/rollout/test/fixtures/lib/ReAdMe.MD

Whitespace-only changes.

packages/rollout/test/fixtures/lib/foo.js

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "@monastic.panic/test-pkg",
3+
"version": "1.0.10",
4+
"main": "./index.js",
5+
"dependencies": {}
6+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "@monastic.panic/test-pkg",
3+
"version": "1.0.10",
4+
"main": "./lib/index.js",
5+
"scripts": {
6+
"prepublish": "echo 'prepublish'",
7+
"prepublishOnly": "echo 'prepublish'",
8+
"prepare": "echo 'prepare' && rm -rf lib && cp -r src lib",
9+
"publish": "echo 'publish'",
10+
"postpublish": "echo 'postpublish'"
11+
},
12+
"devDependencies": {},
13+
"dependencies": {}
14+
}

packages/rollout/test/fixtures/src/foo.js

Whitespace-only changes.

0 commit comments

Comments
 (0)