From 76664592e108babd934e1d15c9481009f4fb422f Mon Sep 17 00:00:00 2001 From: RyanDagg Date: Wed, 30 Aug 2023 11:20:19 -0600 Subject: [PATCH] fix(run): downgrade @heroku-cli/plugins-run to @oclif/core v1 (#2460) * fix: downgrade @heroku-cli/plugin-run to before @oclif/core v2 * refactor: allow test_release to run off of ./bin/run with an env var. Add some new heroku run tests * chore: update redis:info to a command with output * chore: add comment explaining new env var --- .github/workflows/pack-upload.yml | 2 +- packages/cli/package.json | 2 + packages/cli/src/commands/run/detached.ts | 63 ---------- packages/cli/src/commands/run/index.ts | 70 ----------- packages/cli/src/commands/run/inside.ts | 62 ---------- .../unit/commands/run/detached.unit.test.ts | 32 ----- .../test/unit/commands/run/index.unit.test.ts | 37 ------ .../unit/commands/run/inside.unit.test.ts | 52 -------- scripts/postrelease/test_release | 116 ++++++++++-------- yarn.lock | 18 ++- 10 files changed, 83 insertions(+), 371 deletions(-) delete mode 100644 packages/cli/src/commands/run/detached.ts delete mode 100644 packages/cli/src/commands/run/index.ts delete mode 100644 packages/cli/src/commands/run/inside.ts delete mode 100644 packages/cli/test/unit/commands/run/detached.unit.test.ts delete mode 100644 packages/cli/test/unit/commands/run/index.unit.test.ts delete mode 100644 packages/cli/test/unit/commands/run/inside.unit.test.ts diff --git a/.github/workflows/pack-upload.yml b/.github/workflows/pack-upload.yml index d13961829e..4369ad3c65 100644 --- a/.github/workflows/pack-upload.yml +++ b/.github/workflows/pack-upload.yml @@ -28,7 +28,7 @@ jobs: path: /home/runner/work/cli/cli/packages/cli/dist pack_tarballs: - runs-on: ubuntu-latest + runs-on: pub-hk-ubuntu-22.04-2xlarge steps: - uses: actions/checkout@v3 - name: Install system deps diff --git a/packages/cli/package.json b/packages/cli/package.json index 88c89ca51e..00ee4608d6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -19,6 +19,7 @@ "@heroku-cli/plugin-ps": "^8.1.7", "@heroku-cli/plugin-ps-exec": "^2.4.0", "@heroku-cli/plugin-redis-v5": "^8.2.0", + "@heroku-cli/plugin-run": "8.1.4", "@heroku-cli/plugin-spaces": "^8.4.0", "@heroku-cli/schema": "^1.0.25", "@heroku/buildpack-registry": "^1.0.1", @@ -150,6 +151,7 @@ "@heroku-cli/plugin-pg-v5", "@heroku-cli/plugin-ps-exec", "@heroku-cli/plugin-redis-v5", + "@heroku-cli/plugin-run", "@heroku-cli/plugin-spaces", "@oclif/plugin-commands", "@oclif/plugin-help", diff --git a/packages/cli/src/commands/run/detached.ts b/packages/cli/src/commands/run/detached.ts deleted file mode 100644 index 57f0e362be..0000000000 --- a/packages/cli/src/commands/run/detached.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -// tslint:disable:file-name-casing -import color from '@heroku-cli/color' -import {Command, flags} from '@heroku-cli/command' -import {DynoSizeCompletion, ProcessTypeCompletion} from '@heroku-cli/command/lib/completions' -import {ux} from '@oclif/core' - -import Dyno from '../../lib/run/dyno' -import {buildCommand} from '../../lib/run/helpers' -import logDisplayer from '../../lib/run/log-displayer' - -export default class RunDetached extends Command { - static description = 'run a detached dyno, where output is sent to your logs' - - static examples = [ - '$ heroku run:detached ls', - ] - - static strict = false - - static flags = { - app: flags.app({required: true}), - remote: flags.remote(), - env: flags.string({char: 'e', description: "environment variables to set (use ';' to split multiple vars)"}), - size: flags.string({char: 's', description: 'dyno size', completion: DynoSizeCompletion}), - tail: flags.boolean({char: 't', description: 'continually stream logs'}), - type: flags.string({description: 'process type', completion: ProcessTypeCompletion}), - } - - async run() { - const {flags, argv} = await this.parse(RunDetached) - - const opts = { - heroku: this.heroku, - app: flags.app, - command: buildCommand(argv as string[]), - size: flags.size, - type: flags.type, - env: flags.env, - attach: false, - } - - if (!opts.command) { - throw new Error('Usage: heroku run COMMAND\n\nExample: heroku run bash') - } - - const dyno = new Dyno(opts) - - await dyno.start() - - if (flags.tail) { - await logDisplayer(this.heroku, { - app: flags.app, - // @ts-ignore - dyno: dyno.dyno.name, - tail: true, - }) - } else { - // @ts-ignore - ux.log(`Run ${color.cmd('heroku logs --app ' + dyno.opts.app + ' --dyno ' + dyno.dyno.name)} to view the output.`) - } - } -} diff --git a/packages/cli/src/commands/run/index.ts b/packages/cli/src/commands/run/index.ts deleted file mode 100644 index f7729146d0..0000000000 --- a/packages/cli/src/commands/run/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -import {Command, flags} from '@heroku-cli/command' -import {DynoSizeCompletion, ProcessTypeCompletion} from '@heroku-cli/command/lib/completions' -import {ux} from '@oclif/core' -import debugFactory from 'debug' -import * as Heroku from '@heroku-cli/schema' - -import Dyno from '../../lib/run/dyno' -import {buildCommand} from '../../lib/run/helpers' - -const debug = debugFactory('heroku:run') - -export default class Run extends Command { - static description = 'run a one-off process inside a heroku dyno\nShows a notification if the dyno takes more than 20 seconds to start.' - - static examples = [ - '$ heroku run bash', - '$ heroku run -s standard-2x -- myscript.sh -a arg1 -s arg2', - ] - - // This is to allow for variable length arguments - static strict = false - - static flags = { - app: flags.app({description: 'parent app used by review apps', required: true}), - remote: flags.remote(), - size: flags.string({char: 's', description: 'dyno size', completion: DynoSizeCompletion}), - type: flags.string({description: 'process type', completion: ProcessTypeCompletion}), - 'exit-code': flags.boolean({char: 'x', description: 'passthrough the exit code of the remote command'}), - env: flags.string({char: 'e', description: "environment variables to set (use ';' to split multiple vars)"}), - 'no-tty': flags.boolean({description: 'force the command to not run in a tty'}), - listen: flags.boolean({description: 'listen on a local port', hidden: true}), - 'no-notify': flags.boolean({description: 'disables notification when dyno is up (alternatively use HEROKU_NOTIFICATIONS=0)'}), - } - - async run() { - const {argv, flags} = await this.parse(Run) - - const opts = { - 'exit-code': flags['exit-code'], - 'no-tty': flags['no-tty'], - app: flags.app, - attach: true, - command: buildCommand(argv as string[]), - env: flags.env, - heroku: this.heroku, - listen: flags.listen, - notify: !flags['no-notify'], - size: flags.size, - type: flags.type, - } - - if (!opts.command) { - throw new Error('Usage: heroku run COMMAND\n\nExample: heroku run bash') - } - - await this.heroku.get('/account') - const dyno = new Dyno(opts) - try { - await dyno.start() - debug('done running') - } catch (error: any) { - debug(error) - if (error.exitCode) { - ux.error(error.message, {code: error.exitCode, exit: error.exitCode}) - } else { - throw error - } - } - } -} diff --git a/packages/cli/src/commands/run/inside.ts b/packages/cli/src/commands/run/inside.ts deleted file mode 100644 index 85560fc0be..0000000000 --- a/packages/cli/src/commands/run/inside.ts +++ /dev/null @@ -1,62 +0,0 @@ -// tslint:disable:file-name-casing -import {Command, flags} from '@heroku-cli/command' -import {ux} from '@oclif/core' -import debugFactory from 'debug' - -import Dyno from '../../lib/run/dyno' -import {buildCommand} from '../../lib/run/helpers' - -const debug = debugFactory('heroku:run:inside') - -export default class RunInside extends Command { - static description = 'run a one-off process inside an existing heroku dyno' - - static hidden = true; - - static examples = [ - '$ heroku run:inside web.1 bash', - ] - - static flags = { - app: flags.app({required: true}), - remote: flags.remote(), - 'exit-code': flags.boolean({char: 'x', description: 'passthrough the exit code of the remote command'}), - env: flags.string({char: 'e', description: "environment variables to set (use ';' to split multiple vars)"}), - listen: flags.boolean({description: 'listen on a local port', hidden: true}), - } - - static strict = false - - async run() { - const parsed = await this.parse(RunInside) - const {flags} = parsed - const argv = parsed.argv as string[] - - if (argv.length < 2) { - throw new Error('Usage: heroku run:inside DYNO COMMAND\n\nExample: heroku run:inside web.1 bash') - } - - const opts = { - 'exit-code': flags['exit-code'], - app: flags.app, - command: buildCommand(argv.slice(1)), - dyno: argv[0], - env: flags.env, - heroku: this.heroku, - listen: flags.listen, - } - - const dyno = new Dyno(opts) - - try { - await dyno.start() - } catch (error: any) { - debug(error) - if (error.exitCode) { - ux.exit(error.exitCode) - } else { - throw error - } - } - } -} diff --git a/packages/cli/test/unit/commands/run/detached.unit.test.ts b/packages/cli/test/unit/commands/run/detached.unit.test.ts deleted file mode 100644 index e9c9752014..0000000000 --- a/packages/cli/test/unit/commands/run/detached.unit.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {expect, test} from '@oclif/test' - -describe('detached', async () => { - const app = 'heroku-cli-ci-smoke-test-app' - const dyno = 'test-dyno' - const dynoReponse = { - name: dyno, - state: 'starting', - dyno: {name: dyno}, - } - - test - .stdout() - .nock('https://api.heroku.com', api => { - api.post(`/apps/${app}/dynos`) - .reply(200, dynoReponse) - }) - .command(['run:detached', `--app=${app}`, 'test']) - .it('sucessfully runs detached', ctx => { - const rawResponse = ctx.stdout - const updatedDynoNameResponse = rawResponse.replace(/run\.\d{4}/g, 'test-dyno') - expect(updatedDynoNameResponse).to.equal(`Run heroku logs --app ${app} --dyno ${dyno} to view the output.\n`) - }) - - test - .stdout() - .command(['run:detached', `--app=${app}`]) - .catch(error => { - expect(error.message).to.equal('Usage: heroku run COMMAND\n\nExample: heroku run bash') - }) - .it('errors if no arguments are provided') -}) diff --git a/packages/cli/test/unit/commands/run/index.unit.test.ts b/packages/cli/test/unit/commands/run/index.unit.test.ts deleted file mode 100644 index 6e35da771f..0000000000 --- a/packages/cli/test/unit/commands/run/index.unit.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -import {expect, test} from '@oclif/test' -import * as sinon from 'sinon' - -import Dyno from '../../../../src/lib/run/dyno' - -describe('run/index', () => { - describe('runs correct command', () => { - let dynoOpts: { command: any } - test - .nock('https://api.heroku.com', api => { - api.get('/account') - .reply(200, {}) - }) - .stub(Dyno.prototype, 'start', sinon.stub().callsFake(function () { - // @ts-ignore - dynoOpts = this.opts - return Promise.resolve() - })) - .command(['run', 'bash', '--app=heroku-cli-ci-smoke-test-app']) - .it('runs bash', () => { - expect(dynoOpts.command).to.equal('bash') - }) - }) - - describe('errors without command', async () => { - test - .stub(Dyno.prototype, 'start', sinon.stub().callsFake(function () { - return Promise.resolve() - })) - .command(['run', '--app=heroku-cli-ci-smoke-test-app']) - .catch(error => { - expect(error.message).to.contain('Usage: heroku run COMMAND') - }) - .it('throws an error') - }) -}) diff --git a/packages/cli/test/unit/commands/run/inside.unit.test.ts b/packages/cli/test/unit/commands/run/inside.unit.test.ts deleted file mode 100644 index 0c125fa647..0000000000 --- a/packages/cli/test/unit/commands/run/inside.unit.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -import {expect, test} from '@oclif/test' -import * as sinon from 'sinon' - -import Dyno from '../../../../src/lib/run/dyno' - -describe('run:inside', () => { - describe('runs correct command', () => { - let dynoOpts: { command: any } - test - .stub(Dyno.prototype, 'start', sinon.stub().callsFake(function () { - // @ts-ignore - dynoOpts = this.opts - return Promise.resolve() - })) - .command(['run:inside', 'DYNO_NAME', 'bash', '--app=heroku-cli-ci-smoke-test-app']) - .it('runs bash', () => { - expect(dynoOpts.command).to.equal('bash') - }) - }) - - describe('run:inside errors', () => { - test - .stub(Dyno.prototype, 'start', sinon.stub().callsFake(function () { - return Promise.resolve() - })) - .command(['run:inside', '--app=heroku-cli-ci-smoke-test-app']) - .catch(error => { - expect(error.message).to.contain('Usage: heroku run:inside DYNO COMMAND') - }) - .it('errors when fewer than two args are passed') - - test - .stub(Dyno.prototype, 'start', sinon.stub().callsFake(function () { - return Promise.reject(new Error('failed')) - })) - .command(['run:inside', '--app=heroku-cli-ci-smoke-test-app', 'web.1', 'bash']) - .catch(error => expect(error.message).to.equal('failed')) - .it('throws a generic error when the request fails and there is no exit code') - - const errorWithCode: any = new Error('failed') - errorWithCode.exitCode = '403' - - test - .stub(Dyno.prototype, 'start', sinon.stub().callsFake(function () { - return Promise.reject(errorWithCode) - })) - .command(['run:inside', '--app=heroku-cli-ci-smoke-test-app', 'web.1', 'bash']) - .catch(error => expect(error.message).to.equal('EEXIT: 403')) - .it('throws a stylized error when the request fails and there is an exit code') - }) -}) diff --git a/scripts/postrelease/test_release b/scripts/postrelease/test_release index 0a636299ac..ac9b357e06 100755 --- a/scripts/postrelease/test_release +++ b/scripts/postrelease/test_release @@ -5,68 +5,78 @@ # pipefail is necessary for "command not found" to throw an error in these tests. set -e -o pipefail +# these can be run against local changes by running `USE_BINRUN=1 ./scripts/postrelease/test_release` from repo root +if [[ -z "$USE_BINRUN" ]]; then + CMD_BIN="heroku" +else + CMD_BIN="./bin/run" + yarn + yarn build +fi + declare -a COMMANDS=( - "heroku" - "heroku whoami" - "heroku access --app particleboard-staging" - "heroku addons --app status-ui" - "heroku apps -p" - "heroku apps -t heroku-front-end" - "heroku apps:info --app heroku-cli-test-staging" - "heroku auth:2fa" - "heroku auth:whoami" - "heroku authorizations" - "heroku autocomplete" - "heroku buildpacks:search ruby" - "heroku certs --app particleboard-staging" - "heroku ci --app particleboard-staging" - "heroku ci --pipeline status-ui" - "heroku ci:config --pipeline status-ui" - "heroku clients" - "heroku commands" - "heroku domains --app status-ui" - "heroku drains --app heroku-cli-test-staging" - "heroku features --app heroku-cli-test-staging" - "rm -rf ~/status-ui && heroku git:clone ~/status-ui --app status-ui && rm -rf ~/status-ui" # `rm` used so it will always run locally - "heroku help" - "heroku --help" - "heroku -h" - "heroku keys" - "heroku labs" - "heroku local:version" - "heroku maintenance --app heroku-cli-test-staging" - "heroku members --team heroku-front-end" - "heroku notifications" - "heroku orgs" - "heroku pg:backups --app particleboard-staging" - "heroku pipelines" - "heroku plugins:install api" - "heroku plugins:install sudo" - "heroku plugins" - "heroku redis:info --app status-ui" - "heroku regions" - "heroku releases --app heroku-cli-test-staging" - "heroku sessions" - "heroku spaces" - "heroku status" - "heroku teams" - "heroku version" - "heroku --version" - "heroku -v" - "heroku which authorizations" + "$CMD_BIN" + "$CMD_BIN whoami" + "$CMD_BIN access --app particleboard-staging" + "$CMD_BIN addons --app status-ui" + "$CMD_BIN apps -p" + "$CMD_BIN apps -t heroku-front-end" + "$CMD_BIN apps:info --app heroku-cli-test-staging" + "$CMD_BIN auth:2fa" + "$CMD_BIN auth:whoami" + "$CMD_BIN authorizations" + "$CMD_BIN autocomplete" + "$CMD_BIN buildpacks:search ruby" + "$CMD_BIN certs --app particleboard-staging" + "$CMD_BIN ci --app particleboard-staging" + "$CMD_BIN ci --pipeline status-ui" + "$CMD_BIN ci:config --pipeline status-ui" + "$CMD_BIN clients" + "$CMD_BIN commands" + "$CMD_BIN domains --app status-ui" + "$CMD_BIN drains --app heroku-cli-test-staging" + "$CMD_BIN features --app heroku-cli-test-staging" + "rm -rf ~/status-ui && $CMD_BIN git:clone ~/status-ui --app status-ui && rm -rf ~/status-ui" # `rm` used so it will always run locally + "$CMD_BIN help" + "$CMD_BIN --help" + "$CMD_BIN -h" + "$CMD_BIN keys" + "$CMD_BIN labs" + "$CMD_BIN local:version" + "$CMD_BIN maintenance --app heroku-cli-test-staging" + "$CMD_BIN members --team heroku-front-end" + "$CMD_BIN notifications" + "$CMD_BIN orgs" + "$CMD_BIN pg:backups --app particleboard-staging" + "$CMD_BIN pipelines" + "$CMD_BIN plugins:install api" + "$CMD_BIN plugins:install sudo" + "$CMD_BIN plugins" + "$CMD_BIN redis:info --app particleboard-staging" + "$CMD_BIN regions" + "$CMD_BIN releases --app heroku-cli-test-staging" + "$CMD_BIN run ls -a heroku-cli-test-staging -- --color" + "$CMD_BIN run:detached \"echo 'Hello World'\" -a heroku-cli-test-staging" + "$CMD_BIN sessions" + "$CMD_BIN spaces" + "$CMD_BIN status" + "$CMD_BIN teams" + "$CMD_BIN version" + "$CMD_BIN --version" + "$CMD_BIN -v" + "$CMD_BIN which authorizations" ) # todo: how to test this? -# "heroku login" +# "$CMD_BIN login" # todo: find a way to test commands that require 2fa -# "heroku logs --app status-ui" -# "heroku config --app status-ui" -# "heroku webhooks --app status-ui" +# "$CMD_BIN logs --app status-ui" +# "$CMD_BIN config --app status-ui" +# "$CMD_BIN webhooks --app status-ui" for cmd in "${COMMANDS[@]}" do echo -e "\n\n\nRUNNING: $cmd" -# todo: swallow output if sensitive info here? eval "$cmd" done diff --git a/yarn.lock b/yarn.lock index 98436dd9b9..c09ff5e700 100644 --- a/yarn.lock +++ b/yarn.lock @@ -834,6 +834,21 @@ __metadata: languageName: unknown linkType: soft +"@heroku-cli/plugin-run@npm:8.1.4": + version: 8.1.4 + resolution: "@heroku-cli/plugin-run@npm:8.1.4" + dependencies: + "@heroku-cli/color": ^1.1.14 + "@heroku-cli/command": ^9.0.2 + "@heroku-cli/notifications": ^1.2.2 + "@heroku/eventsource": ^1.0.7 + "@oclif/core": 1.26.2 + debug: ^4.1.1 + tslib: ^1 + checksum: c09956b8160839b2a442b593235637f02b49d88fe32164aab5db89e112a9b3144f8e70832053ad1d90c57bb63bc2eb1c09b052c86a69c8ad9e6a4d39175851a5 + languageName: node + linkType: hard + "@heroku-cli/plugin-spaces@^8.4.0, @heroku-cli/plugin-spaces@workspace:packages/spaces": version: 0.0.0-use.local resolution: "@heroku-cli/plugin-spaces@workspace:packages/spaces" @@ -2341,7 +2356,7 @@ __metadata: languageName: node linkType: hard -"@oclif/core@npm:^1.1.1, @oclif/core@npm:^1.2.0, @oclif/core@npm:^1.25.0, @oclif/core@npm:^1.26.2": +"@oclif/core@npm:1.26.2, @oclif/core@npm:^1.1.1, @oclif/core@npm:^1.2.0, @oclif/core@npm:^1.25.0, @oclif/core@npm:^1.26.2": version: 1.26.2 resolution: "@oclif/core@npm:1.26.2" dependencies: @@ -9251,6 +9266,7 @@ __metadata: "@heroku-cli/plugin-ps": ^8.1.7 "@heroku-cli/plugin-ps-exec": ^2.4.0 "@heroku-cli/plugin-redis-v5": ^8.2.0 + "@heroku-cli/plugin-run": 8.1.4 "@heroku-cli/plugin-spaces": ^8.4.0 "@heroku-cli/schema": ^1.0.25 "@heroku/buildpack-registry": ^1.0.1