Skip to content

Commit

Permalink
[Breaking] getLatestError: refactor to use Promises
Browse files Browse the repository at this point in the history
  • Loading branch information
ljharb committed Oct 13, 2021
1 parent 222b068 commit f44edd4
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 166 deletions.
4 changes: 2 additions & 2 deletions .eslintrc
Expand Up @@ -8,16 +8,16 @@
"array-element-newline": [2, { "multiline": true, "minItems": 2 }],
"func-style": "off",
"max-lines-per-function": [2, 115],
"max-params": [2, 4],
"max-statements": [2, 20],
"no-magic-numbers": [2, { "ignore": [0, 1, -1] }],
"no-throw-literal": "warn",
"operator-linebreak": [2, "none", { "overrides": { "?": "before", ":": "before" } }]
},

"overrides": [
{
"files": "test/**",
"rules": {
"max-lines-per-function": "off",
"no-magic-numbers": 0,
},
},
Expand Down
22 changes: 11 additions & 11 deletions bin/safe-publish-latest
Expand Up @@ -32,17 +32,17 @@ if (!name) {
}

const semver = require('semver');
if (!semver.valid(version)) {
console.error(`Error: package version "${version}" is invalid.`);
process.exit(2);
}

const getLatestError = require('../getLatestError');
getLatestError(name, version, options, (errors, messages) => {
if (!errors) {

if (semver.valid(version)) {
getLatestError(name, version).then((messages) => {
console.log([].concat(messages, 'Publish away!').join('\n'));
process.exit(0);
}
console.error(errors.join('\n'));
process.exit(3);
});
}).catch((errors) => {
console.error([].concat(errors).join('\n'));
process.exitCode = 3;
});
} else {
console.error(`Error: package version "${version}" is invalid.`);
process.exitCode = 2;
}
96 changes: 49 additions & 47 deletions getLatestError.js
@@ -1,6 +1,8 @@
'use strict';

const { exec } = require('child_process');
const { promisify } = require('util');
const cp = require('child_process');
const exec = promisify(cp.exec);
const {
eq,
gtr,
Expand All @@ -17,60 +19,60 @@ function isNotPrerelease(v) {
return !isPrerelease(v);
}

module.exports = function getLatestError(name, version, callback) {
module.exports = async function getLatestError(name, version) {
if (process.env.PUBLISH_LATEST_DANGEROUSLY === 'true') {
return callback(null, '$PUBLISH_LATEST_DANGEROUSLY override enabled.');
return '$PUBLISH_LATEST_DANGEROUSLY override enabled.';
}
if (getTag() !== 'latest') {
return callback(null, 'Non-latest dist-tag detected.');
return 'Non-latest dist-tag detected.';
}

return exec(`npm info ${name} versions --json --loglevel=info`, (err, json) => {
if (err) {
if ((/^npm ERR! code E404$/m).test(err)) {
return callback(null, `v${version} is the first version published.`);
}
return callback([
'Error fetching package versions:',
err,
]);
}
let allVersions;
try {
allVersions = [].concat(JSON.parse(json));
} catch (e) {
return callback([
'Error parsing JSON from npm',
e,
]);
let json;
try {
({ stdout: json } = await exec(`npm info ${name} versions --json --loglevel=info`));
} catch (err) {
if ((/^npm ERR! code E404$/m).test(err)) {
return `v${version} is the first version published.`;
}
throw [
'Error fetching package versions:',
err,
];
}

const versions = allVersions.filter(isNotPrerelease);
if (versions.length === 0) {
return callback(null, 'No non-prerelease versions detected.');
}
let allVersions;
try {
allVersions = [].concat(JSON.parse(json));
} catch (e) {
throw [
'Error parsing JSON from npm',
e,
];
}

const max = maxSatisfying(versions, '*');
if (eq(version, max)) {
return callback([
`Attempting to publish already-published version v${version}.`,
]);
}
const versions = allVersions.filter(isNotPrerelease);
if (versions.length === 0) {
return 'No non-prerelease versions detected.';
}

const greater = gtr(version, versions.join('||'));
const isPre = isPrerelease(version);
if (!greater || isPre) {
const msg = isPre
? format('Attempting to publish v%s as "latest", but it is a prerelease version.', version)
: format('Attempting to publish v%s as "latest", but it is not later than v%s.', version, max);
return callback([
msg,
'\nPossible Solutions:',
'\t1) Provide a dist-tag: `npm publish --tag=backport`, for example',
'\t2) Use the very dangerous override: `PUBLISH_LATEST_DANGEROUSLY=true npm publish`',
]);
}
const max = maxSatisfying(versions, '*');
if (eq(version, max)) {
throw `Attempting to publish already-published version v${version}.`;
}

const greater = gtr(version, versions.join('||'));
const isPre = isPrerelease(version);
if (!greater || isPre) {
const msg = isPre
? format('Attempting to publish v%s as "latest", but it is a prerelease version.', version)
: format('Attempting to publish v%s as "latest", but it is not later than v%s.', version, max);
return [
msg,
'\nPossible Solutions:',
'\t1) Provide a dist-tag: `npm publish --tag=backport`, for example',
'\t2) Use the very dangerous override: `PUBLISH_LATEST_DANGEROUSLY=true npm publish`',
];
}

return callback(null, format('v%s is later than v%s.', version, max));
});
return format('v%s is later than v%s.', version, max);
};
206 changes: 100 additions & 106 deletions test/getLatestError.js
@@ -1,122 +1,116 @@
'use strict';

const test = require('tape');
const mockEnv = require('mock-env').morph;
const { morph } = require('mock-env');

const mockEnv = async (callback, env) => new Promise((resolve) => {
resolve(morph(callback, env));
});

const getLatestError = require('../');
const fakeTag = require('./_fakeTag');

test('getLatestError', (t) => {
t.test('env var override', (st) => {
st.plan(2);
mockEnv(() => {
getLatestError(null, null, {}, (error, result) => {
st.error(error, 'there should be no error');
st.equal(result, '$PUBLISH_LATEST_DANGEROUSLY override enabled.');
});
}, { PUBLISH_LATEST_DANGEROUSLY: 'true' });
});
function testResult(fn, env, expected) {
return async (t) => {
const result = await mockEnv(
fn,
env,
);
t.equal(result, expected);
};
}

t.test('non-"latest" tag', (st) => {
st.plan(2);
mockEnv(() => {
getLatestError(null, null, {}, (error, result) => {
st.error(error, 'there should be no error');
st.equal(result, 'Non-latest dist-tag detected.');
});
}, fakeTag('anything but latest'));
function testError(fn, env, expected) {
return async (t) => mockEnv(fn, env).catch((e) => {
t.deepEqual(e, expected);
});
}

t.test('"latest" tag', (st) => {
st.plan(6);

mockEnv(() => {
st.test('when the package has no non-prerelease versions', (s2t) => {
s2t.plan(2);
getLatestError('abcde', '1.0.2', {}, (err, result) => {
s2t.error(err, 'there should be no error');
s2t.equal(result, 'No non-prerelease versions detected.');
});
});

st.test('with a later version', (s2t) => {
s2t.plan(2);
getLatestError('def', '9999.0.0', {}, (err, result) => {
s2t.error(err, 'there should be no error');
s2t.equal(result, 'v9999.0.0 is later than v0.0.8.');
});
});

st.test('with an earlier version', (s2t) => {
s2t.plan(2);
getLatestError('def', '0.0.5', {}, (err, result) => {
s2t.notOk(result, 'no result');
s2t.deepEqual(err, [
'Attempting to publish v0.0.5 as "latest", but it is not later than v0.0.8.',
'\nPossible Solutions:',
'\t1) Provide a dist-tag: `npm publish --tag=backport`, for example',
'\t2) Use the very dangerous override: `PUBLISH_LATEST_DANGEROUSLY=true npm publish`',
]);
});
});

st.test('with an existing version', (s2t) => {
s2t.plan(2);
getLatestError('def', '0.0.8', {}, (err, result) => {
s2t.notOk(result, 'no result');
s2t.deepEqual(err, [
'Attempting to publish already-published version v0.0.8.',
]);
});
});

st.test('with a later prerelease version', (s2t) => {
s2t.plan(2);
getLatestError('def', '9999.0.0-prerelease.0', {}, (err, result) => {
s2t.notOk(result, 'no result');
s2t.deepEqual(err, [
'Attempting to publish v9999.0.0-prerelease.0 as "latest", but it is a prerelease version.',
'\nPossible Solutions:',
'\t1) Provide a dist-tag: `npm publish --tag=backport`, for example',
'\t2) Use the very dangerous override: `PUBLISH_LATEST_DANGEROUSLY=true npm publish`',
]);
});
});

st.test('with an earlier prerelease version', (s2t) => {
s2t.plan(2);
getLatestError('def', '0.0.4-prerelease.0', {}, (err, result) => {
s2t.notOk(result, 'no result');
s2t.deepEqual(err, [
'Attempting to publish v0.0.4-prerelease.0 as "latest", but it is a prerelease version.',
'\nPossible Solutions:',
'\t1) Provide a dist-tag: `npm publish --tag=backport`, for example',
'\t2) Use the very dangerous override: `PUBLISH_LATEST_DANGEROUSLY=true npm publish`',
]);
});
});

st.end();
}, fakeTag('latest'));
});
test('getLatestError', (t) => {
t.test('env var override', testResult(
() => getLatestError(null, null),
{ PUBLISH_LATEST_DANGEROUSLY: 'true' },
'$PUBLISH_LATEST_DANGEROUSLY override enabled.',
));

t.test('nonexistent but valid package name', (st) => {
st.plan(2);
getLatestError('abcdef123', '1.0.0', {}, (err, result) => {
st.error(err, 'there should be no error');
st.equal(result, 'v1.0.0 is the first version published.');
st.end();
});
});
t.test('non-"latest" tag', testResult(
() => getLatestError(null, null),
fakeTag('anything but latest'),
'Non-latest dist-tag detected.',
));

t.test('"latest" tag', async (st) => {
const latest = fakeTag('latest');

t.test('nonexistent but valid scoped package name', (st) => {
st.plan(2);
getLatestError('@ljharb/abcdef123', '1.0.0', {}, (err, result) => {
st.error(err, 'there should be no error');
st.equal(result, 'v1.0.0 is the first version published.');
st.end();
});
st.test('when the package has no non-prerelease versions', testResult(
() => getLatestError('abcde', '1.0.2'),
latest,
'No non-prerelease versions detected.',
));

st.test('when the package has no non-prerelease versions', testResult(
() => getLatestError('abcde', '1.0.2'),
latest,
'No non-prerelease versions detected.',
));

st.test('with a later version', testResult(
() => getLatestError('def', '9999.0.0'),
latest,
'v9999.0.0 is later than v0.0.8.',
));

st.test('with an earlier version', testError(
() => getLatestError('def', '0.0.5'),
latest,
[
'Attempting to publish v0.0.5 as "latest", but it is not later than v0.0.8.',
'\nPossible Solutions:',
'\t1) Provide a dist-tag: `npm publish --tag=backport`, for example',
'\t2) Use the very dangerous override: `PUBLISH_LATEST_DANGEROUSLY=true npm publish`',
],
));

st.test('with an existing version', testError(
() => getLatestError('def', '0.0.8'),
latest,
'Attempting to publish already-published version v0.0.8.',
));

st.test('with a later prerelease version', testError(
() => getLatestError('def', '9999.0.0-prerelease.0'),
latest,
[
'Attempting to publish v9999.0.0-prerelease.0 as "latest", but it is a prerelease version.',
'\nPossible Solutions:',
'\t1) Provide a dist-tag: `npm publish --tag=backport`, for example',
'\t2) Use the very dangerous override: `PUBLISH_LATEST_DANGEROUSLY=true npm publish`',
],
));

st.test('with an earlier prerelease version', testError(
() => getLatestError('def', '0.0.4-prerelease.0'),
latest,
[
'Attempting to publish v0.0.4-prerelease.0 as "latest", but it is a prerelease version.',
'\nPossible Solutions:',
'\t1) Provide a dist-tag: `npm publish --tag=backport`, for example',
'\t2) Use the very dangerous override: `PUBLISH_LATEST_DANGEROUSLY=true npm publish`',
],
));
});

t.test('nonexistent but valid package name', testResult(
() => getLatestError('abcdef123', '1.0.0'),
null,
'v1.0.0 is the first version published.',
));

t.test('nonexistent but valid scoped package name', testResult(
() => getLatestError('@ljharb/abcdef123', '1.0.0'),
null,
'v1.0.0 is the first version published.',
));

t.end();
});

0 comments on commit f44edd4

Please sign in to comment.