Skip to content

Commit ca0667b

Browse files
authored
feat!: upgrade to oclif/core v4 (#180)
* Replace anykey with inquirer * migrate to oclif/core v4 * Switch to npm from yarn * Update dependabot.yml * Add conventional changelog * Fix browser open to login, add script for running example, fill out README * Remove legacy client, update packages, switch to node uuid * Update sinon * Upgrade to eslint 8, apply rules fixes * Add a few more examples, adjust lint rules * Update yargs-parser * Add interactive option to example, fix output * lint cleanup
1 parent 369c860 commit ca0667b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+12587
-6050
lines changed

.circleci/config.yml

-32
This file was deleted.

.eslintrc

-24
This file was deleted.

.eslintrc.js

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
module.exports = {
2+
extends: [
3+
'oclif',
4+
'oclif-typescript',
5+
],
6+
overrides: [
7+
{
8+
files: ['test/**/*.ts', 'test/**/*.js'],
9+
rules: {
10+
'prefer-arrow-callback': 'off',
11+
'unicorn/consistent-destructuring': 'warn',
12+
},
13+
},
14+
{
15+
files: ['examples/**/*.ts', 'examples/**/*.js'],
16+
rules: {
17+
'unicorn/prefer-module': 'off',
18+
'unicorn/prefer-top-level-await': 'off',
19+
},
20+
},
21+
],
22+
plugins: ['import'],
23+
rules: {
24+
'@typescript-eslint/no-explicit-any': 'warn',
25+
camelcase: 'off',
26+
'import/namespace': 'warn',
27+
indent: ['error', 2, {MemberExpression: 1}],
28+
'no-useless-constructor': 'warn',
29+
'unicorn/consistent-function-scoping': 'off',
30+
'unicorn/import-style': 'warn',
31+
'unicorn/no-array-for-each': 'off',
32+
'unicorn/no-array-push-push': 'warn',
33+
'unicorn/no-static-only-class': 'off',
34+
'unicorn/numeric-separators-style': 'off',
35+
'unicorn/prefer-array-some': 'warn',
36+
'unicorn/prefer-node-protocol': 'warn',
37+
'unicorn/prefer-string-replace-all': 'off',
38+
},
39+
}

.github/CODEOWNERS

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../CODEOWNERS

.github/dependabot.yml

+26-9
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,29 @@
1-
# To get started with Dependabot version updates, you'll need to specify which
2-
# package ecosystems to update and where the package manifests are located.
3-
# Please see the documentation for all configuration options:
4-
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5-
61
version: 2
72
updates:
8-
- package-ecosystem: "npm"
9-
directory: "/"
10-
open-pull-requests-limit: 5
11-
schedule:
3+
- package-ecosystem: "github-actions"
4+
directory: "/"
5+
open-pull-requests-limit: 5
6+
schedule:
127
interval: "weekly"
8+
time: "12:00"
9+
day: "sunday"
10+
timezone: "America/Los_Angeles"
11+
- package-ecosystem: "npm"
12+
directory: "/"
13+
open-pull-requests-limit: 5
14+
schedule:
15+
interval: "weekly"
16+
time: "12:00"
17+
day: "sunday"
18+
timezone: "America/Los_Angeles"
19+
groups:
20+
dev-deps:
21+
dependency-type: "development"
22+
patch-dependencies:
23+
update-types:
24+
- "patch"
25+
ignore:
26+
- dependency-name: "@oclif/core"
27+
update-types: ["version-update:semver-major"]
28+
- dependency-name: "typescript"
29+
update-types: ["version-update:semver-major"]

.github/workflows/ci.yml

+5-7
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,8 @@ jobs:
1616
uses: actions/setup-node@v4
1717
with:
1818
node-version: ${{ matrix.node-version }}
19-
cache: yarn
20-
- run: yarn --frozen-lockfile --network-timeout 1000000
21-
env:
22-
CI: true
23-
- run: yarn test
24-
env:
25-
CI: true
19+
- run: npm ci
20+
- name: unit tests
21+
run: npm test
22+
- name: linting
23+
run: npm run lint

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
/tmp
66
lib/
77
node_modules/
8-
package-lock.json
8+
yarn.lock
99
.idea/

.tool-versions

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
nodejs 20.18.3
1+
nodejs 22.15.0

CODEOWNERS

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# All files require review from the Heroku Front-End Dev Tools team.
2+
3+
# Comment line immediately above ownership line is reserved for related GUS information.
4+
# Please exercise caution while editing.
5+
6+
#GUSINFO: Heroku FE Dev Tools,Heroku CLI & Plugins
7+
* @heroku/frontend-dev-tooling
8+
19
#ECCN:Open Source
2-
#GUSINFO:Heroku FE Dev Tools,Heroku CLI & Plugins
3-
* @heroku/front-end

README.md

+51
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,54 @@ Base class for Heroku CLI commands. Built off of [oclif](https://oclif.io).
88
[![Known Vulnerabilities](https://snyk.io/test/npm/@heroku-cli/command/badge.svg)](https://snyk.io/test/npm/@heroku-cli/command)
99
[![Downloads/week](https://img.shields.io/npm/dw/@heroku-cli/command.svg)](https://npmjs.org/package/@heroku-cli/command)
1010
[![License](https://img.shields.io/npm/l/@heroku-cli/command.svg)](https://github.com/heroku/heroku-cli-command/blob/master/package.json)
11+
12+
## Overview
13+
14+
This package provides the core functionality for Heroku CLI commands, including a comprehensive set of completion handlers for various Heroku resources. It serves as the foundation for building Heroku CLI commands with built-in support for command-line completion.
15+
16+
## Features
17+
18+
### Completion Handlers
19+
20+
The package includes completion handlers for various Heroku resources:
21+
22+
- **Apps**: Autocomplete for Heroku application names
23+
- **Addons**: Autocomplete for add-ons associated with specific apps
24+
- **Dynos**: Autocomplete for dyno names within apps
25+
- **Buildpacks**: Common Heroku buildpack options
26+
- **Dyno Sizes**: Available dyno size options
27+
- **Files**: Local file system completion
28+
- **Pipelines**: Heroku pipeline names
29+
- **Process Types**: Process types from Procfile
30+
- **Regions**: Available Heroku regions
31+
- **Git Remotes**: Git remote names
32+
- **Roles**: User role options (admin, collaborator, member, owner)
33+
- **Scopes**: Permission scope options
34+
- **Spaces**: Heroku Private Spaces
35+
- **Stacks**: Available Heroku stacks
36+
- **Stages**: Pipeline stage options
37+
- **Teams**: Heroku team names
38+
39+
### APIClient
40+
41+
The package includes a built-in `APIClient` for making authenticated requests to the Heroku Platform API:
42+
43+
- Handles authentication and request formatting
44+
- Provides a simple interface for making GET requests to Heroku resources
45+
- Automatically parses JSON responses
46+
- Used internally by completion handlers to fetch resource lists
47+
- Supports configurable request options through the CLI config
48+
49+
Example usage:
50+
```typescript
51+
const heroku = new APIClient(config)
52+
const {body: resources} = await heroku.get('/apps')
53+
```
54+
55+
## Usage
56+
57+
This package is primarily used as a dependency in other Heroku CLI plugins and commands. It provides the base functionality needed to implement Heroku CLI commands with proper completion support.
58+
59+
## Development
60+
61+
Built with TypeScript and uses the [oclif](https://oclif.io) framework for CLI command development.

examples/favorites.ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {ux} from '@oclif/core'
2+
3+
import {Command} from '../src'
4+
5+
type Favorite = {
6+
id: string;
7+
resource_id: string;
8+
resource_name: string;
9+
type: string;
10+
}
11+
12+
type Favorites = Favorite[]
13+
14+
class FavoritesCommand extends Command {
15+
async run() {
16+
const {body: favorites} = await this.heroku.get<Favorites>(
17+
'/favorites?type=app',
18+
{hostname: 'particleboard.heroku.com'},
19+
)
20+
21+
ux.stdout('Favorited Apps')
22+
ux.stdout('')
23+
for (const f of favorites) {
24+
ux.stdout(f.resource_name)
25+
}
26+
}
27+
}
28+
29+
(FavoritesCommand.run([]) as any)
30+
.catch(require('@oclif/core').Errors.handle)

examples/login.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1-
import {Command} from '../src'
1+
import {Command, flags} from '../src'
22

33
class LoginCommand extends Command {
4+
static flags = {
5+
interactive: flags.boolean({char: 'i', description: 'login with username/password'}),
6+
}
7+
48
async run() {
9+
const {flags} = await this.parse(LoginCommand)
510
this.log('logging in')
6-
await this.heroku.login()
11+
const interactive = (flags.interactive) ? 'interactive' : undefined
12+
await this.heroku.login({method: interactive})
713
}
814
}
915

10-
(LoginCommand.run([]) as any)
16+
(LoginCommand.run(process.argv.slice(2)) as any)
1117
.catch(require('@oclif/core').Errors.handle)

examples/logout.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import {Command} from '../src'
22

3-
class LoginCommand extends Command {
3+
class LogoutCommand extends Command {
44
async run() {
55
this.log('logging out')
66
await this.heroku.logout()
77
}
88
}
99

10-
LoginCommand.run([])
11-
.catch(require('@oclif/errors/handle'))
10+
(LogoutCommand.run([]) as any)
11+
.catch(require('@oclif/core').Errors.handle)

examples/run.sh

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/bash
2+
3+
if [ -z "$1" ]; then
4+
echo "Please provide a command name (login or logout)"
5+
exit 1
6+
fi
7+
8+
COMMAND=$1
9+
shift
10+
11+
if [ ! -f "examples/$COMMAND.ts" ]; then
12+
echo "Command '$COMMAND' not found in examples directory"
13+
exit 1
14+
fi
15+
16+
./node_modules/.bin/ts-node "examples/$COMMAND.ts" "$@"

examples/status.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import {HTTP} from '@heroku/http-call'
2+
import {ux} from '@oclif/core'
3+
4+
import {Command} from '../src'
5+
6+
class StatusCommand extends Command {
7+
async run() {
8+
ux.stdout('Checking Heroku status...')
9+
10+
const {body: status} = await HTTP.get('https://status.heroku.com/api/v4/current-status')
11+
12+
if (Array.isArray(status) && status.length > 0) {
13+
this.log('\nCurrent Heroku Incidents:')
14+
for (const incident of status) {
15+
this.log(`\n${incident.name}`)
16+
this.log(`Status: ${incident.status}`)
17+
this.log(`Created: ${new Date(incident.created_at).toLocaleString()}`)
18+
if (incident.updated_at) {
19+
this.log(`Updated: ${new Date(incident.updated_at).toLocaleString()}`)
20+
}
21+
}
22+
} else {
23+
this.log('\nNo current incidents reported.')
24+
}
25+
}
26+
}
27+
28+
(StatusCommand.run([]) as any)
29+
.catch(require('@oclif/core').Errors.handle)

examples/whoami.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import * as Heroku from '@heroku-cli/schema'
2+
import {ux} from '@oclif/core'
3+
4+
import {Command} from '../src'
5+
6+
class StatusCommand extends Command {
7+
notloggedin() {
8+
this.error('not logged in', {exit: 100})
9+
}
10+
11+
async run() {
12+
if (process.env.HEROKU_API_KEY) this.warn('HEROKU_API_KEY is set')
13+
if (!this.heroku.auth) this.notloggedin()
14+
try {
15+
const {body: account} = await this.heroku.get<Heroku.Account>('/account', {retryAuth: false})
16+
ux.stdout(account.email)
17+
} catch (error: any) {
18+
if (error.statusCode === 401) this.notloggedin()
19+
throw error
20+
}
21+
}
22+
}
23+
24+
(StatusCommand.run([]) as any)
25+
.catch(require('@oclif/core').Errors.handle)
26+

0 commit comments

Comments
 (0)