From f3d8d93597e3fc6404cdd877002b459c4d68c8d0 Mon Sep 17 00:00:00 2001 From: Eric Black Date: Wed, 13 Nov 2024 14:58:48 -0800 Subject: [PATCH 1/4] Get buildpacks from latest release for fir apps --- packages/cli/src/commands/buildpacks/index.ts | 12 ++++-- packages/cli/src/lib/buildpacks/buildpacks.ts | 42 +++++++++++++++---- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/packages/cli/src/commands/buildpacks/index.ts b/packages/cli/src/commands/buildpacks/index.ts index 72dfc42e7d..74b2057b2c 100644 --- a/packages/cli/src/commands/buildpacks/index.ts +++ b/packages/cli/src/commands/buildpacks/index.ts @@ -1,5 +1,7 @@ import {Command, flags as Flags} from '@heroku-cli/command' import {ux} from '@oclif/core' +import {App} from '../../lib/types/fir' +import color from '@heroku-cli/color' import {BuildpackCommand} from '../../lib/buildpacks/buildpacks' @@ -14,12 +16,16 @@ export default class Index extends Command { async run() { const {flags} = await this.parse(Index) const buildpacksCommand = new BuildpackCommand(this.heroku) - - const buildpacks = await buildpacksCommand.fetch(flags.app) + const {body: app} = await this.heroku.get(`/apps/${flags.app}`, { + headers: { + Accept: 'application/vnd.heroku+json; version=3.sdk', + }, + }) + const buildpacks = await buildpacksCommand.fetch(flags.app, app.generation === 'fir') if (buildpacks.length === 0) { this.log(`${flags.app} has no Buildpack URL set.`) } else { - ux.styledHeader(`${flags.app} Buildpack URL${buildpacks.length > 1 ? 's' : ''}`) + ux.styledHeader(`${color.app(flags.app)} Buildpack${buildpacks.length > 1 ? 's' : ''}`) buildpacksCommand.display(buildpacks, '') } } diff --git a/packages/cli/src/lib/buildpacks/buildpacks.ts b/packages/cli/src/lib/buildpacks/buildpacks.ts index 351e1c9001..f21f838c76 100644 --- a/packages/cli/src/lib/buildpacks/buildpacks.ts +++ b/packages/cli/src/lib/buildpacks/buildpacks.ts @@ -5,6 +5,7 @@ import {ux} from '@oclif/core' import {findIndex as lodashFindIndex} from 'lodash' import {Result} from 'true-myth' import push from '../git/push' +import {OciImage, Release} from '../../lib/types/fir' const validUrl = require('valid-url') @@ -26,14 +27,41 @@ export class BuildpackCommand { this.registry = new BuildpackRegistry() } - async fetch(app: string): Promise { - const buildpacks = await this.heroku.get(`/apps/${app}/buildpack-installations`) + async fetch(app: string, isFirApp = false): Promise { + let buildpacks: any + if (isFirApp) { + const {body: releases} = await this.heroku.request(`/apps/${app}/releases`, { + partial: true, + headers: { + Range: 'version ..; max=10, order=desc', + Accept: 'application/vnd.heroku+json; version=3.sdk', + }, + }) + const latestImageId = releases[0].oci_image?.id + const {body: ociImages} = await this.heroku.get(`/apps/${app}/oci-images/${latestImageId}`, { + headers: { + Accept: 'application/vnd.heroku+json; version=3.sdk', + }, + }) + buildpacks = ociImages[0].buildpacks.map((b, index) => { + return { + buildpack: { + url: b.id || b.homepage, + name: b.id, + }, + ordinal: index, + } + }) + } else { + const buildpacksBody = await this.heroku.get(`/apps/${app}/buildpack-installations`) + buildpacks = buildpacksBody.body + } + return this.mapBuildpackResponse(buildpacks) } - mapBuildpackResponse(buildpacks: {body: any}): BuildpackResponse[] { - const body = buildpacks.body - return body.map((bp: BuildpackResponse) => { + mapBuildpackResponse(buildpacks: BuildpackResponse[]): BuildpackResponse[] { + return buildpacks.map((bp: BuildpackResponse) => { bp.buildpack.url = bp.buildpack.url.replace(/^urn:buildpack:/, '') return bp }) @@ -55,7 +83,7 @@ export class BuildpackCommand { } Result.match({ - Ok: _ => {}, + Ok: () => {}, Err: err => { ux.error(`Could not find the buildpack: ${buildpack}. ${err}`, {exit: 1}) }, @@ -117,7 +145,7 @@ export class BuildpackCommand { } async put(app: string, buildpackUpdates: {buildpack: string}[]): Promise { - const buildpacks = await this.heroku.put(`/apps/${app}/buildpack-installations`, { + const {body: buildpacks} = await this.heroku.put(`/apps/${app}/buildpack-installations`, { headers: {Range: ''}, body: {updates: buildpackUpdates}, }) From 1e76fb9239ede35c8ef9873e77c3168319302cf2 Mon Sep 17 00:00:00 2001 From: Eric Black Date: Wed, 13 Nov 2024 16:53:16 -0800 Subject: [PATCH 2/4] Add test for fir app --- .../commands/buildpacks/index.unit.test.ts | 164 ++++++++++++++++-- 1 file changed, 152 insertions(+), 12 deletions(-) diff --git a/packages/cli/test/unit/commands/buildpacks/index.unit.test.ts b/packages/cli/test/unit/commands/buildpacks/index.unit.test.ts index a75a6bde55..88ba9b9ef6 100644 --- a/packages/cli/test/unit/commands/buildpacks/index.unit.test.ts +++ b/packages/cli/test/unit/commands/buildpacks/index.unit.test.ts @@ -1,21 +1,136 @@ +/* eslint-disable mocha/no-setup-in-describe */ import {expect, test} from '@oclif/test' import * as nock from 'nock' import {BuildpackInstallationsStub as Stubber} from '../../../helpers/buildpacks/buildpack-installations-stub' nock.disableNetConnect() +const cedarApp = { + acm: false, + archived_at: null, + build_stack: {name: 'heroku-24'}, + created_at: '2024-09-06T17:45:29Z', + git_url: 'https://git.heroku.com', + id: '12345678-aaaa-bbbb-cccc-b2443790f501', + generation: 'cedar', + maintenance: false, + name: 'example', + owner: {email: 'example-owner@heroku.com'}, + internal_routing: null, + region: {name: 'virginia'}, + released_at: '2024-11-13T20:07:47Z', + repo_size: null, + slug_size: null, + stack: {name: 'heroku-24'}, + updated_at: '2024-11-13T20:07:47Z', + web_url: 'https://cedar-example-app.herokuapp.com', +} + +const firApp = { + acm: false, + archived_at: null, + build_stack: {name: 'heroku-24'}, + created_at: '2024-09-06T17:45:29Z', + generation: 'fir', + git_url: 'https://git.heroku.com', + id: '12345678-aaaa-bbbb-cccc-b2443790f501', + maintenance: false, + name: 'example', + owner: {email: 'example-owner@heroku.com'}, + internal_routing: null, + region: {name: 'virginia'}, + released_at: '2024-11-13T20:07:47Z', + repo_size: null, + slug_size: null, + stack: {name: 'heroku-24'}, + updated_at: '2024-11-13T20:07:47Z', + web_url: 'https://fir-example-app.herokuapp.com', +} + +const releases = [ + { + addon_plan_names: [ + 'heroku-postgresql:dev', + ], + artifacts: [ + { + type: 'oci-image', + id: '01234567-89ab-cdef-0123-456789abcdef', + }, + ], + app: { + name: 'example', + id: '01234567-89ab-cdef-0123-456789abcdef', + }, + created_at: '2012-01-01T12:00:00Z', + description: 'Added new feature', + id: '01234567-89ab-cdef-0123-456789abcdef', + updated_at: '2012-01-01T12:00:00Z', + oci_image: { + id: '01234567-89ab-cdef-0123-456789abcdef', + }, + slug: { + id: '01234567-89ab-cdef-0123-456789abcdef', + }, + status: 'succeeded', + user: { + id: '01234567-89ab-cdef-0123-456789abcdef', + email: 'username@example.com', + }, + version: 11, + current: true, + output_stream_url: 'https://release-output.heroku.com/streams/01234567-89ab-cdef-0123-456789abcdef', + eligible_for_rollback: true, + }, +] + +const ociImages = [ + { + id: '01234567-89ab-cdef-0123-456789abcdef', + base_image_name: 'heroku/heroku:22-cnb', + base_top_layer: 'sha256:ea36ae5fbc1e7230e0a782bf216fb46500e210382703baa6bab8acf2c6a23f78', + commit: '60883d9e8947a57e04dc9124f25df004866a2051', + commit_description: 'fixed a bug with API documentation', + image_repo: 'd7ba1ace-b396-4691-968c-37ae53153426/builds', + digest: 'sha256:dc14ae5fbc1e7230e0a782bf216fb46500e210631703bcc6bab8acf2c6a23f42', + stack: { + id: 'ba46bf09-7bd1-42fd-90df-a1a9a93eb4a2', + name: 'cnb', + }, + process_types: { + web: { + name: 'web', + display_cmd: 'bundle exec puma -p $PORT', + command: '/cnb/process/web', + working_dir: '/workspace/webapp', + default: true, + }, + }, + buildpacks: [ + { + id: 'heroku/ruby', + version: '2.0.0', + homepage: 'https://github.com/heroku/buildpacks-ruby', + }, + ], + created_at: '2012-01-01T12:00:00Z', + updated_at: '2012-01-01T12:00:00Z', + architecture: 'arm64', + }, +] describe('buildpacks', function () { test .nock('https://api.heroku.com', (api: nock.Scope) => { + api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) Stubber.get(api, ['https://github.com/heroku/heroku-buildpack-ruby']) }) .stdout() .stderr() - .command(['buildpacks', '-a', 'example']) + .command(['buildpacks', '-a', cedarApp.name]) .it('# displays the buildpack URL', ctx => { expect(ctx.stderr).to.equal('') expect(ctx.stdout).to.equal( - `=== example Buildpack URL + `=== ⬢ ${cedarApp.name} Buildpack https://github.com/heroku/heroku-buildpack-ruby `) @@ -23,15 +138,16 @@ https://github.com/heroku/heroku-buildpack-ruby test .nock('https://api.heroku.com', (api: nock.Scope) => { + api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) Stubber.get(api, [{url: 'urn:buildpack:heroku/ruby', name: 'heroku/ruby'}]) }) .stdout() .stderr() - .command(['buildpacks', '-a', 'example']) + .command(['buildpacks', '-a', cedarApp.name]) .it('# maps buildpack urns to names', ctx => { expect(ctx.stderr).to.equal('') expect(ctx.stdout).to.equal( - `=== example Buildpack URL + `=== ⬢ ${cedarApp.name} Buildpack heroku/ruby `) @@ -39,15 +155,16 @@ heroku/ruby test .nock('https://api.heroku.com', (api: nock.Scope) => { + api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) Stubber.get(api, ['https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/ruby.tgz']) }) .stdout() .stderr() - .command(['buildpacks', '-a', 'example']) + .command(['buildpacks', '-a', cedarApp.name]) .it('# does not map buildpack s3 to names', ctx => { expect(ctx.stderr).to.equal('') expect(ctx.stdout).to.equal( - `=== example Buildpack URL + `=== ⬢ ${cedarApp.name} Buildpack https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/ruby.tgz `) @@ -55,20 +172,22 @@ https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/ruby.tgz test .nock('https://api.heroku.com', (api: nock.Scope) => { + api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) Stubber.get(api) }) .stdout() .stderr() - .command(['buildpacks', '-a', 'example']) + .command(['buildpacks', '-a', cedarApp.name]) .it('# with no buildpack URL set does not display a buildpack URL', ctx => { expect(ctx.stderr).to.equal('') expect(ctx.stdout).to.equal( - `example has no Buildpack URL set. + `${cedarApp.name} has no Buildpack URL set. `) }) test .nock('https://api.heroku.com', (api: nock.Scope) => { + api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) Stubber.get(api, [ 'https://github.com/heroku/heroku-buildpack-java', 'https://github.com/heroku/heroku-buildpack-ruby', @@ -76,11 +195,11 @@ https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/ruby.tgz }) .stdout() .stderr() - .command(['buildpacks', '-a', 'example']) + .command(['buildpacks', '-a', cedarApp.name]) .it('# with two buildpack URLs set displays the buildpack URL', ctx => { expect(ctx.stderr).to.equal('') expect(ctx.stdout).to.equal( - `=== example Buildpack URLs + `=== ⬢ ${cedarApp.name} Buildpacks 1. https://github.com/heroku/heroku-buildpack-java 2. https://github.com/heroku/heroku-buildpack-ruby @@ -89,6 +208,7 @@ https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/ruby.tgz test .nock('https://api.heroku.com', (api: nock.Scope) => { + api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) Stubber.get(api, [ 'https://buildpack-registry.s3.amazonaws.com/buildpacks/heroku/java.tgz', 'https://buildpack-registry.s3.amazonaws.com/buildpacks/rust-lang/rust.tgz', @@ -96,14 +216,34 @@ https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/ruby.tgz }) .stdout() .stderr() - .command(['buildpacks', '-a', 'example']) + .command(['buildpacks', '-a', cedarApp.name]) .it('# returns the buildpack registry name back', ctx => { expect(ctx.stderr).to.equal('') expect(ctx.stdout).to.equal( - `=== example Buildpack URLs + `=== ⬢ ${cedarApp.name} Buildpacks 1. heroku/java 2. rust-lang/rust +`) + }) + + test + .nock('https://api.heroku.com', { + reqheaders: {accept: 'application/vnd.heroku+json; version=3.sdk'} + }, (api: nock.Scope) => { + api.get(`/apps/${firApp.name}`).reply(200, firApp) + api.get(`/apps/${firApp.name}/releases`).reply(200, releases) + api.get(`/apps/${firApp.name}/oci-images/${releases[0].id}`).reply(200, ociImages) + }) + .stdout() + .stderr() + .command(['buildpacks', '-a', cedarApp.name]) + .it('# returns cnb buildpack ids for fir apps', ctx => { + expect(ctx.stderr).to.equal('') + expect(ctx.stdout).to.equal( + `=== ⬢ ${firApp.name} Buildpack + +heroku/ruby `) }) }) From 7b842daa72aa0884453239013ff0331f707a876e Mon Sep 17 00:00:00 2001 From: Eric Black Date: Wed, 13 Nov 2024 16:53:46 -0800 Subject: [PATCH 3/4] Add missing comma --- packages/cli/test/unit/commands/buildpacks/index.unit.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/test/unit/commands/buildpacks/index.unit.test.ts b/packages/cli/test/unit/commands/buildpacks/index.unit.test.ts index 88ba9b9ef6..b4115d4649 100644 --- a/packages/cli/test/unit/commands/buildpacks/index.unit.test.ts +++ b/packages/cli/test/unit/commands/buildpacks/index.unit.test.ts @@ -229,7 +229,7 @@ https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/ruby.tgz test .nock('https://api.heroku.com', { - reqheaders: {accept: 'application/vnd.heroku+json; version=3.sdk'} + reqheaders: {accept: 'application/vnd.heroku+json; version=3.sdk'}, }, (api: nock.Scope) => { api.get(`/apps/${firApp.name}`).reply(200, firApp) api.get(`/apps/${firApp.name}/releases`).reply(200, releases) From 5c065c40135c63ccc872824843dd98bda0974353 Mon Sep 17 00:00:00 2001 From: Eric Black Date: Thu, 14 Nov 2024 09:48:41 -0800 Subject: [PATCH 4/4] Add checks for when there are no releases yet. Replace old url checker with newer one. --- packages/cli/package.json | 1 - packages/cli/src/commands/buildpacks/index.ts | 2 +- packages/cli/src/lib/buildpacks/buildpacks.ts | 12 +-- .../commands/buildpacks/index.unit.test.ts | 76 ++++++++++++------- yarn.lock | 8 -- 5 files changed, 54 insertions(+), 45 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index e4f38d246a..a292e59ca8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -76,7 +76,6 @@ "tslib": "1.14.1", "tunnel-ssh": "4.1.6", "urijs": "^1.19.11", - "valid-url": "^1.0.9", "validator": "^13.7.0", "word-wrap": "^1.2.5", "ws": "^6.2.2" diff --git a/packages/cli/src/commands/buildpacks/index.ts b/packages/cli/src/commands/buildpacks/index.ts index 74b2057b2c..5bfd8e5df9 100644 --- a/packages/cli/src/commands/buildpacks/index.ts +++ b/packages/cli/src/commands/buildpacks/index.ts @@ -23,7 +23,7 @@ export default class Index extends Command { }) const buildpacks = await buildpacksCommand.fetch(flags.app, app.generation === 'fir') if (buildpacks.length === 0) { - this.log(`${flags.app} has no Buildpack URL set.`) + this.log(`${color.app(flags.app)} has no Buildpacks.`) } else { ux.styledHeader(`${color.app(flags.app)} Buildpack${buildpacks.length > 1 ? 's' : ''}`) buildpacksCommand.display(buildpacks, '') diff --git a/packages/cli/src/lib/buildpacks/buildpacks.ts b/packages/cli/src/lib/buildpacks/buildpacks.ts index f21f838c76..185014fd3f 100644 --- a/packages/cli/src/lib/buildpacks/buildpacks.ts +++ b/packages/cli/src/lib/buildpacks/buildpacks.ts @@ -6,8 +6,7 @@ import {findIndex as lodashFindIndex} from 'lodash' import {Result} from 'true-myth' import push from '../git/push' import {OciImage, Release} from '../../lib/types/fir' - -const validUrl = require('valid-url') +import {isURL} from 'validator' export type BuildpackResponse = { buildpack: { @@ -37,7 +36,8 @@ export class BuildpackCommand { Accept: 'application/vnd.heroku+json; version=3.sdk', }, }) - const latestImageId = releases[0].oci_image?.id + if (releases.length === 0 || releases[0].oci_image === null) return [] + const latestImageId = releases[0].oci_image.id const {body: ociImages} = await this.heroku.get(`/apps/${app}/oci-images/${latestImageId}`, { headers: { Accept: 'application/vnd.heroku+json; version=3.sdk', @@ -53,8 +53,8 @@ export class BuildpackCommand { } }) } else { - const buildpacksBody = await this.heroku.get(`/apps/${app}/buildpack-installations`) - buildpacks = buildpacksBody.body + const {body: buildpackInstallations} = await this.heroku.get(`/apps/${app}/buildpack-installations`) + buildpacks = buildpackInstallations } return this.mapBuildpackResponse(buildpacks) @@ -78,7 +78,7 @@ export class BuildpackCommand { } async registryNameToUrl(buildpack: string): Promise { - if (validUrl.isWebUri(buildpack)) { + if (isURL(buildpack)) { return buildpack } diff --git a/packages/cli/test/unit/commands/buildpacks/index.unit.test.ts b/packages/cli/test/unit/commands/buildpacks/index.unit.test.ts index b4115d4649..7285320302 100644 --- a/packages/cli/test/unit/commands/buildpacks/index.unit.test.ts +++ b/packages/cli/test/unit/commands/buildpacks/index.unit.test.ts @@ -1,6 +1,7 @@ /* eslint-disable mocha/no-setup-in-describe */ import {expect, test} from '@oclif/test' import * as nock from 'nock' +import heredoc from 'tsheredoc' import {BuildpackInstallationsStub as Stubber} from '../../../helpers/buildpacks/buildpack-installations-stub' nock.disableNetConnect() @@ -129,11 +130,11 @@ describe('buildpacks', function () { .command(['buildpacks', '-a', cedarApp.name]) .it('# displays the buildpack URL', ctx => { expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `=== ⬢ ${cedarApp.name} Buildpack + expect(ctx.stdout).to.equal(heredoc(` + === ⬢ ${cedarApp.name} Buildpack -https://github.com/heroku/heroku-buildpack-ruby -`) + https://github.com/heroku/heroku-buildpack-ruby + `)) }) test @@ -146,11 +147,11 @@ https://github.com/heroku/heroku-buildpack-ruby .command(['buildpacks', '-a', cedarApp.name]) .it('# maps buildpack urns to names', ctx => { expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `=== ⬢ ${cedarApp.name} Buildpack + expect(ctx.stdout).to.equal(heredoc(` + === ⬢ ${cedarApp.name} Buildpack -heroku/ruby -`) + heroku/ruby + `)) }) test @@ -163,11 +164,11 @@ heroku/ruby .command(['buildpacks', '-a', cedarApp.name]) .it('# does not map buildpack s3 to names', ctx => { expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `=== ⬢ ${cedarApp.name} Buildpack + expect(ctx.stdout).to.equal(heredoc(` + === ⬢ ${cedarApp.name} Buildpack -https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/ruby.tgz -`) + https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/ruby.tgz + `)) }) test @@ -180,9 +181,9 @@ https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/ruby.tgz .command(['buildpacks', '-a', cedarApp.name]) .it('# with no buildpack URL set does not display a buildpack URL', ctx => { expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `${cedarApp.name} has no Buildpack URL set. -`) + expect(ctx.stdout).to.equal(heredoc(` + ⬢ ${cedarApp.name} has no Buildpacks. + `)) }) test @@ -198,12 +199,12 @@ https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/ruby.tgz .command(['buildpacks', '-a', cedarApp.name]) .it('# with two buildpack URLs set displays the buildpack URL', ctx => { expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `=== ⬢ ${cedarApp.name} Buildpacks + expect(ctx.stdout).to.equal(heredoc(` + === ⬢ ${cedarApp.name} Buildpacks -1. https://github.com/heroku/heroku-buildpack-java -2. https://github.com/heroku/heroku-buildpack-ruby -`) + 1. https://github.com/heroku/heroku-buildpack-java + 2. https://github.com/heroku/heroku-buildpack-ruby + `)) }) test @@ -219,12 +220,12 @@ https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/ruby.tgz .command(['buildpacks', '-a', cedarApp.name]) .it('# returns the buildpack registry name back', ctx => { expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `=== ⬢ ${cedarApp.name} Buildpacks + expect(ctx.stdout).to.equal(heredoc(` + === ⬢ ${cedarApp.name} Buildpacks -1. heroku/java -2. rust-lang/rust -`) + 1. heroku/java + 2. rust-lang/rust + `)) }) test @@ -240,10 +241,27 @@ https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/ruby.tgz .command(['buildpacks', '-a', cedarApp.name]) .it('# returns cnb buildpack ids for fir apps', ctx => { expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `=== ⬢ ${firApp.name} Buildpack + expect(ctx.stdout).to.equal(heredoc(` + === ⬢ ${firApp.name} Buildpack -heroku/ruby -`) + heroku/ruby + `)) + }) + + test + .nock('https://api.heroku.com', { + reqheaders: {accept: 'application/vnd.heroku+json; version=3.sdk'}, + }, (api: nock.Scope) => { + api.get(`/apps/${firApp.name}`).reply(200, firApp) + api.get(`/apps/${firApp.name}/releases`).reply(200, []) + }) + .stdout() + .stderr() + .command(['buildpacks', '-a', cedarApp.name]) + .it('# returns nothing when no releases', ctx => { + expect(ctx.stderr).to.equal('') + expect(ctx.stdout).to.equal(heredoc(` + ⬢ ${cedarApp.name} has no Buildpacks. + `)) }) }) diff --git a/yarn.lock b/yarn.lock index e64080ea81..2c435f2312 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10625,7 +10625,6 @@ __metadata: tunnel-ssh: 4.1.6 typescript: 4.8.4 urijs: ^1.19.11 - valid-url: ^1.0.9 validator: ^13.7.0 word-wrap: ^1.2.5 ws: ^6.2.2 @@ -17302,13 +17301,6 @@ __metadata: languageName: node linkType: hard -"valid-url@npm:^1.0.9": - version: 1.0.9 - resolution: "valid-url@npm:1.0.9" - checksum: 3ecb030559404441c2cf104cbabab8770efb0f36d117db03d1081052ef133015a68806148ce954bb4dd0b5c42c14b709a88783c93d66b0916cb67ba771c98702 - languageName: node - linkType: hard - "validate-npm-package-license@npm:^3.0.1, validate-npm-package-license@npm:^3.0.4": version: 3.0.4 resolution: "validate-npm-package-license@npm:3.0.4"