@@ -42,6 +42,7 @@ const SPEC_RAW_URL = (commit: string, filename: string) =>
4242async function resolveCommit ( ref ?: string | number ) : Promise < string > {
4343 if ( ref === undefined ) return resolveCommit ( await pickPr ( ) )
4444 if ( typeof ref === 'number' ) {
45+ console . error ( `Resolving PR #${ ref } to commit...` )
4546 const query = `{
4647 repository(owner: "oxidecomputer", name: "omicron") {
4748 pullRequest(number: ${ ref } ) { headRefOid }
@@ -50,37 +51,67 @@ async function resolveCommit(ref?: string | number): Promise<string> {
5051 const pr = await $ `gh api graphql -f query=${ query } ` . json ( )
5152 return pr . data . repository . pullRequest . headRefOid
5253 }
54+ // Tags, branches, and SHAs are passed through directly — the GitHub
55+ // contents API accepts any ref in the ?ref= param
5356 return ref
5457}
5558
59+ /** 5 or fewer digits is a PR number; longer digit strings are short SHAs */
5660const normalizeRef = ( ref ?: string | number ) =>
57- typeof ref === 'string' && / ^ \d + $ / . test ( ref ) ? parseInt ( ref , 10 ) : ref
61+ typeof ref === 'string' && / ^ \d { 1 , 5 } $ / . test ( ref ) ? parseInt ( ref , 10 ) : ref
62+
63+ async function listSchemaDir ( ref : string ) {
64+ console . error ( `Fetching schema list for ${ ref } ...` )
65+ try {
66+ return await $ `gh api ${ SPEC_DIR_URL ( ref ) } ` . stderr ( 'null' ) . json ( )
67+ } catch {
68+ throw new Error (
69+ `Could not list openapi/nexus/ at ref '${ ref } '. ` +
70+ `This ref may predate the versioned schema directory.`
71+ )
72+ }
73+ }
5874
59- async function getLatestSchema ( commit : string ) {
60- const contents = await $ `gh api ${ SPEC_DIR_URL ( commit ) } ` . json ( )
75+ async function getLatestSchema ( ref : string ) {
76+ const contents = await listSchemaDir ( ref )
77+ const schemaFiles = contents
78+ . map ( ( f : { name : string } ) => f . name )
79+ . filter ( ( n : string ) => n . startsWith ( 'nexus-' ) )
80+ . sort ( )
6181 const latestLink = contents . find ( ( f : { name : string } ) => f . name === 'nexus-latest.json' )
62- if ( ! latestLink ) throw new Error ( 'nexus-latest.json not found' )
82+ if ( ! latestLink ) {
83+ throw new Error (
84+ `nexus-latest.json not found at ref '${ ref } '. ` +
85+ `Available schemas: ${ schemaFiles . join ( ', ' ) || '(none)' } `
86+ )
87+ }
6388 return ( await fetch ( latestLink . download_url ) . then ( ( r ) => r . text ( ) ) ) . trim ( )
6489}
6590
66- /** When diffing a single commit , we diff its latest schema against the previous one */
67- async function getLatestAndPreviousSchema ( commit : string ) {
68- const contents = await $ `gh api ${ SPEC_DIR_URL ( commit ) } ` . json ( )
91+ /** When diffing a single ref , we diff its latest schema against the previous one */
92+ async function getLatestAndPreviousSchema ( ref : string ) {
93+ const contents = await listSchemaDir ( ref )
6994
7095 const latestLink = contents . find ( ( f : { name : string } ) => f . name === 'nexus-latest.json' )
71- if ( ! latestLink ) throw new Error ( 'nexus-latest.json not found' )
72- const latest = ( await fetch ( latestLink . download_url ) . then ( ( r ) => r . text ( ) ) ) . trim ( )
73-
7496 const schemaFiles = contents
7597 . filter (
7698 ( f : { name : string } ) => f . name . startsWith ( 'nexus-' ) && f . name !== 'nexus-latest.json'
7799 )
78100 . map ( ( f : { name : string } ) => f . name )
79101 . sort ( )
80102
103+ if ( ! latestLink ) {
104+ throw new Error (
105+ `nexus-latest.json not found at ref '${ ref } '. ` +
106+ `Available schemas: ${ schemaFiles . join ( ', ' ) || '(none)' } `
107+ )
108+ }
109+ const latest = ( await fetch ( latestLink . download_url ) . then ( ( r ) => r . text ( ) ) ) . trim ( )
110+
81111 const latestIndex = schemaFiles . indexOf ( latest )
82- if ( latestIndex === - 1 ) throw new Error ( `Latest schema ${ latest } not found in dir` )
83- if ( latestIndex === 0 ) throw new Error ( 'No previous schema version found' )
112+ if ( latestIndex === - 1 )
113+ throw new Error ( `Latest schema ${ latest } not found in dir at ref '${ ref } '` )
114+ if ( latestIndex === 0 ) throw new Error ( `No previous schema version found at ref '${ ref } '` )
84115
85116 return { previous : schemaFiles [ latestIndex - 1 ] , latest }
86117}
@@ -90,24 +121,25 @@ async function resolveTarget(ref1?: string | number, ref2?: string): Promise<Dif
90121 if ( ref2 !== undefined ) {
91122 if ( ref1 === undefined )
92123 throw new ValidationError ( 'Provide a base ref when passing two refs' )
93- const [ baseCommit , headCommit ] = await Promise . all ( [
124+ const [ baseRef , headRef ] = await Promise . all ( [
94125 resolveCommit ( normalizeRef ( ref1 ) ) ,
95126 resolveCommit ( normalizeRef ( ref2 ) ) ,
96127 ] )
128+ console . error ( `Comparing ${ baseRef } vs ${ headRef } ` )
97129 const [ baseSchema , headSchema ] = await Promise . all ( [
98- getLatestSchema ( baseCommit ) ,
99- getLatestSchema ( headCommit ) ,
130+ getLatestSchema ( baseRef ) ,
131+ getLatestSchema ( headRef ) ,
100132 ] )
101- return { baseCommit, baseSchema, headCommit, headSchema }
133+ return { baseCommit : baseRef , baseSchema, headCommit : headRef , headSchema }
102134 }
103135
104- // Single ref: compare previous schema to latest within that commit
105- const commit = await resolveCommit ( normalizeRef ( ref1 ) )
106- const { previous, latest } = await getLatestAndPreviousSchema ( commit )
136+ // Single ref: compare previous schema to latest within that ref
137+ const ref = await resolveCommit ( normalizeRef ( ref1 ) )
138+ const { previous, latest } = await getLatestAndPreviousSchema ( ref )
107139 return {
108- baseCommit : commit ,
140+ baseCommit : ref ,
109141 baseSchema : previous ,
110- headCommit : commit ,
142+ headCommit : ref ,
111143 headSchema : latest ,
112144 }
113145}
@@ -170,9 +202,8 @@ await new Command()
170202
171203Arguments:
172204 No args Interactive PR picker
173- <pr> PR number (e.g., 1234)
174- <commit> Commit SHA
175- <base> <head> Two refs (commits or PRs), compare latest schema on each
205+ <ref> PR number, commit SHA, branch, or tag
206+ <base> <head> Two refs, compare latest schema on each
176207
177208Dependencies:
178209 - Deno
@@ -193,22 +224,27 @@ Dependencies:
193224 } )
194225 . arguments ( '[ref1:string] [ref2:string]' )
195226 . action ( async ( options , ref ?: string , ref2 ?: string ) => {
196- const target = await resolveTarget ( ref , ref2 )
197- const force = options . force ?? false
227+ try {
228+ const target = await resolveTarget ( ref , ref2 )
229+ const force = options . force ?? false
198230
199- const [ baseSchema , headSchema ] = await Promise . all ( [
200- ensureSchema ( target . baseCommit , target . baseSchema , force ) ,
201- ensureSchema ( target . headCommit , target . headSchema , force ) ,
202- ] )
203-
204- if ( options . format === 'schema' ) {
205- await runDiff ( baseSchema , headSchema , target . baseSchema , target . headSchema )
206- } else {
207- const [ baseClient , headClient ] = await Promise . all ( [
208- ensureClient ( baseSchema , force ) ,
209- ensureClient ( headSchema , force ) ,
231+ const [ baseSchema , headSchema ] = await Promise . all ( [
232+ ensureSchema ( target . baseCommit , target . baseSchema , force ) ,
233+ ensureSchema ( target . headCommit , target . headSchema , force ) ,
210234 ] )
211- await runDiff ( baseClient , headClient , target . baseSchema , target . headSchema )
235+
236+ if ( options . format === 'schema' ) {
237+ await runDiff ( baseSchema , headSchema , target . baseSchema , target . headSchema )
238+ } else {
239+ const [ baseClient , headClient ] = await Promise . all ( [
240+ ensureClient ( baseSchema , force ) ,
241+ ensureClient ( headSchema , force ) ,
242+ ] )
243+ await runDiff ( baseClient , headClient , target . baseSchema , target . headSchema )
244+ }
245+ } catch ( e ) {
246+ console . error ( `error: ${ e instanceof Error ? e . message : e } ` )
247+ Deno . exit ( 1 )
212248 }
213249 } )
214250 . parse ( Deno . args )
0 commit comments