Skip to content

Commit

Permalink
feat!: oclif (take 2) (#1068)
Browse files Browse the repository at this point in the history
## 🧰 Changes

i took a stab at this in
[#962](#962) but ran into issues at
the time but oclif has since shipped a lot of handy features (and plus
the work that folks did on `owl` got me excited about oclif again) so i
decided to revive this and i got it all working 🥹

here's a high-level overview:
- [x] refactors all commands, associated helper functions, and github
actions build process to be oclif-friendly
- [x] converts all tests to use oclif test utilities


BREAKING CHANGE: the `rdme` GitHub Actions is now a [the `node20` JavaScript action](https://docs.github.com/en/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#runs-for-javascript-actions) rather than [a Docker container action](https://docs.github.com/en/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#runs-for-docker-container-actions).

BREAKING CHANGE: `rdme` is now powered by [`oclif`](https://oclif.io/). The formatting and content of certain error messages and outputs may have changed. Please continue to only utilize exit codes to determine command successes/failures.

completed tasks:

- [x] a handful of tests are still failing, hoping that
oclif/test#652 gets merged in)
- [x] github actions dry runs are failing (but i got them working in
[#1067](https://github.com/readmeio/rdme/pull/1067))[^1]
- [x] remove all docker build/tagging/release code
- [x] update `semantic-release` workflow to support this new process.
here are the assets we'll need to consider every time we tag a release
([link](https://github.com/readmeio/rdme/blob/d01d76fe3c2e4a98b4f5c415be03e589fbe3b56e/.releaserc.yml#L30)):
  - [x] command list README (i.e., the one generated by `oclif readme`
- [x] we do **not** need to update `action.yml` on every release anymore
- [x] everything in `dist-gha` (the two JS files ~~and `package.json`~~)
- [x] (do we need an `oclif` manifest in this directory? probably not?)
- [x] we might not actually need to track this duplicated `package.json`
— what if we used the
[`runs.pre`](https://docs.github.com/en/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#runspre)
script to generate it?
  - [x] package/package-lock.json
  - [x] CHANGELOG.md
- [x] `oclif` manifest (this one is interesting... we probably shouldn't
git track it but it should be included in our npm assets)

## 🧬 QA & Testing

to test this out, check out this branch and play around:

```sh
npm ci
bin/dev.js whoami
```

i've confirmed in a separate repo that the action works as expected:
https://github.com/readmeio/oas-examples/actions/runs/11902772744/job/33168462740

i ticketed a few follow-ups in RM-11447

(resolves RM-8260)

[^1]: i'm going to move away from the docker-based github action builds
— it's pretty complex and unfortunately doesn't play nicely with oclif.
i figured out a way to get it working with pre-built JS dists in
[#1067](#1067) that is just as fast
(if not faster) than before.
  • Loading branch information
kanadgupta authored Nov 19, 2024
1 parent 6df2f0d commit 5e05f93
Show file tree
Hide file tree
Showing 113 changed files with 8,820 additions and 6,575 deletions.
5 changes: 4 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# test result artifacts
coverage/

# release build artifacts
dist/
exe/
dist-gha/
4 changes: 2 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@
/**
* This is a small rule to prevent us from using console.log() statements in our commands.
*
* We've had troubles in the past where our test coverage required us to use Jest mocks for
* We've had troubles in the past where our test coverage required us to use Vitest mocks for
* console.log() calls, hurting our ability to write resilient tests and easily debug issues.
*
* We should be returning Promise-wrapped values in our main command functions
* so we can write robust tests and take advantage of `bin/rdme.js`,
* so we can write robust tests and take advantage of `bin/run.js` and `src/baseCommand.ts`,
* which we use for printing function outputs and returning correct exit codes.
*
* Furthermore, we should also be using our custom loggers (see src/lib/logger.js)
Expand Down
19 changes: 8 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,14 @@ jobs:
path: oas-examples-repo
repository: readmeio/oas-examples

# For every GitHub Action release, we deploy our Docker images
# to the GitHub Container Registry for performance reasons.
# Instead of testing against the pre-built image, we can build
# an image from the currently checked-out repo contents by doing a
# li'l update in the action.yml file to point to the current Dockerfile.
- name: Replace Docker image value in action.yml
uses: jacobtomlinson/gha-find-replace@v3
with:
find: 'image:.*'
replace: "image: 'Dockerfile'"
include: rdme-repo/action.yml
# Since this workflow file is in the `rdme` repository itself,
# we need to test the GitHub Action using the current commit.
# This step builds the `rdme` action code so we can do that.
#
# This step is not required for syncing your docs to ReadMe!
- name: Rebuild GitHub Action for testing purposes
run: npm ci && npm run build:gha
working-directory: rdme-repo

- name: Run `openapi:validate` command
uses: ./rdme-repo/
Expand Down
18 changes: 7 additions & 11 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,13 @@ jobs:
regex: false
include: documentation/*

# For every GitHub Action release, we deploy our Docker images
# to the GitHub Container Registry for performance reasons.
# Instead of testing against the pre-built image, we can build
# an image from the currently checked-out repo contents by doing a
# li'l update in the action.yml file to point to the current Dockerfile.
- name: Replace Docker image value in action.yml
uses: jacobtomlinson/gha-find-replace@v3
with:
find: 'image:.*'
replace: "image: 'Dockerfile'"
include: action.yml
# Since this workflow file is in the `rdme` repository itself,
# we need to test the GitHub Action using the current commit.
# This step builds the `rdme` action code so we can do that.
#
# This step is not required for syncing your docs to ReadMe!
- name: Rebuild GitHub Action for testing purposes
run: npm run build:gha

# And finally, with our updated documentation,
# we run the `rdme` GitHub Action to sync the Markdown file
Expand Down
31 changes: 0 additions & 31 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,37 +33,6 @@ jobs:
- name: Install semantic-release and friends
run: npm i --no-save semantic-release@24 @semantic-release/changelog@6 @semantic-release/exec@6 @semantic-release/git@10

# We do a dry run here to analyze the commits and grab the next version
# for usage in the Docker metadata action
- name: Dry run of semantic-release workflow
run: npx semantic-release --dry-run
id: release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# For every release we add 2-3 tags to the docker build:
# 1. A semver tag (e.g., 8.0.0, 8.0.0-next.0)
# 2. A branch tag (e.g., next, main)
# 3. A `latest` tag (only on the main branch)
tags: |
type=semver,pattern={{version}},value=${{ steps.release.outputs.nextVersion }}
type=ref,event=branch
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
- name: Run semantic-release workflow
env:
GH_TOKEN: ${{ secrets.RELEASE_GH_TOKEN }}
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
.env
coverage/
dist/
exe/
node_modules/

# required for building with TS + oclif + GitHub Actions
dist-gha/package.json
oclif.manifest.json
src/package.json
10 changes: 0 additions & 10 deletions .npmignore

This file was deleted.

10 changes: 8 additions & 2 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# invalid files
__tests__/__fixtures__/invalid-json/yikes.json
CHANGELOG.md

# test result artifacts
coverage/

# release build artifacts
CHANGELOG.md
dist/
exe/
documentation/commands.md
dist-gha/
21 changes: 10 additions & 11 deletions .releaserc.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
$schema: https://json.schemastore.org/semantic-release.json
branches:
- name: main
- name: next
Expand All @@ -14,11 +15,6 @@ plugins:
- [
'@semantic-release/exec',
{
# Runs two commands:
# 1. Updates our action.yml file to reflect current Docker image version.
# This needs to happen before `@semantic-release/git` so we can commit this file.
# 2. Builds and pushes the Docker image
'prepareCmd': './bin/set-action-image.js && ./bin/docker.js',
# Adds a major version git tag (e.g., v8) as a convenience for GitHub Actions users
# We need to run this in the publish phase so it can be force-pushed separately from the other tags
'publishCmd': './bin/set-major-version-tag.js push',
Expand All @@ -27,18 +23,21 @@ plugins:
- [
'@semantic-release/git',
{
assets: ['action.yml', 'CHANGELOG.md', 'package.json', 'package-lock.json'],
assets:
[
'CHANGELOG.md',
'documentation/commands.md',
'package.json',
'package-lock.json',
'dist-gha/commands.js',
'dist-gha/run.js',
],
message: "build(release): 🚀 v${nextRelease.version} 🦉\n\n${nextRelease.notes}\n[skip ci]",
},
]
- [
'@semantic-release/exec',
{
# Verify existence of docker CLI
'verifyConditionsCmd': './bin/verify-clis.sh',
# Sets the next version as a GitHub Actions output parameter for usage in subsequent workflow steps
# https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter
'verifyReleaseCmd': 'echo nextVersion=${nextRelease.version} >> $GITHUB_OUTPUT',
# Adds an additional git tag without the `v` prefix as a convenience for GitHub Actions users
'prepareCmd': 'git tag ${nextRelease.version}',
},
Expand Down
29 changes: 24 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,38 @@

## Running Shell Commands Locally 🐚

To run test commands from within the repository, run the build and then run your commands from the root of the repository and use `./bin/rdme.js` instead of `rdme` so it properly points to the command executable, like so:
To get started, run the `build` script to create a symlink with `package.json` (required for our `oclif` setup to read our commands properly). You only need to do this the first time you clone the repository.

```sh
npm run build
./bin/rdme.js openapi:validate __tests__/__fixtures__/ref-oas/petstore.json
```

If you need to debug commands quicker and re-building TS everytime is becoming cumbersome, you can use the debug command, like so:
To run test commands, use `./bin/dev.js` instead of `rdme`. For example, if the command you're testing looks like this...

```sh
npm run debug -- openapi:validate __tests__/__fixtures__/ref-oas/petstore.json
rdme openapi:validate __tests__/__fixtures__/ref-oas/petstore.json
```

... your local command will look like this:

```sh
bin/dev.js openapi:validate __tests__/__fixtures__/ref-oas/petstore.json
```

The `bin/dev.js` file has a few features that are useful for local development:

- It reads directly from your TypeScript files (so no need to re-run the TypeScript compiler every time you make a change)
- It returns error messages with full stack traces

`bin/dev.js` is convenient for useful for rapid development but it's not a 1:1 recreation of what the end-user experience with `rdme` is like. To recreate the production `rdme` experience, use the `bin/run.js` file instead. You'll need to re-run the TypeScript compiler (i.e., `npm run build`) every time you make a change. So for example:

```sh
npm run build
bin/run.js openapi:validate __tests__/__fixtures__/ref-oas/petstore.json
```

Your changes to the command code may make changes to [the command reference document](./documentation/commands.md) — it is up to you whether you include those changes in your PR or if you let the release process take care of it. More information on that can be found in [MAINTAINERS.md](./MAINTAINERS.md).

## Running GitHub Actions Locally 🐳

To run GitHub Actions locally, we'll be using [`act`](https://github.com/nektos/act) (make sure to read their [prerequisites list](https://github.com/nektos/act#necessary-prerequisites-for-running-act) and have that ready to go before installing `act`)!
Expand Down Expand Up @@ -70,7 +89,7 @@ act -j simple

### Usage of `console`

As you'll learn in our commands logic (see [`bin/rdme.js`](bin/rdme.js) and the [`src/cmds`](src/cmds) directory), we wrap our command outputs in resolved/rejected [`Promise` objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) and use [`bin/rdme.js`](bin/rdme.js) file to log the results to the console and return the correct status code. This is so we can write more resilient tests, ensure that the proper exit codes are being returned, and make debugging easier.
As you'll learn in our commands logic (see [`bin/run.js`](bin/run.js) and the [`src/cmds`](src/cmds) directory), we wrap our command outputs in resolved/rejected [`Promise` objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) and use [`bin/run.js`](bin/run.js) file to log the results to the console and return the correct status code. This is so we can write more resilient tests, ensure that the proper exit codes are being returned, and make debugging easier.

When writing command logic, avoid using `console` statements (and correspondingly, avoid mocking `console` statements in tests) when possible.

Expand Down
11 changes: 0 additions & 11 deletions Dockerfile

This file was deleted.

13 changes: 6 additions & 7 deletions MAINTAINERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,24 @@ Nearly all of our release process is automated. In this section, we discuss ever
When code is merged into the `main` or `next` branches, a release workflow (powered by [`semantic-release`](https://github.com/semantic-release/semantic-release)) automatically kicks off that does the following:

- All commit messages since the last release are analyzed to determine whether or not the new changes warrant a new release (i.e., if the changes are features or fixes as opposed to smaller housekeeping changes) 🧐
- Based on the changes, the version is bumped in [`package.json`](./package.json) and the [`action.yml`](./action.yml) with a new git tag 🏷️ For example, say the current version is `8.5.1` and the commit history includes a new feature. This would result in a minor semver bump, which would produce the following tags:
- Based on the changes, the version is bumped in [`package.json`](./package.json) 🥊 For example, say the current version is `8.5.1` and the commit history includes a new feature. This would result in a minor semver bump, which would produce the following tags:
- A release tag like `v8.6.0` if on the `main` branch
- A prerelease tag like `v8.6.0-next.1` if on the `next` branch
- A changelog is generated and appended to [`CHANGELOG.md`](./CHANGELOG.md) 🪵
- A build commit (like [this](https://github.com/readmeio/rdme/commit/533a2db50b39c3b6130b3af07bebaed38218db4c)) is created with the updated `package*.json` and `CHANGELOG.md` files 🆕
- A new Docker image is built with the latest code and release metadata and pushed to [the GitHub Container Registry](https://github.com/readmeio/rdme/pkgs/container/rdme) 🐳
- A few other files, such as [`CHANGELOG.md`](./CHANGELOG.md), [the command reference page](./documentation/commands.md), and our GitHub Actions bundle files, are updated based on this code 🪵
- A build commit (like [this](https://github.com/readmeio/rdme/commit/533a2db50b39c3b6130b3af07bebaed38218db4c)) is created with all of the updated files (e.g., `package.json`, `CHANGELOG.md`, etc.) 🆕
- A couple duplicated tags are created for the current commit so our users can refer to them differently in their GitHub Actions (e.g., `8.6.0`, `v8`) 🔖
- The new commit and tags are pushed to GitHub 📌
- The new version is published to the `npm` registry 🚀 The package [distribution tag](https://docs.npmjs.com/adding-dist-tags-to-packages) will depend on which branch is being pushed to:
- If on the `main` branch, a version is pushed on the main distribution tag (a.k.a. `latest`, which is used when someone runs `npm i rdme` with no other specifiers).
- If on the `next` branch, the prerelease distribution tag (a.k.a. [`next`](https://www.npmjs.com/package/rdme/v/next)) is updated.
- A [GitHub release is created](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository#creating-a-release) for the tag (in draft form) 🐙
- A [GitHub release is created](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository#creating-a-release) for the tag 🐙
- If on the `main` branch, the new changes are backported to the `next` branch so both branches remain in sync 🔄

## One more thing ☝️

> [!NOTE]
> This section is only required if you're building an actual release and not a prelease (i.e., changes are being merged into the `main` branch).
> This section is only worth adhering to if you're building an actual release and not a prelease (i.e., changes are being merged into the `main` branch).
While nearly all of our release process is automated, there's one more step left before we call it a day — writing up and publishing [the GitHub release](https://github.com/readmeio/rdme/releases)! The automation creates a GitHub release in a draft state, but it needs to be published so the latest version is surfaced to folks that discover our tool via [the GitHub Marketplace listing](https://github.com/marketplace/actions/rdme-sync-to-readme).
While nearly all of our release process is automated, there's one more step left before we call it a day — enhancing [the GitHub release](https://github.com/readmeio/rdme/releases)! The automation auto-generates and publishes a GitHub release, but it's often nice to add some human-generated language for folks that discover our tool via [the GitHub Marketplace listing](https://github.com/marketplace/actions/rdme-sync-to-readme).

I like to summarize the changes and note any highlights in a blurb above the auto-generated release notes. These release links are nice for sharing with customers, on socials, etc. and because of the way that GitHub's algorithm works, they present a great opportunity for our developer tools to get more visibility — so definitely worth putting a little thought and care into these!
4 changes: 2 additions & 2 deletions __tests__/bin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ describe('bin', () => {
expect.assertions(1);

await new Promise(resolve => {
exec(`node ${__dirname}/../bin/rdme.js`, (error, stdout) => {
expect(stdout).toContain('a utility for interacting with ReadMe');
exec(`node ${__dirname}/../bin/run.js`, (error, stdout) => {
expect(stdout).toContain("ReadMe's official CLI and GitHub Action");
resolve(true);
});
});
Expand Down
3 changes: 0 additions & 3 deletions __tests__/cmds/__snapshots__/whoami.test.ts.snap

This file was deleted.

Loading

0 comments on commit 5e05f93

Please sign in to comment.