1- #! /usr/bin/env -S deno run --allow-run=gh,git --allow-net --allow-read --allow-write --allow-env
1+ #! /usr/bin/env -S deno run --allow-run=gh,git,mktemp --allow-net --allow-read --allow-write --allow-env
22
33/*
44 * This Source Code Form is subject to the terms of the Mozilla Public
1010import * as flags from 'https://deno.land/std@0.159.0/flags/mod.ts'
1111import * as path from 'https://deno.land/std@0.159.0/path/mod.ts'
1212import $ from 'https://deno.land/x/dax@0.39.2/mod.ts'
13+ import { existsSync } from 'jsr:@std/fs@1.0'
1314
1415const HELP = `
15- Update tools/console_version in ../omicron with current console commit
16- hash and tarball hash and create PR in Omicron with that change.
16+ Update tools/console_version in ../omicron to the specified console
17+ commit and create PR in Omicron with that change. We use a git worktree
18+ to avoid touching your Omicron clone.
1719
1820Requirements:
1921 - GitHub CLI installed
2022 - Omicron is a sibling dir to console
2123
2224Usage:
23- ./tools/deno/bump-omicron.ts [options]
25+ ./tools/deno/bump-omicron.ts [commit-ish=main] [ options]
2426
2527Options:
2628 -d, --dry-run Dry run, showing changes without creating PR
2729 -h, --help Show this help message
2830 -m, --message <msg> Add message to PR title: 'Bump web console (<msg>)'
2931`
3032
31- const OMICRON_DIR = '../omicron'
32- const VERSION_FILE = path . join ( OMICRON_DIR , 'tools/console_version' )
33-
33+ const OMICRON_DIR = path . resolve ( '../omicron' )
3434const GH_MISSING = 'GitHub CLI not found. Please install it and try again.'
35- const VERSION_FILE_MISSING = `Omicron console version file at '${ VERSION_FILE } ' not found. This script assumes Omicron is cloned in a sibling directory next to Console.`
3635
3736/**
3837 * These lines get printed in an Omicron PR, so any references to commits or
@@ -56,29 +55,28 @@ function linkifyGitLog(line: string): string {
5655 return `* ${ shaLink } ${ rest } `
5756}
5857
59- // script starts here
58+ async function makeOmicronWorktree ( ) {
59+ const tmpDir = await $ `mktemp -d` . text ( )
60+ await $ `git worktree add ${ tmpDir } origin/main` . cwd ( OMICRON_DIR ) . quiet ( 'stdout' )
6061
61- const args = flags . parse ( Deno . args , {
62- alias : { dryRun : [ 'd' , 'dry-run' ] , h : 'help' , m : 'message' } ,
63- boolean : [ 'dryRun' , 'help' ] ,
64- string : [ 'message' ] ,
65- } )
66-
67- if ( args . help ) {
68- console . info ( HELP )
69- Deno . exit ( )
62+ return {
63+ dir : tmpDir ,
64+ [ Symbol . asyncDispose ] : async function ( ) {
65+ console . log ( 'Cleaning up worktree' )
66+ await $ `git worktree remove ${ tmpDir } ` . cwd ( OMICRON_DIR ) . quiet ( 'stdout' )
67+ } ,
68+ }
7069}
7170
72- const newCommit = await $ `git rev-parse HEAD` . text ( )
73-
74- const shaUrl = `https://dl.oxide.computer/releases/console/${ newCommit } .sha256.txt`
75- const shaResp = await fetch ( shaUrl )
71+ async function fetchTarballSha ( commit : string ) {
72+ const shaUrl = `https://dl.oxide.computer/releases/console/${ commit } .sha256.txt`
73+ const shaResp = await fetch ( shaUrl )
7674
77- if ( ! shaResp . ok ) {
78- const workflowId =
79- await $ `gh run list -L 1 -w 'Upload assets to dl.oxide.computer' --json databaseId --jq '.[0].databaseId'` . text ( )
80- console . error (
81- `
75+ if ( ! shaResp . ok ) {
76+ const workflowId =
77+ await $ `gh run list -L 1 -w 'Upload assets to dl.oxide.computer' --json databaseId --jq '.[0].databaseId'` . text ( )
78+ console . error (
79+ `
8280Failed to fetch console tarball SHA. Either the current commit is not on origin/main or the asset upload job is still running.
8381
8482Status: ${ shaResp . status }
@@ -87,90 +85,133 @@ Body: ${await shaResp.text()}
8785
8886Running 'gh run watch ${ workflowId } ' to watch the current upload action.
8987`
90- )
91- await $ `gh run watch ${ workflowId } `
92- Deno . exit ( 1 )
93- }
94-
95- const newSha2 = ( await shaResp . text ( ) ) . trim ( )
96- const newVersionFile = `COMMIT="${ newCommit } "\nSHA2="${ newSha2 } "\n`
88+ )
89+ await $ `gh run watch ${ workflowId } `
90+ return Deno . exit ( 1 )
91+ }
9792
98- const oldVersionFile = await Deno . readTextFile ( VERSION_FILE ) . catch ( ( ) => {
99- throw Error ( VERSION_FILE_MISSING )
100- } )
93+ return ( await shaResp . text ( ) ) . trim ( )
94+ }
10195
102- const oldCommit = / C O M M I T = " ? ( [ a - f 0 - 9 ] + ) " ? / . exec ( oldVersionFile ) ?. [ 1 ]
103- if ( ! oldCommit ) throw Error ( 'Could not parse existing version file' )
96+ async function getOldCommit ( ) {
97+ const oldVersionFile = await $ `git show origin/main:tools/console_version`
98+ . cwd ( OMICRON_DIR )
99+ . text ( )
104100
105- if ( oldCommit === newCommit ) {
106- console . info ( 'Nothing to update: Omicron already has the current commit pinned ')
107- Deno . exit ( )
101+ const oldCommit = / C O M M I T = " ? ( [ a - f 0 - 9 ] + ) " ? / . exec ( oldVersionFile ) ?. [ 1 ]
102+ if ( ! oldCommit ) throw new Error ( 'Could not parse existing version file ')
103+ return oldCommit
108104}
109105
110- const commitRange = `${ oldCommit . slice ( 0 , 8 ) } ...${ newCommit . slice ( 0 , 8 ) } `
106+ async function makeOmicronPR (
107+ consoleCommit : string ,
108+ prTitle : string ,
109+ changesLink : string ,
110+ commits : string
111+ ) {
112+ const branchName = 'bump-console-' + consoleCommit . slice ( 0 , 8 )
113+
114+ {
115+ // create git worktree for latest main in temp dir. `using` ensures this gets
116+ // cleaned up at the end of the block
117+ await using worktree = await makeOmicronWorktree ( )
118+
119+ const newSha2 = await fetchTarballSha ( consoleCommit )
120+ const newVersionFile = `COMMIT="${ consoleCommit } "\nSHA2="${ newSha2 } "\n`
121+
122+ const versionFileAbsPath = path . resolve ( worktree . dir , 'tools/console_version' )
123+ await Deno . writeTextFile ( versionFileAbsPath , newVersionFile )
124+ console . info ( 'Updated ' , versionFileAbsPath )
125+
126+ // cd to omicron, pull main, create new branch, commit changes, push, PR it, go back to
127+ // main, delete branch
128+ Deno . chdir ( worktree . dir )
129+ await $ `git checkout -b ${ branchName } `
130+ console . info ( 'Created branch' , branchName )
131+
132+ await $ `git add tools/console_version`
133+
134+ // commits are console commits, so they won't auto-link in omicron
135+ const commitsMarkdown = commits . split ( '\n' ) . map ( linkifyGitLog ) . join ( '\n' )
136+ const prBody = `${ changesLink } \n\n${ commitsMarkdown } `
137+ await $ `git commit -m ${ prTitle } -m ${ prBody } `
138+
139+ await $ `git push --set-upstream origin ${ branchName } `
140+ console . info ( 'Committed changes and pushed' )
141+
142+ // create PR
143+ const prUrl = await $ `gh pr create --title ${ prTitle } --body ${ prBody } ` . text ( )
144+ console . info ( 'PR created:' , prUrl )
145+
146+ // set it to auto merge
147+ const prNum = prUrl . match ( / \d + $ / ) ! [ 0 ]
148+ await $ `gh pr merge ${ prNum } --auto --squash`
149+ console . info ( 'PR set to auto-merge when CI passes' )
150+ }
111151
112- const commits = await $ `git log --graph --oneline ${ commitRange } ` . text ( )
113- // commits are console commits, so they won't auto-link in omicron
114- const commitsMarkdown = commits . split ( '\n' ) . map ( linkifyGitLog ) . join ( '\n' )
152+ // worktree has been cleaned up, so branch delete is allowed
153+ await $ `git branch -D ${ branchName } ` . cwd ( OMICRON_DIR )
154+ }
115155
116- const changesLine = `https://github.com/oxidecomputer/console/compare/${ commitRange } `
156+ // wrapped in a function so we can do early returns rather than early
157+ // Deno.exits, which mess up the worktree cleanup
158+ async function run ( commitIsh : string , dryRun : boolean , messageArg : string | undefined ) {
159+ await $ `git fetch` . cwd ( OMICRON_DIR )
117160
118- const branchName = 'bump-console-' + newCommit . slice ( 0 , 8 )
119- const prBody = ` ${ changesLine } \n\n ${ commitsMarkdown } `
161+ const oldConsoleCommit = await getOldCommit ( )
162+ const newConsoleCommit = await $ `git rev-parse ${ commitIsh } ` . text ( )
120163
121- console . info ( `\n${ changesLine } \n\n${ commits } \n` )
164+ if ( oldConsoleCommit === newConsoleCommit ) {
165+ console . info ( `Nothing to update: Omicron already has ${ newConsoleCommit } pinned` )
166+ return
167+ }
122168
123- if ( args . dryRun ) Deno . exit ( )
169+ const commitRange = `${ oldConsoleCommit . slice ( 0 , 8 ) } ...${ newConsoleCommit . slice ( 0 , 8 ) } `
170+ const commits = await $ `git log --graph --oneline ${ commitRange } ` . text ( )
171+ const changesLink = `https://github.com/oxidecomputer/console/compare/${ commitRange } `
124172
125- const message =
126- args . message ||
127- ( await $ . prompt ( {
128- message : 'Description? (enter to skip)' ,
129- noClear : true ,
130- } ) )
173+ console . info ( `\n${ changesLink } \n\n${ commits } \n` )
131174
132- const prTitle = 'Bump web console' + ( message ? ` ( ${ message } )` : '' )
175+ if ( dryRun ) return
133176
134- console . info ( `\nPR title: ${ prTitle } \n` )
177+ const message =
178+ messageArg ||
179+ ( await $ . prompt ( { message : 'Description? (enter to skip)' , noClear : true } ) )
180+ const prTitle = 'Bump web console' + ( message ? ` (${ message } )` : '' )
181+ console . info ( `\nPR title: ${ prTitle } \n` )
135182
136- const go = await $ . confirm ( { message : 'Make Omicron PR?' , noClear : true } )
137- if ( ! go ) Deno . exit ( )
183+ const go = await $ . confirm ( { message : 'Make Omicron PR?' , noClear : true } )
184+ if ( ! go ) return
138185
139- if ( ! $ . commandExistsSync ( 'gh' ) ) throw Error ( GH_MISSING )
186+ if ( ! $ . commandExistsSync ( 'gh' ) ) throw new Error ( GH_MISSING )
140187
141- await Deno . writeTextFile ( VERSION_FILE , newVersionFile )
142- console . info ( 'Updated ' , VERSION_FILE )
188+ const consoleDir = Deno . cwd ( ) // save it so we can change back
143189
144- const consoleDir = Deno . cwd ( )
190+ await makeOmicronPR ( newConsoleCommit , prTitle , changesLink , commits )
145191
146- // cd to omicron, pull main, create new branch, commit changes, push, PR it, go back to
147- // main, delete branch
148- Deno . chdir ( OMICRON_DIR )
149- await $ `git checkout main`
150- await $ `git pull`
151- await $ `git checkout -b ${ branchName } `
152- console . info ( 'Created branch' , branchName )
192+ // bump omicron tag in console to current commit
193+ Deno . chdir ( consoleDir )
194+ console . info ( 'Bumping omicron tag in console' )
195+ await $ `git tag -f -a omicron -m 'pinned commit on omicron main' ${ commitIsh } `
196+ await $ `git push -f origin tag omicron`
197+ }
153198
154- await $ `git add tools/console_version`
155- await $ `git commit -m ${ prTitle } -m ${ prBody } `
156- await $ `git push --set-upstream origin ${ branchName } `
157- console . info ( 'Committed changes and pushed' )
199+ // script starts here
158200
159- // create PR
160- const prUrl = await $ `gh pr create --title ${ prTitle } --body ${ prBody } ` . text ( )
161- console . info ( 'PR created:' , prUrl )
201+ const args = flags . parse ( Deno . args , {
202+ alias : { dryRun : [ 'd' , 'dry-run' ] , h : 'help' , m : 'message' } ,
203+ boolean : [ 'dryRun' , 'help' ] ,
204+ string : [ 'message' ] ,
205+ } )
162206
163- // set it to auto merge
164- const prNum = prUrl . match ( / \d + $ / ) ! [ 0 ]
165- await $ `gh pr merge ${ prNum } --auto --squash`
166- console . info ( 'PR set to auto-merge when CI passes' )
207+ if ( args . help ) {
208+ console . info ( HELP )
209+ Deno . exit ( )
210+ }
167211
168- await $ `git checkout main`
169- await $ `git branch -D ${ branchName } `
170- console . info ( 'Checked out omicron main, deleted branch' , branchName )
212+ if ( ! existsSync ( OMICRON_DIR ) ) {
213+ throw new Error ( `Omicron repo not found at ${ OMICRON_DIR } ` )
214+ }
171215
172- // bump omicron tag in console to current commit
173- Deno . chdir ( consoleDir )
174- console . info ( 'Bumping omicron tag in console' )
175- await $ `git tag -f -a omicron -m 'pinned commit on omicron main'`
176- await $ `git push -f origin tag omicron`
216+ const commitIsh = args . _ [ 0 ] ?. toString ( ) || 'main'
217+ await run ( commitIsh , args . dryRun , args . message )
0 commit comments