Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions src/commands/ci/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {getPipeline} from '../../lib/ci/pipelines.js'
import {createSourceBlob} from '../../lib/ci/source.js'
import {waitForStates} from '../../lib/ci/test-run.js'
import Git from '../../lib/git/git.js'
import KolkrabbiAPI from '../../lib/pipelines/kolkrabbi-api.js'
import Dyno from '../../lib/run/dyno.js'

// Default command. Run setup, source profile.d scripts and open a bash session
Expand Down Expand Up @@ -38,11 +37,8 @@ export default class Debug extends Command {
const {flags} = await this.parse(Debug)
const pipeline = await getPipeline(flags, this.heroku)

const kolkrabbi = new KolkrabbiAPI(this.config.userAgent, () => this.heroku.auth)

const pipelineRepository = await kolkrabbi.getPipelineRepository(pipeline.id)
const organization = pipelineRepository.organization
&& pipelineRepository.organization.name
const {body: fullPipeline} = await this.heroku.get<Heroku.Pipeline>(`/pipelines/${pipeline.id}`, {headers: {Accept: 'application/vnd.heroku+json; version=3.pipelines'}})
const organization = fullPipeline.owner?.type === 'team' ? (fullPipeline.owner.name ?? fullPipeline.owner.id) : undefined

const git = new Git()
const commit = await git.readCommit('HEAD')
Expand Down
5 changes: 2 additions & 3 deletions src/commands/ci/rerun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import * as Heroku from '@heroku-cli/schema'
import * as color from '@heroku/heroku-cli-util/color'
import {Args, ux} from '@oclif/core'

import * as Kolkrabbi from '../../lib/ci/interfaces/kolkrabbi.js'
import {getPipeline} from '../../lib/ci/pipelines.js'
import {createSourceBlob} from '../../lib/ci/source.js'
import {displayAndExit} from '../../lib/ci/test-run.js'
Expand Down Expand Up @@ -43,10 +42,10 @@ export default class CiReRun extends Command {
const sourceBlobUrl = await createSourceBlob(sourceTestRun.commit_sha, this)
ux.action.stop()

const {body: pipelineRepository} = await this.heroku.get<Kolkrabbi.KolkrabbiApiPipelineRepositories>(`https://kolkrabbi.heroku.com/pipelines/${pipeline.id}/repository`)
const {body: fullPipeline} = await this.heroku.get<Heroku.Pipeline>(`/pipelines/${pipeline.id}`, {headers: {Accept: 'application/vnd.heroku+json; version=3.pipelines'}})

ux.action.start('Starting test run')
const organization = pipelineRepository.organization && pipelineRepository.organization.name
const organization = fullPipeline.owner?.type === 'team' ? (fullPipeline.owner.name ?? fullPipeline.owner.id) : undefined

const {body: testRun} = await this.heroku.post<Heroku.TestRun>('/test-runs', {
body: {
Expand Down
5 changes: 2 additions & 3 deletions src/commands/ci/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import * as color from '@heroku/heroku-cli-util/color'
import {ux} from '@oclif/core/ux'

import {gitService} from '../../lib/ci/git.js'
import * as Kolkrabbi from '../../lib/ci/interfaces/kolkrabbi.js'
import {getPipeline} from '../../lib/ci/pipelines.js'
import {createSourceBlob} from '../../lib/ci/source.js'
import {displayAndExit} from '../../lib/ci/test-run.js'
Expand All @@ -31,8 +30,8 @@ export default class CiRun extends Command {
ux.action.stop()

ux.action.start('Starting test run')
const {body: pipelineRepository} = await this.heroku.get<Kolkrabbi.KolkrabbiApiPipelineRepositories>(`https://kolkrabbi.heroku.com/pipelines/${pipeline.id}/repository`)
const organization = pipelineRepository.organization && pipelineRepository.organization.name
const {body: fullPipeline} = await this.heroku.get<Heroku.Pipeline>(`/pipelines/${pipeline.id}`, {headers: {Accept: 'application/vnd.heroku+json; version=3.pipelines'}})
const organization = fullPipeline.owner?.type === 'team' ? (fullPipeline.owner.name ?? fullPipeline.owner.id) : undefined
const {body: testRun} = await this.heroku.post<Heroku.TestRun>('/test-runs', {
body: {
commit_branch: commit.branch,
Expand Down
13 changes: 5 additions & 8 deletions src/commands/pipelines/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ import * as color from '@heroku/heroku-cli-util/color'
import {Args, ux} from '@oclif/core'

import {getPipeline} from '../../lib/api.js'
import GitHubAPI from '../../lib/pipelines/github-api.js'
import KolkrabbiAPI from '../../lib/pipelines/kolkrabbi-api.js'
import getGitHubToken from '../../lib/pipelines/setup/get-github-token.js'
import getNameAndRepo from '../../lib/pipelines/setup/get-name-and-repo.js'
import getRepo from '../../lib/pipelines/setup/get-repo.js'
import {nameAndRepo} from '../../lib/pipelines/setup/validate.js'
Expand Down Expand Up @@ -45,20 +42,20 @@ export default class Connect extends Command {
return
}

const kolkrabbi = new KolkrabbiAPI(this.config.userAgent, () => this.heroku.auth)
const github = new GitHubAPI(this.config.userAgent, await getGitHubToken(kolkrabbi))

const {
name: pipelineName,
repo: repoName,
} = await getNameAndRepo(combinedInputs)

const repo = await getRepo(github, repoName)
const repo = await getRepo(this.heroku, repoName)

const pipeline = await getPipeline(this.heroku, pipelineName)

ux.action.start('Linking to repo')
await kolkrabbi.createPipelineRepository(pipeline.body.id, repo.id)
await this.heroku.post(`/pipelines/${pipeline.body.id}/repo`, {
body: {repo_url: `https://github.com/${repo.full_name}`},
headers: {Accept: 'application/vnd.heroku+json; version=3.repositories-api'},
})
ux.action.stop()
}
}
124 changes: 57 additions & 67 deletions src/commands/pipelines/diff.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {Command, flags} from '@heroku-cli/command'
import {color, hux} from '@heroku/heroku-cli-util'
import {HTTP} from '@heroku/http-call'
import {ux} from '@oclif/core/ux'

import type {OciImage, PipelineCoupling, Slug} from '../../lib/types/fir.js'
Expand All @@ -14,7 +13,8 @@ import {
SDK_HEADER,
} from '../../lib/api.js'
import {GenerationKind, getGeneration} from '../../lib/apps/generation.js'
import KolkrabbiAPI from '../../lib/pipelines/kolkrabbi-api.js'

const REPOSITORIES_API_HEADER = 'application/vnd.heroku+json; version=3.repositories-api'

interface AppInfo {
hash?: string;
Expand All @@ -35,8 +35,10 @@ export default class PipelinesDiff extends Command {
}
getAppInfo = async (appName: string, appId: string, generation: GenerationKind): Promise<AppInfo> => {
// Find GitHub connection for the app
const githubApp = await this.kolkrabbi.getAppLink(appId)
.catch(() => ({hash: null, name: appName, repo: null}))
const githubApp = await this.heroku.get<{full_name: string}>(`/apps/${appId}/repo`, {
headers: {Accept: REPOSITORIES_API_HEADER},
}).then(res => res.body)
.catch(() => ({full_name: undefined}))

// Find the commit hash of the latest release for this app
let slug: Slug
Expand All @@ -62,12 +64,58 @@ export default class PipelinesDiff extends Command {
commit = ociImages[0]?.commit
}
} catch {
return {hash: undefined, name: appName, repo: githubApp.repo}
return {hash: undefined, name: appName, repo: githubApp.full_name}
}

return {hash: commit, name: appName, repo: githubApp.full_name}
}
private diff = async (targetApp: AppInfo, downstreamApp: AppInfo, pipelineId: string) => {
if (!downstreamApp.repo) {
return ux.stdout(`\n${color.app(targetApp.name)} was not compared to ${color.app(downstreamApp.name)} as ${color.app(downstreamApp.name)} is not connected to GitHub`)
}

if (downstreamApp.repo !== targetApp.repo) {
return ux.stdout(`\n${color.app(targetApp.name)} was not compared to ${color.app(downstreamApp.name)} as ${color.app(downstreamApp.name)} is not connected to the same GitHub repo as ${color.app(targetApp.name)}`)
}

if (!downstreamApp.hash) {
return ux.stdout(`\n${color.app(targetApp.name)} was not compared to ${color.app(downstreamApp.name)} as ${color.app(downstreamApp.name)} does not have any releases`)
}

if (downstreamApp.hash === targetApp.hash) {
return ux.stdout(`\n${color.app(targetApp.name)} is up to date with ${color.app(downstreamApp.name)}`)
}

return {hash: commit, name: appName, repo: githubApp.repo}
// Do the actual GitHub diff via the repositories-api server-side proxy
try {
const {body: githubDiff} = await this.heroku.get<GitHubDiff>(`/pipelines/${pipelineId}/repo/compare?base=${downstreamApp.hash}&head=${targetApp.hash}`, {
headers: {Accept: REPOSITORIES_API_HEADER},
})

ux.stdout('')
hux.styledHeader(`${color.app(targetApp.name)} is ahead of ${color.app(downstreamApp.name)} by ${githubDiff.ahead_by} commit${githubDiff.ahead_by === 1 ? '' : 's'}`)
/* eslint-disable perfectionist/sort-objects */
const mapped = githubDiff.commits.map((commit: Commit) => ({
sha: commit.sha.slice(0, 7),
date: commit.commit.author.date,
author: commit.commit.author.name,
message: commit.commit.message.split('\n')[0],
})).reverse()
hux.table(mapped, {
sha: {
header: 'SHA',
},
date: {},
author: {},
message: {},
})
/* eslint-enable perfectionist/sort-objects */
ux.stdout(`\n${color.info(`https://github.com/${targetApp.repo}/compare/${downstreamApp.hash}...${targetApp.hash}`)}`)
} catch {
ux.stdout(`\n${color.app(targetApp.name)} was not compared to ${color.app(downstreamApp.name)} because we were unable to perform a diff`)
ux.stdout('are you sure you have pushed your latest commits to GitHub?')
}
}
kolkrabbi: KolkrabbiAPI = new KolkrabbiAPI(this.config.userAgent, () => this.heroku.auth)

async run() {
const {flags} = await this.parse(PipelinesDiff)
Expand All @@ -85,6 +133,7 @@ export default class PipelinesDiff extends Command {

const targetAppId = coupling!.app!.id!
const generation = getGeneration(pipeline)!
const pipelineId = coupling!.pipeline!.id!

ux.action.start('Fetching apps from pipeline')
const allApps = await listPipelineApps(this.heroku, coupling!.pipeline!.id!)
Expand Down Expand Up @@ -130,69 +179,10 @@ export default class PipelinesDiff extends Command {
return ux.error(`No release was found for ${targetAppName}, unable to diff`)
}

// Fetch GitHub token for the user
const githubAccount = await this.kolkrabbi.getAccount()
// Diff [{target, downstream[0]}, {target, downstream[1]}, .., {target, downstream[n]}]
const downstreamAppsInfo = appInfo.slice(1)
for (const downstreamAppInfo of downstreamAppsInfo) {
await diff(targetAppInfo, downstreamAppInfo, githubAccount.github.token, this.config.userAgent)
}
}
}

async function diff(targetApp: AppInfo, downstreamApp: AppInfo, githubToken: string, herokuUserAgent: string) {
if (!downstreamApp.repo) {
return ux.stdout(`\n${color.app(targetApp.name)} was not compared to ${color.app(downstreamApp.name)} as ${color.app(downstreamApp.name)} is not connected to GitHub`)
}

if (downstreamApp.repo !== targetApp.repo) {
return ux.stdout(`\n${color.app(targetApp.name)} was not compared to ${color.app(downstreamApp.name)} as ${color.app(downstreamApp.name)} is not connected to the same GitHub repo as ${color.app(targetApp.name)}`)
}

if (!downstreamApp.hash) {
return ux.stdout(`\n${color.app(targetApp.name)} was not compared to ${color.app(downstreamApp.name)} as ${color.app(downstreamApp.name)} does not have any releases`)
}

if (downstreamApp.hash === targetApp.hash) {
return ux.stdout(`\n${color.app(targetApp.name)} is up to date with ${color.app(downstreamApp.name)}`)
}

// Do the actual GitHub diff
try {
const path = `${targetApp.repo}/compare/${downstreamApp.hash}...${targetApp.hash}`
const headers = {
authorization: 'token ' + githubToken,
'Content-Type': 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
await this.diff(targetAppInfo, downstreamAppInfo, pipelineId)
}

if (herokuUserAgent) {
Reflect.set(headers, 'user-agent', herokuUserAgent)
}

const {body: githubDiff} = await HTTP.get<GitHubDiff>(`https://api.github.com/repos/${path}`, {headers})

ux.stdout('')
hux.styledHeader(`${color.app(targetApp.name)} is ahead of ${color.app(downstreamApp.name)} by ${githubDiff.ahead_by} commit${githubDiff.ahead_by === 1 ? '' : 's'}`)
/* eslint-disable perfectionist/sort-objects */
const mapped = githubDiff.commits.map((commit: Commit) => ({
sha: commit.sha.slice(0, 7),
date: commit.commit.author.date,
author: commit.commit.author.name,
message: commit.commit.message.split('\n')[0],
})).reverse()
hux.table(mapped, {
sha: {
header: 'SHA',
},
date: {},
author: {},
message: {},
})
/* eslint-enable perfectionist/sort-objects */
ux.stdout(`\n${color.info(`https://github.com/${path}`)}`)
} catch {
ux.stdout(`\n${color.app(targetApp.name)} was not compared to ${color.app(downstreamApp.name)} because we were unable to perform a diff`)
ux.stdout('are you sure you have pushed your latest commits to GitHub?')
}
}
26 changes: 11 additions & 15 deletions src/commands/pipelines/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ import debug from 'debug'
import openBrowser from 'open'

import {createPipeline, getAccountInfo, getTeam} from '../../lib/api.js'
import GitHubAPI from '../../lib/pipelines/github-api.js'
import KolkrabbiAPI from '../../lib/pipelines/kolkrabbi-api.js'
import createApps from '../../lib/pipelines/setup/create-apps.js'
import getCISettings from '../../lib/pipelines/setup/get-ci-settings.js'
import getGitHubToken from '../../lib/pipelines/setup/get-github-token.js'
import getNameAndRepo from '../../lib/pipelines/setup/get-name-and-repo.js'
import getRepo from '../../lib/pipelines/setup/get-repo.js'
import getSettings from '../../lib/pipelines/setup/get-settings.js'
Expand Down Expand Up @@ -55,17 +51,13 @@ export default class Setup extends Command {
return
}

const kolkrabbi = new KolkrabbiAPI(this.config.userAgent, () => this.heroku.auth)
const github = new GitHubAPI(this.config.userAgent, await getGitHubToken(kolkrabbi))

const {team, yes} = flags

const {name: pipelineName, repo: repoName} = await getNameAndRepo(args)
const stagingAppName = pipelineName + STAGING_APP_INDICATOR
const repo = await getRepo(github, repoName)
const settings = await getSettings(yes, repo.default_branch)
const repo = await getRepo(this.heroku, repoName)
const settings = await getSettings(yes)

const ciSettings = await getCISettings(yes, team)
const ownerType = team ? 'team' : 'user'

// If team or org is not specified, we assign ownership to the user creating
Expand All @@ -79,10 +71,16 @@ export default class Setup extends Command {
ux.action.stop()

ux.action.start('Linking to repo')
await kolkrabbi.createPipelineRepository(pipeline.id, repo.id)
await this.heroku.post(`/pipelines/${pipeline.id}/repo`, {
body: {repo_url: `https://github.com/${repo.full_name}`},
headers: {Accept: 'application/vnd.heroku+json; version=3.repositories-api'},
})
ux.action.stop()

const archiveURL = await kolkrabbi.getArchiveURL(repoName, repo.default_branch)
const {body: archive} = await this.heroku.get<{archive_link: string}>(`/repos/${repo.full_name}/archives/${repo.default_branch}`, {
headers: {Accept: 'application/vnd.heroku+json; version=3.repositories-api'},
})
const archiveURL = archive.archive_link

const appSetupsResult: any = await createApps(this.heroku, archiveURL, pipeline, pipelineName, stagingAppName, team)
const appSetups = appSetupsResult.map((result: any) => result.body)
Expand All @@ -91,9 +89,7 @@ export default class Setup extends Command {
await pollAppSetups(this.heroku, appSetups)
ux.action.stop()

const stagingApp = appSetups.find((appSetup: any) => appSetup.app.name === stagingAppName).app

const setup = setupPipeline(kolkrabbi, stagingApp.id, settings, pipeline.id, ciSettings)
const setup = setupPipeline(this.heroku, settings, pipeline.id, repo.full_name)

ux.action.start('Configuring pipeline')
try {
Expand Down
21 changes: 4 additions & 17 deletions src/commands/reviewapps/disable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import * as Heroku from '@heroku-cli/schema'
import * as color from '@heroku/heroku-cli-util/color'
import {ux} from '@oclif/core/ux'

import KolkrabbiAPI from '../../lib/pipelines/kolkrabbi-api.js'

export default class ReviewappsDisable extends Command {
static description = 'disable review apps and/or settings on an existing pipeline'
static examples = [
Expand Down Expand Up @@ -81,27 +79,16 @@ export default class ReviewappsDisable extends Command {
settings.wait_for_ci = false
}

const kolkrabbi = new KolkrabbiAPI(this.config.userAgent, () => this.heroku.auth)

ux.action.start('Configuring pipeline')

const {body: pipeline} = await this.heroku.get<Heroku.Pipeline>(`/pipelines/${flags.pipeline}`)

settings.pipeline = pipeline.id

try {
const {body: feature} = await this.heroku.get<Heroku.AccountFeature>('/account/features/dashboard-repositories-api')

if (feature.enabled) {
const {body: repo} = await this.heroku.get<{full_name: string}>(`/pipelines/${pipeline.id}/repo`, {
headers: {Accept: 'application/vnd.heroku+json; version=3.repositories-api'},
})
settings.repo = repo.full_name
}
} catch {
const {repository} = await kolkrabbi.getPipelineRepository(pipeline.id)
settings.repo = repository.name
}
const {body: repo} = await this.heroku.get<{full_name: string}>(`/pipelines/${pipeline.id}/repo`, {
headers: {Accept: 'application/vnd.heroku+json; version=3.repositories-api'},
})
settings.repo = repo.full_name

// eslint-disable-next-line unicorn/prefer-ternary
if (flags.autodeploy || flags['no-autodeploy'] || flags.autodestroy || flags['no-autodestroy'] || flags['wait-for-ci'] || flags['no-wait-for-ci']) {
Expand Down
Loading
Loading