CLI to bump package.json version and add git tags
(Disclaimer: I'm not a JavaScript expert and I don't have much experience with async/await either, so take my advices with a grain of salt. I hope you will find it helpful anyway.)

Running async calls in parallel

If you assume these will run in parallel, they won't:


// both of these may run together
await git.push();
await git.pushTags();



To make them run in parallel, you could use Promise.all:

await Promise.all([git.push(), git.pushTags()]);


Or save the result of the calls without await, and await on the results:

const pushResult = git.push();
const pushTagsResult = git.pushTags();
await pushResult;
await pushTagsResult;


Not too much async and spinners?

Most of the operations are fast,
such as updating the file, git add, and git commit,
and I don't see a good reason to make them run in parallel and with a progress indicator.

The only operation that may be slow and where the progress indicator makes sense is the git push.

Since the main logic is not packaged as reusable code that can be imported and called from another module,
wrapping it in async (in the IIFE at the end) seems unnecessary too.
If the updating of the package was separated from the command line parsing,
then it could make sense to make it possible to run async,
if you had a use case for that.
Without a use case, I think it's over-engineering.

Addressing your questions


  A lot of my code is wrapped in try-catch blocks which I understand is not supposed to be the case with async/await or Promises. I'm not sure how to fix this.


I don't see anything wrong with your try-catch blocks (but see the disclaimer at the top).


  There's a lot of duplication with the spinner initialisation code. I am aware the ora has an ora.promise method as well. I couldn't get this to work.


As mentioned earlier, I think the spinners are overkill.
However, even if you remove them from most places,
much duplication will still remain.

I don't think you need the granularity of catching exception at every step.
You could wrap those steps in a single try-catch block,
and that will reduce the duplication and boilerplate code.


  I haven't added much error checking yet. For example, later, I'd like to check if a package.json exists, and if it doesn't, get the current version from git tags. How can I structure my code in such a way that I can easily add such functionality in the future?


The way you did so far seems fine to me.
If later you want to get tags from Git,
you can add a function for that,
and call it from nextVersion as needed.