1919// Usage:
2020//
2121// // package the extension (based on TAG_NAME).
22- // go run build/ release.go package
22+ // go run ./tools/ release package
2323// // publish the extension.
24- // go run build/ release.go publish
24+ // go run ./tools/ release publish
2525package main
2626
2727import (
@@ -35,39 +35,182 @@ import (
3535 "strings"
3636)
3737
38- var flagN = flag .Bool ("n" , false , "print the underlying commands but do not run them" )
38+ var (
39+ flagN = false
40+ )
41+
42+ var (
43+ cmdPackage = & command {
44+ usage : "package" ,
45+ short : "package the extension to vsix file" ,
46+ long : `package command builds the extension and produces .vsix file in -out` ,
47+ run : runPackage ,
48+ }
49+ cmdPublish = & command {
50+ usage : "publish" ,
51+ short : "publish the packaged extension (vsix) to the Visual Studio Code marketplace" ,
52+ long : `publish command publishes all the extension files in -in to the Visual Studio Code marketplace` ,
53+ run : runPublish ,
54+ }
55+
56+ allCommands = []* command {cmdPackage , cmdPublish }
57+ )
58+
59+ func init () {
60+ cmdPackage .flags .String ("out" , "." , "directory where the artifacts are written" )
61+ cmdPublish .flags .String ("in" , "." , "directory where the artifacts to be published are" )
62+
63+ addCommonFlags := func (cmd * command ) {
64+ cmd .flags .BoolVar (& flagN , "n" , flagN , "print the underlying commands but do not run them" )
65+ }
66+ for _ , cmd := range allCommands {
67+ addCommonFlags (cmd )
68+ name := cmd .name ()
69+ cmd .flags .Usage = func () {
70+ help (name )
71+ }
72+ }
73+ }
3974
4075func main () {
76+ flag .Usage = usage
4177 flag .Parse ()
42- if flag .NArg () != 1 {
43- usage ()
44- os .Exit (1 )
78+
79+ args := flag .Args ()
80+ if flag .NArg () == 0 {
81+ flag .Usage ()
82+ os .Exit (2 )
83+ }
84+ // len(args) > 0
85+
86+ if args [0 ] == "help" {
87+ flag .CommandLine .SetOutput (os .Stdout )
88+ switch len (args ) {
89+ case 1 :
90+ flag .Usage ()
91+ case 2 :
92+ help (args [1 ])
93+ default :
94+ flag .Usage ()
95+ fatalf (`too many arguments to "help"` )
96+ }
97+ os .Exit (0 )
98+ }
99+
100+ cmd := findCommand (args [0 ])
101+ if cmd == nil {
102+ flag .Usage ()
103+ os .Exit (2 )
104+ }
105+
106+ cmd .run (cmd , args [1 :])
107+ }
108+
109+ func usage () {
110+ printCommand := func (cmd * command ) {
111+ output (fmt .Sprintf ("\t %s\t %s" , cmd .name (), cmd .short ))
45112 }
46- cmd := flag .Arg (0 )
113+ output ("go run release.go [command]" )
114+ output ("The commands are:" )
115+ output ()
116+ for _ , cmd := range allCommands {
117+ printCommand (cmd )
118+ }
119+ output ()
120+ }
121+
122+ func output (msgs ... any ) {
123+ fmt .Fprintln (flag .CommandLine .Output (), msgs ... )
124+ }
125+
126+ func findCommand (name string ) * command {
127+ for _ , cmd := range allCommands {
128+ if cmd .name () == name {
129+ return cmd
130+ }
131+ }
132+ return nil
133+ }
134+
135+ func help (name string ) {
136+ cmd := findCommand (name )
137+ if cmd == nil {
138+ fatalf ("unknown command %q" , name )
139+ }
140+ output (fmt .Sprintf ("Usage: release %s" , cmd .usage ))
141+ output ()
142+ if cmd .long != "" {
143+ output (cmd .long )
144+ } else {
145+ output (fmt .Sprintf ("release %s is used to %s." , cmd .name (), cmd .short ))
146+ }
147+ anyflags := false
148+ cmd .flags .VisitAll (func (* flag.Flag ) {
149+ anyflags = true
150+ })
151+ if anyflags {
152+ output ()
153+ output ("Flags:" )
154+ output ()
155+ cmd .flags .PrintDefaults ()
156+ }
157+ }
158+
159+ type command struct {
160+ usage string
161+ short string
162+ long string
163+ flags flag.FlagSet
164+ run func (cmd * command , args []string )
165+ }
166+
167+ func (c command ) name () string {
168+ name , _ , _ := strings .Cut (c .usage , " " )
169+ return name
170+ }
171+
172+ func (c command ) lookupFlag (name string ) flag.Value {
173+ f := c .flags .Lookup (name )
174+ if f == nil {
175+ fatalf ("flag %q not found" , name )
176+ }
177+ return f .Value
178+ }
179+
180+ // runPackage implements the "package" subcommand.
181+ func runPackage (cmd * command , args []string ) {
182+ cmd .flags .Parse (args ) // will exit on error
47183
48184 checkWD ()
185+
49186 requireTools ("jq" , "npx" , "gh" , "git" )
50- requireEnvVars ("TAG_NAME" )
51187
52- tagName , version , isRC := releaseVersionInfo ()
53- vsix := fmt .Sprintf ("go-%s.vsix" , version )
188+ tagName := requireEnv ("TAG_NAME" )
54189
55- switch cmd {
56- case "package" :
57- buildPackage (version , tagName , vsix )
58- case "publish" :
59- requireEnvVars ("VSCE_PAT" , "GITHUB_TOKEN" )
60- publish (tagName , vsix , isRC )
61- default :
62- usage ()
63- os .Exit (1 )
64- }
190+ version , _ := releaseVersionInfo (tagName )
191+ checkPackageJSON (tagName )
192+ outDir := prepareOutputDir (cmd .lookupFlag ("out" ).String ())
193+ vsix := filepath .Join (outDir , fmt .Sprintf ("go-%s.vsix" , version ))
194+ buildPackage (version , tagName , vsix )
65195}
66196
67- func usage () {
68- fmt .Fprintf (os .Stderr , "Usage: %s <flags> [package|publish]\n \n " , os .Args [0 ])
69- fmt .Fprintln (os .Stderr , "Flags:" )
70- flag .PrintDefaults ()
197+ // runPublish implements the "publish" subcommand.
198+ func runPublish (cmd * command , args []string ) {
199+ cmd .flags .Parse (args ) // will exit on error
200+
201+ checkWD ()
202+
203+ requireTools ("jq" , "npx" , "gh" , "git" )
204+
205+ requireEnv ("VSCE_PAT" )
206+ requireEnv ("GITHUB_TOKEN" )
207+ tagName := requireEnv ("TAG_NAME" )
208+
209+ version , isRC := releaseVersionInfo (tagName )
210+ checkPackageJSON (tagName )
211+ inDir := prepareInputDir (cmd .lookupFlag ("in" ).String ())
212+ vsix := filepath .Join (inDir , fmt .Sprintf ("go-%s.vsix" , version ))
213+ publish (tagName , vsix , isRC )
71214}
72215
73216func fatalf (format string , args ... any ) {
@@ -76,6 +219,50 @@ func fatalf(format string, args ...any) {
76219 os .Exit (1 )
77220}
78221
222+ // prepareOutputDir normalizes --output-dir. If the directory doesn't exist,
223+ // prepareOutputDir creates it.
224+ func prepareOutputDir (outDir string ) string {
225+ if outDir == "" {
226+ outDir = "."
227+ }
228+
229+ if flagN {
230+ // -n used for testing. don't create the directory nor try to resolve.
231+ return outDir
232+ }
233+
234+ // resolve to absolute path so output dir can be consitent
235+ // even when child processes accessing it need to run in a different directory.
236+ dir , err := filepath .Abs (outDir )
237+ if err != nil {
238+ fatalf ("failed to get absolute path of output directory: %v" , err )
239+ }
240+
241+ if err := os .MkdirAll (dir , 0755 ); err != nil {
242+ fatalf ("failed to create output directory: %v" , err )
243+ }
244+ return dir
245+ }
246+
247+ // prepareInputDir normalizes --input-dir.
248+ func prepareInputDir (inDir string ) string {
249+ if inDir == "" {
250+ inDir = "."
251+ }
252+ if flagN {
253+ // -n used for testing. don't create the directory nor try to resolve.
254+ return inDir
255+ }
256+
257+ // resolve to absolute path so input dir can be consitent
258+ // even when child processes accessing it need to run in a different directory.
259+ dir , err := filepath .Abs (inDir )
260+ if err != nil {
261+ fatalf ("failed to get absolute path of output directory: %v" , err )
262+ }
263+ return dir
264+ }
265+
79266func requireTools (tools ... string ) {
80267 for _ , tool := range tools {
81268 if _ , err := exec .LookPath (tool ); err != nil {
@@ -84,12 +271,12 @@ func requireTools(tools ...string) {
84271 }
85272}
86273
87- func requireEnvVars (vars ... string ) {
88- for _ , v := range vars {
89- if os .Getenv (v ) == "" {
90- fatalf ("required environment variable %q not set" , v )
91- }
274+ func requireEnv (name string ) string {
275+ v := os .Getenv (name )
276+ if v == "" {
277+ fatalf ("required environment variable %q not set" , v )
92278 }
279+ return v
93280}
94281
95282// checkWD checks if the working directory is the extension directory where package.json is located.
@@ -106,30 +293,37 @@ func checkWD() {
106293
107294// releaseVersionInfo computes the version and label information for this release.
108295// It requires the TAG_NAME environment variable to be set and the tag matches the version info embedded in package.json.
109- func releaseVersionInfo () (tagName , version string , isPrerelease bool ) {
110- tagName = os .Getenv ("TAG_NAME" )
111- if tagName == "" {
112- fatalf ("TAG_NAME environment variable is not set" )
113- }
296+ func releaseVersionInfo (tagName string ) (version string , isPrerelease bool ) {
114297 // versionTag should be of the form vMajor.Minor.Patch[-rc.N].
115298 // e.g. v1.1.0-rc.1, v1.1.0
116299 // The MajorMinorPatch part should match the version in package.json.
117300 // The optional `-rc.N` part is captured as the `Label` group
118301 // and the validity is checked below.
119- versionTagRE := regexp .MustCompile (`^v(?P<MajorMinorPatch>\d+\.\d+\.\d+)(?P<Label>\S*)$` )
120- m := versionTagRE .FindStringSubmatch (tagName )
121- if m == nil {
122- fatalf ("TAG_NAME environment variable %q is not a valid version" , tagName )
123- }
124- mmp := m [versionTagRE .SubexpIndex ("MajorMinorPatch" )]
125- label := m [versionTagRE .SubexpIndex ("Label" )]
302+ mmp , label := parseVersionTagName (tagName )
126303 if label != "" {
127304 if ! strings .HasPrefix (label , "-rc." ) {
128305 fatalf ("TAG_NAME environment variable %q is not a valid release candidate version" , tagName )
129306 }
130307 isPrerelease = true
131308 }
309+ return mmp + label , isPrerelease
310+ }
311+
312+ func parseVersionTagName (tagName string ) (majorMinorPatch , label string ) {
313+ versionTagRE := regexp .MustCompile (`^v(?P<MajorMinorPatch>\d+\.\d+\.\d+)(?P<Label>\S*)$` )
314+ m := versionTagRE .FindStringSubmatch (tagName )
315+ if m == nil {
316+ fatalf ("TAG_NAME environment variable %q is not a valid version" , tagName )
317+ }
318+ return m [versionTagRE .SubexpIndex ("MajorMinorPatch" )], m [versionTagRE .SubexpIndex ("Label" )]
319+ }
132320
321+ func checkPackageJSON (tagName string ) {
322+ if flagN {
323+ tracef ("jq -r .version package.json" )
324+ return
325+ }
326+ mmp , _ := parseVersionTagName (tagName )
133327 cmd := exec .Command ("jq" , "-r" , ".version" , "package.json" )
134328 cmd .Stderr = os .Stderr
135329 var buf bytes.Buffer
@@ -138,19 +332,15 @@ func releaseVersionInfo() (tagName, version string, isPrerelease bool) {
138332 fatalf ("failed to read package.json version" )
139333 }
140334 versionInPackageJSON := buf .Bytes ()
141- if * flagN {
142- return tagName , mmp + label , isPrerelease
143- }
144335 if got := string (bytes .TrimSpace (versionInPackageJSON )); got != mmp {
145336 fatalf ("package.json version %q does not match TAG_NAME %q" , got , tagName )
146337 }
147- return tagName , mmp + label , isPrerelease
148338}
149339
150340func commandRun (cmd * exec.Cmd ) error {
151- if * flagN {
341+ if flagN {
152342 if cmd .Dir != "" {
153- fmt . Fprintf ( os . Stderr , "cd %v\n " , cmd .Dir )
343+ tracef ( "cd %v" , cmd .Dir )
154344 }
155345 fmt .Fprintf (os .Stderr , "%v\n " , strings .Join (cmd .Args , " " ))
156346 return nil
@@ -159,8 +349,8 @@ func commandRun(cmd *exec.Cmd) error {
159349}
160350
161351func copy (dst , src string ) error {
162- if * flagN {
163- fmt . Fprintf ( os . Stderr , "cp %s %s\n " , src , dst )
352+ if flagN {
353+ tracef ( "cp %s %s" , src , dst )
164354 return nil
165355 }
166356 data , err := os .ReadFile (src )
@@ -193,8 +383,8 @@ func buildPackage(version, tagName, output string) {
193383// publish publishes the extension to the VS Code Marketplace and GitHub, using npx vsce and gh release create.
194384func publish (tagName , packageFile string , isPrerelease bool ) {
195385 // check if the package file exists.
196- if * flagN {
197- fmt . Fprintf ( os . Stderr , "stat %s\n " , packageFile )
386+ if flagN {
387+ tracef ( "stat %s" , packageFile )
198388 } else {
199389 if _ , err := os .Stat (packageFile ); os .IsNotExist (err ) {
200390 fatalf ("package file %q does not exist. Did you run 'go run build/release.go package'?" , packageFile )
@@ -242,3 +432,8 @@ func commitSHA() string {
242432 }
243433 return strings .TrimSpace (string (commitSHA ))
244434}
435+
436+ func tracef (format string , args ... any ) {
437+ str := fmt .Sprintf (format , args ... )
438+ fmt .Fprintln (os .Stderr , str )
439+ }
0 commit comments