Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Internal improvements, add more tests #689

Merged
merged 66 commits into from Sep 2, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
d3df4f8
chore: start separating tests
tommy-mitchell Apr 6, 2023
636eb0b
tests(`git`): add tests for `git-util.js`
tommy-mitchell Apr 7, 2023
e822ae9
further separate tests, add `npm` tests, style
tommy-mitchell Apr 8, 2023
22cac30
fix(`git`): move tests to integration, fix `defaultBranch` tests
tommy-mitchell Apr 8, 2023
796595d
tests(`util`): add more tests
tommy-mitchell Apr 8, 2023
af48c4e
feat(`pretty-version-diff`): simplify and add tests
tommy-mitchell Apr 9, 2023
dce03f5
update `Version` API, remove `pretty-version-diff.js`, consolidate ta…
tommy-mitchell Apr 11, 2023
49378f4
fix(`test/index`): incorrect message expectation
tommy-mitchell Apr 11, 2023
993a53e
fix(`version`): passing test marked as failing
tommy-mitchell Apr 11, 2023
f6b988e
chore(`version`): rename `setNewVersionFrom` to `setFrom`
tommy-mitchell Apr 11, 2023
76dd173
chore: bump `semver`
tommy-mitchell Apr 11, 2023
75a2156
fix(`version`): get correct diff
tommy-mitchell Apr 11, 2023
722370b
chore(`version`): use new `semver.RELEASE_TYPES`
tommy-mitchell Apr 11, 2023
9097485
fix(`packed-files`): don't rename `.gitignore`
tommy-mitchell Apr 11, 2023
68997f1
feat(`version`): improved prerelease diffs, more docs
tommy-mitchell Apr 12, 2023
280c66e
feat(`version`): update/document API
tommy-mitchell Apr 13, 2023
21eeab1
tests(`prerequisite-tasks`): test is passing now?
tommy-mitchell Apr 13, 2023
d234532
tests: add more `git` and `npm` tests
tommy-mitchell Apr 14, 2023
d258504
fix: comment out unused imports
tommy-mitchell Apr 14, 2023
7a39e51
docs: update old info on readme
tommy-mitchell Apr 14, 2023
12a3650
add `ui`/`cli` tests, improve `customVersion` validation, style impro…
tommy-mitchell Apr 18, 2023
ef1d2e9
update CI to Node.js 20
tommy-mitchell Apr 18, 2023
b854b81
tests(`cli`): try to fix failure on CI
tommy-mitchell Apr 19, 2023
d675d1f
Merge branch 'main' into improve-tests
tommy-mitchell Apr 19, 2023
9d810a1
feat: add changes from #932, add tests
tommy-mitchell Apr 19, 2023
f2bc525
undo Node.js 20 on CI, issues with `esmock`
tommy-mitchell Apr 19, 2023
2b502c3
Merge branch 'main' into improve-tests
tommy-mitchell Jul 2, 2023
e403bf9
update deps
tommy-mitchell Jul 2, 2023
26accf9
fix(`npm`): incorrect version handling, missing async, style fix
tommy-mitchell Jul 4, 2023
366c8c3
suppress ESM warnings on tests
tommy-mitchell Jul 4, 2023
f5331c4
tests(`npm`): finish adding tests, seperate into own files
tommy-mitchell Jul 4, 2023
bab7bb2
mark tests as failing
tommy-mitchell Jul 4, 2023
438db1e
fix(`npm.collaborators`): revert version change
tommy-mitchell Jul 4, 2023
1ed67ac
remove reminders (added to #684)
tommy-mitchell Jul 4, 2023
da36e87
refactor(`cli`): move defaults into `meow` configuration
tommy-mitchell Jul 4, 2023
7534032
remove todos, update `chalk` usage, grammar tweak
tommy-mitchell Jul 4, 2023
649f7ef
update `esmock` to fix `ui` tests
tommy-mitchell Jul 30, 2023
f3c09b2
fix lint
tommy-mitchell Jul 30, 2023
0d262ea
fix lint
tommy-mitchell Jul 30, 2023
0413d1f
move `util` tests to separate files, fix failing tests, add test for…
tommy-mitchell Jul 31, 2023
676bdfd
move `git-util` tests to separate files
tommy-mitchell Jul 31, 2023
2eefd96
use `git add .` in tests
tommy-mitchell Jul 31, 2023
80cfc9d
fix: await create file
tommy-mitchell Jul 31, 2023
86c3ffd
tests(`hyperlink`): use `esmock`
tommy-mitchell Jul 31, 2023
213d374
fix(`hyperlinks`): mark tests as serial
tommy-mitchell Jul 31, 2023
f869534
tests(`hyperlinks`): properly mock `terminal-link`
tommy-mitchell Jul 31, 2023
a1618c1
rename `test/git` to `test/git-util`
tommy-mitchell Jul 31, 2023
784b2cd
tests(`git-util`): finish testing
tommy-mitchell Jul 31, 2023
53657bf
tests(`inquirer`): log debug messages with AVA
tommy-mitchell Jul 31, 2023
44a045a
handle todos, style tweaks
tommy-mitchell Jul 31, 2023
1cdf40b
fix lint
tommy-mitchell Jul 31, 2023
57c9df4
fix: correct config name
tommy-mitchell Jul 31, 2023
8d5ede5
document test helpers
tommy-mitchell Jul 31, 2023
bdc92fb
Merge branch 'improve-tests' of https://github.com/tommy-mitchell/np …
tommy-mitchell Jul 31, 2023
b286a28
remove todos
tommy-mitchell Jul 31, 2023
4aa52f8
fix: move `chalk-template` to regular deps
tommy-mitchell Jul 31, 2023
f140762
try resolving `util.readPkg` error
tommy-mitchell Aug 2, 2023
b06fc98
better resolution of issue
tommy-mitchell Aug 2, 2023
6ba3b4b
fixes
tommy-mitchell Aug 31, 2023
cd16b94
update deps
tommy-mitchell Aug 31, 2023
c92873c
fix: lint
tommy-mitchell Aug 31, 2023
361b609
fix: missing option in test
tommy-mitchell Aug 31, 2023
e3c5e5c
fix: ignore some checks when `--release-draft-only`
tommy-mitchell Aug 31, 2023
f98b85d
fix(`prerequisite-tasks`): don't double parse version
tommy-mitchell Sep 1, 2023
c6249a2
version style tweaks
tommy-mitchell Sep 1, 2023
a682b51
semver order
tommy-mitchell Sep 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions package.json
Expand Up @@ -71,16 +71,22 @@
},
"devDependencies": {
"ava": "^5.2.0",
"chalk-template": "^1.0.0",
"common-tags": "^1.8.2",
"esmock": "^2.2.1",
"fs-extra": "^11.1.1",
"move-file": "^3.1.0",
"sinon": "^15.0.3",
"strip-ansi": "^7.0.1",
"tempy": "^3.0.0",
"write-pkg": "^5.1.0",
"xo": "^0.53.1"
},
"ava": {
"files": [
"!test/fixtures",
"!test/_helpers"
],
"environmentVariables": {
"FORCE_HYPERLINK": "1"
},
Expand Down
8 changes: 4 additions & 4 deletions source/cli-implementation.js
Expand Up @@ -10,7 +10,7 @@ import config from './config.js';
import * as util from './util.js';
import * as git from './git-util.js';
import * as npm from './npm/util.js';
import Version from './version.js';
import {SEMVER_INCREMENTS} from './version.js';
import ui from './ui.js';
import np from './index.js';

Expand All @@ -19,7 +19,7 @@ const cli = meow(`
$ np <version>

Version can be:
${Version.SEMVER_INCREMENTS.join(' | ')} | 1.2.3
${SEMVER_INCREMENTS.join(' | ')} | 1.2.3

Options
--any-branch Allow publishing from any branch
Expand Down Expand Up @@ -130,8 +130,8 @@ try {
isUnknown: false,
};

// Use current (latest) version when 'releaseDraftOnly', otherwise use the first argument.
const version = flags.releaseDraftOnly ? pkg.version : (cli.input.length > 0 ? cli.input[0] : false);
// Use current (latest) version when 'releaseDraftOnly', otherwise try to use the first argument.
const version = flags.releaseDraftOnly ? pkg.version : (cli.input.at(0) ?? false); // TODO: can this be undefined?

const branch = flags.branch || await git.defaultBranch();
const options = await ui({
Expand Down
69 changes: 34 additions & 35 deletions source/git-util.js
Expand Up @@ -2,7 +2,7 @@ import path from 'node:path';
import {execa} from 'execa';
import escapeStringRegexp from 'escape-string-regexp';
import ignoreWalker from 'ignore-walk';
import Version from './version.js';
import * as util from './util.js';

export const latestTag = async () => {
const {stdout} = await execa('git', ['describe', '--abbrev=0', '--tags']);
Expand Down Expand Up @@ -38,6 +38,12 @@ export const readFileFromLastRelease = async file => {
return oldFile;
};

const tagList = async () => {
// Returns the list of tags, sorted by creation date in ascending order.
const {stdout} = await execa('git', ['tag', '--sort=creatordate']);
return stdout.split('\n');
};

const firstCommit = async () => {
const {stdout} = await execa('git', ['rev-list', '--max-parents=0', 'HEAD']);
return stdout;
Expand Down Expand Up @@ -97,12 +103,6 @@ export const verifyCurrentBranchIsReleaseBranch = async releaseBranch => {
}
};

export const tagList = async () => {
// Returns the list of tags, sorted by creation date in ascending order.
const {stdout} = await execa('git', ['tag', '--sort=creatordate']);
return stdout.split('\n');
};

export const isHeadDetached = async () => {
try {
// Command will fail with code 1 if the HEAD is detached.
Expand All @@ -113,7 +113,7 @@ export const isHeadDetached = async () => {
}
};

export const isWorkingTreeClean = async () => {
const isWorkingTreeClean = async () => {
try {
const {stdout: status} = await execa('git', ['status', '--porcelain']);
if (status !== '') {
Expand Down Expand Up @@ -182,7 +182,27 @@ export const fetch = async () => {
await execa('git', ['fetch']);
};

export const tagExistsOnRemote = async tagName => {
const hasLocalBranch = async branch => {
try {
await execa('git', ['show-ref', '--verify', '--quiet', `refs/heads/${branch}`]);
return true;
} catch {
return false;
}
};

export const defaultBranch = async () => {
for (const branch of ['main', 'master', 'gh-pages']) {
// eslint-disable-next-line no-await-in-loop
if (await hasLocalBranch(branch)) {
return branch;
}
}

throw new Error('Could not infer the default Git branch. Please specify one with the --branch flag or with a np config.');
};

const tagExistsOnRemote = async tagName => {
try {
const {stdout: revInfo} = await execa('git', ['rev-parse', '--quiet', '--verify', `refs/tags/${tagName}`]);

Expand All @@ -202,26 +222,6 @@ export const tagExistsOnRemote = async tagName => {
}
};

async function hasLocalBranch(branch) {
try {
await execa('git', ['show-ref', '--verify', '--quiet', `refs/heads/${branch}`]);
return true;
} catch {
return false;
}
}

export const defaultBranch = async () => {
for (const branch of ['main', 'master', 'gh-pages']) {
// eslint-disable-next-line no-await-in-loop
if (await hasLocalBranch(branch)) {
return branch;
}
}

throw new Error('Could not infer the default Git branch. Please specify one with the --branch flag or with a np config.');
};

export const verifyTagDoesNotExistOnRemote = async tagName => {
if (await tagExistsOnRemote(tagName)) {
throw new Error(`Git tag \`${tagName}\` already exists.`);
Expand All @@ -233,6 +233,10 @@ export const commitLogFromRevision = async revision => {
return stdout;
};

const push = async () => {
await execa('git', ['push', '--follow-tags']);
};

export const pushGraceful = async remoteIsOnGitHub => {
try {
await push();
Expand All @@ -247,10 +251,6 @@ export const pushGraceful = async remoteIsOnGitHub => {
}
};

export const push = async () => {
await execa('git', ['push', '--follow-tags']);
};

export const deleteTag = async tagName => {
await execa('git', ['tag', '--delete', tagName]);
};
Expand All @@ -267,8 +267,7 @@ const gitVersion = async () => {

export const verifyRecentGitVersion = async () => {
const installedVersion = await gitVersion();

Version.verifyRequirementSatisfied('git', installedVersion);
util.validateEngineVersionSatisfies('git', installedVersion);
};

export const checkIfFileGitIgnored = async pathToFile => {
Expand Down
105 changes: 44 additions & 61 deletions source/index.js
Expand Up @@ -83,10 +83,13 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => {
if (publishStatus === 'FAILED') {
await rollback();
} else {
console.log('\nAborted!');
console.log('\nAborted!'); // TODO: maybe only show 'Aborted!' if user cancels?
}
}, {minimumWait: 2000});

const shouldEnable2FA = options['2fa'] && options.availability.isAvailable && !options.availability.isUnknown && !pkg.private && !npm.isExternalRegistry(pkg);

// TODO: move tasks to subdirectory
const tasks = new Listr([
{
title: 'Prerequisite check',
Expand All @@ -97,13 +100,7 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => {
title: 'Git',
task: () => gitTasks(options),
},
], {
showSubtasks: false,
renderer: options.renderer ?? 'default',
});

if (runCleanup) {
tasks.add([
...runCleanup ? [
{
title: 'Cleanup',
enabled: () => !hasLockFile,
Expand Down Expand Up @@ -136,11 +133,8 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => {
return exec('npm', [...args, '--engine-strict']);
},
},
]);
}

if (runTests) {
tasks.add([
] : [],
...runTests ? [
{
title: 'Running tests using npm',
enabled: () => options.yarn === false,
Expand All @@ -159,10 +153,7 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => {
}),
),
},
]);
}

tasks.add([
] : [],
{
title: 'Bumping version using Yarn',
enabled: () => options.yarn === true,
Expand Down Expand Up @@ -211,10 +202,7 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => {
return exec('npm', args);
},
},
]);

if (options.runPublish) {
tasks.add([
...options.runPublish ? [
{
title: `Publishing package using ${pkgManagerName}`,
skip() {
Expand All @@ -239,49 +227,37 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => {
);
},
},
]);

const isExternalRegistry = npm.isExternalRegistry(pkg);
if (options['2fa'] && options.availability.isAvailable && !options.availability.isUnknown && !pkg.private && !isExternalRegistry) {
tasks.add([
{
title: 'Enabling two-factor authentication',
skip() {
if (options.preview) {
const args = enable2fa.getEnable2faArgs(pkg.name, options);
return `[Preview] Command not executed: npm ${args.join(' ')}.`;
}
},
task: (context, task) => enable2fa(task, pkg.name, {otp: context.otp}),
...shouldEnable2FA ? [{
title: 'Enabling two-factor authentication',
skip() {
if (options.preview) {
const args = enable2fa.getEnable2faArgs(pkg.name, options);
return `[Preview] Command not executed: npm ${args.join(' ')}.`;
}
},
]);
}
} else {
publishStatus = 'SUCCESS';
}

tasks.add({
title: 'Pushing tags',
async skip() {
if (!(await git.hasUpstream())) {
return 'Upstream branch not found; not pushing.';
}
task: (context, task) => enable2fa(task, pkg.name, {otp: context.otp}),
}] : [],
] : [],
{
title: 'Pushing tags',
async skip() {
if (!(await git.hasUpstream())) {
return 'Upstream branch not found; not pushing.';
}

if (options.preview) {
return '[Preview] Command not executed: git push --follow-tags.';
}
if (options.preview) {
return '[Preview] Command not executed: git push --follow-tags.';
}

if (publishStatus === 'FAILED' && options.runPublish) {
return 'Couldn\'t publish package to npm; not pushing.';
}
},
async task() {
pushedObjects = await git.pushGraceful(isOnGitHub);
if (publishStatus === 'FAILED' && options.runPublish) {
return 'Couldn\'t publish package to npm; not pushing.';
}
},
async task() {
pushedObjects = await git.pushGraceful(isOnGitHub);
},
},
});

if (options.releaseDraft) {
tasks.add({
...options.releaseDraft ? [{
title: 'Creating release draft on GitHub',
enabled: () => isOnGitHub === true,
skip() {
Expand All @@ -290,7 +266,14 @@ const np = async (input = 'patch', options, {pkg, rootDir}) => {
}
},
task: () => releaseTaskHelper(options, pkg),
});
}] : [],
], {
showSubtasks: false,
renderer: options.renderer ?? 'default',
});

if (!options.runPublish) {
publishStatus = 'SUCCESS';
}

await tasks.run();
Expand Down
2 changes: 1 addition & 1 deletion source/npm/handle-npm-error.js
Expand Up @@ -27,7 +27,7 @@ const handleNpmError = (error, task, message, executor) => {
// Attempting to privately publish a scoped package without the correct npm plan
// https://stackoverflow.com/a/44862841/10292952
if (error.code === 402 || error.stderr.includes('npm ERR! 402 Payment Required')) {
throw new Error('You cannot publish a privately scoped package without a paid plan. Did you mean to publish publicly?');
throw new Error('You cannot publish a scoped package privately without a paid plan. Did you mean to publish publicly?');
}

return throwError(() => error);
Expand Down