Skip to content

Commit 1c790d2

Browse files
authored
api-diff tool: pick an omicron PR, see diff for generated API (#1819)
* api-diff tool: pick an omicron PR, see diff for generated API * increase license check location threshold for long shebang * only compare Api.ts, use git difftool as shortcut to preferred diff tool * rewrite in bash and nu. nu has args and flags, documented for free * delete nu and bash scripts, polish up TS one
1 parent 97be772 commit 1c790d2

File tree

1 file changed

+122
-0
lines changed

1 file changed

+122
-0
lines changed

tools/deno/api-diff.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#! /usr/bin/env -S deno run --allow-run --allow-net --allow-read --allow-write=/tmp --allow-env
2+
3+
/*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
7+
*
8+
* Copyright Oxide Computer Company
9+
*/
10+
import * as flags from 'https://deno.land/std@0.208.0/flags/mod.ts'
11+
import { exists } from 'https://deno.land/std@0.208.0/fs/mod.ts'
12+
import { $, CommandBuilder } from 'https://deno.land/x/dax@0.35.0/mod.ts'
13+
14+
const HELP = `
15+
Display changes to API client caused by a given Omicron PR. Works by downloading
16+
the OpenAPI spec before and after, generating clients in temp dirs, and diffing.
17+
18+
Requirements:
19+
- Deno (which you have if you're seeing this message)
20+
- GitHub CLI (gh)
21+
22+
Usage:
23+
./tools/deno/api-diff.ts [-f] [PR number]
24+
./tools/deno/api-diff.ts -h
25+
26+
Flags:
27+
-f, --force Download spec and regen client even if dir already exists
28+
-h, --help Show this help message
29+
30+
Parameters:
31+
PR number <int>: If left out, interactive picker is shown
32+
`.trim()
33+
34+
// inspired by: https://github.com/dsherret/dax/issues/137#issuecomment-1603848769
35+
declare module 'https://deno.land/x/dax@0.35.0/mod.ts' {
36+
interface CommandBuilder {
37+
pipe(next: CommandBuilder): CommandBuilder
38+
}
39+
}
40+
41+
CommandBuilder.prototype.pipe = function (next: CommandBuilder): CommandBuilder {
42+
const p = this.stdout('piped').spawn()
43+
return next.stdin(p.stdout())
44+
}
45+
46+
// have to do this this way because I couldn't figure out how to get
47+
// my stupid bash function to show up here. I'm sure it's possible
48+
async function pickPr() {
49+
const listPRs = () =>
50+
$`gh pr list -R oxidecomputer/omicron --limit 100
51+
--json number,title,updatedAt,author
52+
--template '{{range .}}{{tablerow .number .title .author.name (timeago .updatedAt)}}{{end}}'`
53+
const picker = () => $`fzf --height 25% --reverse`
54+
const cut = () => $`cut -f1 -d ' '`
55+
56+
const prNum = await listPRs().pipe(picker()).pipe(cut()).text()
57+
if (!/^\d+$/.test(prNum)) {
58+
console.error(`Error picking PR. Expected number, got '${prNum}'`)
59+
Deno.exit()
60+
}
61+
return parseInt(prNum, 10)
62+
}
63+
64+
async function getPrRange(prNum: number) {
65+
const query = `{
66+
repository(owner: "oxidecomputer", name: "omicron") {
67+
pullRequest(number: ${prNum}) {
68+
baseRefOid
69+
headRefOid
70+
}
71+
}
72+
}`
73+
const pr = await $`gh api graphql -f query=${query}`.json()
74+
const { baseRefOid: base, headRefOid: head } = pr.data.repository.pullRequest
75+
return { base, head } as { base: string; head: string }
76+
}
77+
78+
async function genForCommit(commit: string, force: boolean) {
79+
const tmpDir = `/tmp/api-diff/${commit}`
80+
const alreadyExists = await exists(tmpDir)
81+
82+
// if the directory already exists, skip it
83+
if (force || !alreadyExists) {
84+
await $`rm -rf ${tmpDir}`
85+
await $`mkdir -p ${tmpDir}`
86+
await $`npm run --silent --prefix ../oxide.ts gen-from ${commit} ${tmpDir}`
87+
await $`npx prettier --write --log-level error ${tmpDir}`
88+
}
89+
90+
return tmpDir
91+
}
92+
93+
//////////////////////////////
94+
// ACTUAL SCRIPT FOLLOWS
95+
//////////////////////////////
96+
97+
if (!$.commandExistsSync('gh')) throw Error('Need gh (GitHub CLI)')
98+
99+
const args = flags.parse(Deno.args, {
100+
alias: { force: ['f'], h: 'help' },
101+
boolean: ['force', 'help'],
102+
})
103+
104+
if (args.help) {
105+
console.log(HELP)
106+
Deno.exit()
107+
}
108+
109+
const prNum = args._[0] ? args._[0] : await pickPr()
110+
111+
if (typeof prNum !== 'number') {
112+
console.error(`PR number must be a number. Got '${prNum}' instead.`)
113+
Deno.exit()
114+
}
115+
116+
const { base, head } = await getPrRange(prNum)
117+
118+
const tmpDirBase = await genForCommit(base, args.force)
119+
const tmpDirHead = await genForCommit(head, args.force)
120+
121+
// git difftool is a trick to diff with whatever you have git set to use
122+
await $`git --no-pager difftool ${tmpDirBase}/Api.ts ${tmpDirHead}/Api.ts || true`

0 commit comments

Comments
 (0)