github.com/evanw/esbuild@v0.21.4/pkg/cli/cli_impl.go (about)

     1  package cli
     2  
     3  // This file implements the public CLI. It's deliberately implemented using
     4  // esbuild's public "Build", "Transform", and "AnalyzeMetafile" APIs instead of
     5  // using internal APIs so that any tests that cover the CLI also implicitly
     6  // cover the public API as well.
     7  
     8  import (
     9  	"fmt"
    10  	"io/ioutil"
    11  	"net"
    12  	"os"
    13  	"sort"
    14  	"strconv"
    15  	"strings"
    16  
    17  	"github.com/evanw/esbuild/internal/cli_helpers"
    18  	"github.com/evanw/esbuild/internal/fs"
    19  	"github.com/evanw/esbuild/internal/logger"
    20  	"github.com/evanw/esbuild/pkg/api"
    21  )
    22  
    23  func newBuildOptions() api.BuildOptions {
    24  	return api.BuildOptions{
    25  		Banner:      make(map[string]string),
    26  		Define:      make(map[string]string),
    27  		Footer:      make(map[string]string),
    28  		Loader:      make(map[string]api.Loader),
    29  		LogOverride: make(map[string]api.LogLevel),
    30  		Supported:   make(map[string]bool),
    31  	}
    32  }
    33  
    34  func newTransformOptions() api.TransformOptions {
    35  	return api.TransformOptions{
    36  		Define:      make(map[string]string),
    37  		LogOverride: make(map[string]api.LogLevel),
    38  		Supported:   make(map[string]bool),
    39  	}
    40  }
    41  
    42  type parseOptionsKind uint8
    43  
    44  const (
    45  	// This means we're parsing it for our own internal use
    46  	kindInternal parseOptionsKind = iota
    47  
    48  	// This means the result is returned through a public API
    49  	kindExternal
    50  )
    51  
    52  type parseOptionsExtras struct {
    53  	watch       bool
    54  	metafile    *string
    55  	mangleCache *string
    56  }
    57  
    58  func isBoolFlag(arg string, flag string) bool {
    59  	if strings.HasPrefix(arg, flag) {
    60  		remainder := arg[len(flag):]
    61  		return len(remainder) == 0 || remainder[0] == '='
    62  	}
    63  	return false
    64  }
    65  
    66  func parseBoolFlag(arg string, defaultValue bool) (bool, *cli_helpers.ErrorWithNote) {
    67  	equals := strings.IndexByte(arg, '=')
    68  	if equals == -1 {
    69  		return defaultValue, nil
    70  	}
    71  	value := arg[equals+1:]
    72  	switch value {
    73  	case "false":
    74  		return false, nil
    75  	case "true":
    76  		return true, nil
    77  	}
    78  	return false, cli_helpers.MakeErrorWithNote(
    79  		fmt.Sprintf("Invalid value %q in %q", value, arg),
    80  		"Valid values are \"true\" or \"false\".",
    81  	)
    82  }
    83  
    84  func parseOptionsImpl(
    85  	osArgs []string,
    86  	buildOpts *api.BuildOptions,
    87  	transformOpts *api.TransformOptions,
    88  	kind parseOptionsKind,
    89  ) (extras parseOptionsExtras, err *cli_helpers.ErrorWithNote) {
    90  	hasBareSourceMapFlag := false
    91  
    92  	// Parse the arguments now that we know what we're parsing
    93  	for _, arg := range osArgs {
    94  		switch {
    95  		case isBoolFlag(arg, "--bundle") && buildOpts != nil:
    96  			if value, err := parseBoolFlag(arg, true); err != nil {
    97  				return parseOptionsExtras{}, err
    98  			} else {
    99  				buildOpts.Bundle = value
   100  			}
   101  
   102  		case isBoolFlag(arg, "--preserve-symlinks") && buildOpts != nil:
   103  			if value, err := parseBoolFlag(arg, true); err != nil {
   104  				return parseOptionsExtras{}, err
   105  			} else {
   106  				buildOpts.PreserveSymlinks = value
   107  			}
   108  
   109  		case isBoolFlag(arg, "--splitting") && buildOpts != nil:
   110  			if value, err := parseBoolFlag(arg, true); err != nil {
   111  				return parseOptionsExtras{}, err
   112  			} else {
   113  				buildOpts.Splitting = value
   114  			}
   115  
   116  		case isBoolFlag(arg, "--allow-overwrite") && buildOpts != nil:
   117  			if value, err := parseBoolFlag(arg, true); err != nil {
   118  				return parseOptionsExtras{}, err
   119  			} else {
   120  				buildOpts.AllowOverwrite = value
   121  			}
   122  
   123  		case isBoolFlag(arg, "--watch") && buildOpts != nil:
   124  			if value, err := parseBoolFlag(arg, true); err != nil {
   125  				return parseOptionsExtras{}, err
   126  			} else {
   127  				extras.watch = value
   128  			}
   129  
   130  		case isBoolFlag(arg, "--minify"):
   131  			if value, err := parseBoolFlag(arg, true); err != nil {
   132  				return parseOptionsExtras{}, err
   133  			} else if buildOpts != nil {
   134  				buildOpts.MinifySyntax = value
   135  				buildOpts.MinifyWhitespace = value
   136  				buildOpts.MinifyIdentifiers = value
   137  			} else {
   138  				transformOpts.MinifySyntax = value
   139  				transformOpts.MinifyWhitespace = value
   140  				transformOpts.MinifyIdentifiers = value
   141  			}
   142  
   143  		case isBoolFlag(arg, "--minify-syntax"):
   144  			if value, err := parseBoolFlag(arg, true); err != nil {
   145  				return parseOptionsExtras{}, err
   146  			} else if buildOpts != nil {
   147  				buildOpts.MinifySyntax = value
   148  			} else {
   149  				transformOpts.MinifySyntax = value
   150  			}
   151  
   152  		case isBoolFlag(arg, "--minify-whitespace"):
   153  			if value, err := parseBoolFlag(arg, true); err != nil {
   154  				return parseOptionsExtras{}, err
   155  			} else if buildOpts != nil {
   156  				buildOpts.MinifyWhitespace = value
   157  			} else {
   158  				transformOpts.MinifyWhitespace = value
   159  			}
   160  
   161  		case isBoolFlag(arg, "--minify-identifiers"):
   162  			if value, err := parseBoolFlag(arg, true); err != nil {
   163  				return parseOptionsExtras{}, err
   164  			} else if buildOpts != nil {
   165  				buildOpts.MinifyIdentifiers = value
   166  			} else {
   167  				transformOpts.MinifyIdentifiers = value
   168  			}
   169  
   170  		case isBoolFlag(arg, "--mangle-quoted"):
   171  			if value, err := parseBoolFlag(arg, true); err != nil {
   172  				return parseOptionsExtras{}, err
   173  			} else {
   174  				var mangleQuoted *api.MangleQuoted
   175  				if buildOpts != nil {
   176  					mangleQuoted = &buildOpts.MangleQuoted
   177  				} else {
   178  					mangleQuoted = &transformOpts.MangleQuoted
   179  				}
   180  				if value {
   181  					*mangleQuoted = api.MangleQuotedTrue
   182  				} else {
   183  					*mangleQuoted = api.MangleQuotedFalse
   184  				}
   185  			}
   186  
   187  		case strings.HasPrefix(arg, "--mangle-props="):
   188  			value := arg[len("--mangle-props="):]
   189  			if buildOpts != nil {
   190  				buildOpts.MangleProps = value
   191  			} else {
   192  				transformOpts.MangleProps = value
   193  			}
   194  
   195  		case strings.HasPrefix(arg, "--reserve-props="):
   196  			value := arg[len("--reserve-props="):]
   197  			if buildOpts != nil {
   198  				buildOpts.ReserveProps = value
   199  			} else {
   200  				transformOpts.ReserveProps = value
   201  			}
   202  
   203  		case strings.HasPrefix(arg, "--mangle-cache=") && buildOpts != nil && kind == kindInternal:
   204  			value := arg[len("--mangle-cache="):]
   205  			extras.mangleCache = &value
   206  
   207  		case strings.HasPrefix(arg, "--drop:"):
   208  			value := arg[len("--drop:"):]
   209  			switch value {
   210  			case "console":
   211  				if buildOpts != nil {
   212  					buildOpts.Drop |= api.DropConsole
   213  				} else {
   214  					transformOpts.Drop |= api.DropConsole
   215  				}
   216  			case "debugger":
   217  				if buildOpts != nil {
   218  					buildOpts.Drop |= api.DropDebugger
   219  				} else {
   220  					transformOpts.Drop |= api.DropDebugger
   221  				}
   222  			default:
   223  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   224  					fmt.Sprintf("Invalid value %q in %q", value, arg),
   225  					"Valid values are \"console\" or \"debugger\".",
   226  				)
   227  			}
   228  
   229  		case strings.HasPrefix(arg, "--drop-labels="):
   230  			if buildOpts != nil {
   231  				buildOpts.DropLabels = splitWithEmptyCheck(arg[len("--drop-labels="):], ",")
   232  			} else {
   233  				transformOpts.DropLabels = splitWithEmptyCheck(arg[len("--drop-labels="):], ",")
   234  			}
   235  
   236  		case strings.HasPrefix(arg, "--legal-comments="):
   237  			value := arg[len("--legal-comments="):]
   238  			var legalComments api.LegalComments
   239  			switch value {
   240  			case "none":
   241  				legalComments = api.LegalCommentsNone
   242  			case "inline":
   243  				legalComments = api.LegalCommentsInline
   244  			case "eof":
   245  				legalComments = api.LegalCommentsEndOfFile
   246  			case "linked":
   247  				legalComments = api.LegalCommentsLinked
   248  			case "external":
   249  				legalComments = api.LegalCommentsExternal
   250  			default:
   251  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   252  					fmt.Sprintf("Invalid value %q in %q", value, arg),
   253  					"Valid values are \"none\", \"inline\", \"eof\", \"linked\", or \"external\".",
   254  				)
   255  			}
   256  			if buildOpts != nil {
   257  				buildOpts.LegalComments = legalComments
   258  			} else {
   259  				transformOpts.LegalComments = legalComments
   260  			}
   261  
   262  		case strings.HasPrefix(arg, "--charset="):
   263  			var value *api.Charset
   264  			if buildOpts != nil {
   265  				value = &buildOpts.Charset
   266  			} else {
   267  				value = &transformOpts.Charset
   268  			}
   269  			name := arg[len("--charset="):]
   270  			switch name {
   271  			case "ascii":
   272  				*value = api.CharsetASCII
   273  			case "utf8":
   274  				*value = api.CharsetUTF8
   275  			default:
   276  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   277  					fmt.Sprintf("Invalid value %q in %q", name, arg),
   278  					"Valid values are \"ascii\" or \"utf8\".",
   279  				)
   280  			}
   281  
   282  		case isBoolFlag(arg, "--tree-shaking"):
   283  			if value, err := parseBoolFlag(arg, true); err != nil {
   284  				return parseOptionsExtras{}, err
   285  			} else {
   286  				var treeShaking *api.TreeShaking
   287  				if buildOpts != nil {
   288  					treeShaking = &buildOpts.TreeShaking
   289  				} else {
   290  					treeShaking = &transformOpts.TreeShaking
   291  				}
   292  				if value {
   293  					*treeShaking = api.TreeShakingTrue
   294  				} else {
   295  					*treeShaking = api.TreeShakingFalse
   296  				}
   297  			}
   298  
   299  		case isBoolFlag(arg, "--ignore-annotations"):
   300  			if value, err := parseBoolFlag(arg, true); err != nil {
   301  				return parseOptionsExtras{}, err
   302  			} else if buildOpts != nil {
   303  				buildOpts.IgnoreAnnotations = value
   304  			} else {
   305  				transformOpts.IgnoreAnnotations = value
   306  			}
   307  
   308  		case isBoolFlag(arg, "--keep-names"):
   309  			if value, err := parseBoolFlag(arg, true); err != nil {
   310  				return parseOptionsExtras{}, err
   311  			} else if buildOpts != nil {
   312  				buildOpts.KeepNames = value
   313  			} else {
   314  				transformOpts.KeepNames = value
   315  			}
   316  
   317  		case arg == "--sourcemap":
   318  			if buildOpts != nil {
   319  				buildOpts.Sourcemap = api.SourceMapLinked
   320  			} else {
   321  				transformOpts.Sourcemap = api.SourceMapInline
   322  			}
   323  			hasBareSourceMapFlag = true
   324  
   325  		case strings.HasPrefix(arg, "--sourcemap="):
   326  			value := arg[len("--sourcemap="):]
   327  			var sourcemap api.SourceMap
   328  			switch value {
   329  			case "linked":
   330  				sourcemap = api.SourceMapLinked
   331  			case "inline":
   332  				sourcemap = api.SourceMapInline
   333  			case "external":
   334  				sourcemap = api.SourceMapExternal
   335  			case "both":
   336  				sourcemap = api.SourceMapInlineAndExternal
   337  			default:
   338  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   339  					fmt.Sprintf("Invalid value %q in %q", value, arg),
   340  					"Valid values are \"linked\", \"inline\", \"external\", or \"both\".",
   341  				)
   342  			}
   343  			if buildOpts != nil {
   344  				buildOpts.Sourcemap = sourcemap
   345  			} else {
   346  				transformOpts.Sourcemap = sourcemap
   347  			}
   348  			hasBareSourceMapFlag = false
   349  
   350  		case strings.HasPrefix(arg, "--source-root="):
   351  			sourceRoot := arg[len("--source-root="):]
   352  			if buildOpts != nil {
   353  				buildOpts.SourceRoot = sourceRoot
   354  			} else {
   355  				transformOpts.SourceRoot = sourceRoot
   356  			}
   357  
   358  		case isBoolFlag(arg, "--sources-content"):
   359  			if value, err := parseBoolFlag(arg, true); err != nil {
   360  				return parseOptionsExtras{}, err
   361  			} else {
   362  				var sourcesContent *api.SourcesContent
   363  				if buildOpts != nil {
   364  					sourcesContent = &buildOpts.SourcesContent
   365  				} else {
   366  					sourcesContent = &transformOpts.SourcesContent
   367  				}
   368  				if value {
   369  					*sourcesContent = api.SourcesContentInclude
   370  				} else {
   371  					*sourcesContent = api.SourcesContentExclude
   372  				}
   373  			}
   374  
   375  		case strings.HasPrefix(arg, "--sourcefile="):
   376  			if buildOpts != nil {
   377  				if buildOpts.Stdin == nil {
   378  					buildOpts.Stdin = &api.StdinOptions{}
   379  				}
   380  				buildOpts.Stdin.Sourcefile = arg[len("--sourcefile="):]
   381  			} else {
   382  				transformOpts.Sourcefile = arg[len("--sourcefile="):]
   383  			}
   384  
   385  		case strings.HasPrefix(arg, "--resolve-extensions=") && buildOpts != nil:
   386  			buildOpts.ResolveExtensions = splitWithEmptyCheck(arg[len("--resolve-extensions="):], ",")
   387  
   388  		case strings.HasPrefix(arg, "--main-fields=") && buildOpts != nil:
   389  			buildOpts.MainFields = splitWithEmptyCheck(arg[len("--main-fields="):], ",")
   390  
   391  		case strings.HasPrefix(arg, "--conditions=") && buildOpts != nil:
   392  			buildOpts.Conditions = splitWithEmptyCheck(arg[len("--conditions="):], ",")
   393  
   394  		case strings.HasPrefix(arg, "--public-path=") && buildOpts != nil:
   395  			buildOpts.PublicPath = arg[len("--public-path="):]
   396  
   397  		case strings.HasPrefix(arg, "--global-name="):
   398  			if buildOpts != nil {
   399  				buildOpts.GlobalName = arg[len("--global-name="):]
   400  			} else {
   401  				transformOpts.GlobalName = arg[len("--global-name="):]
   402  			}
   403  
   404  		case arg == "--metafile" && buildOpts != nil && kind == kindExternal:
   405  			buildOpts.Metafile = true
   406  
   407  		case strings.HasPrefix(arg, "--metafile=") && buildOpts != nil && kind == kindInternal:
   408  			value := arg[len("--metafile="):]
   409  			buildOpts.Metafile = true
   410  			extras.metafile = &value
   411  
   412  		case strings.HasPrefix(arg, "--outfile=") && buildOpts != nil:
   413  			buildOpts.Outfile = arg[len("--outfile="):]
   414  
   415  		case strings.HasPrefix(arg, "--outdir=") && buildOpts != nil:
   416  			buildOpts.Outdir = arg[len("--outdir="):]
   417  
   418  		case strings.HasPrefix(arg, "--outbase=") && buildOpts != nil:
   419  			buildOpts.Outbase = arg[len("--outbase="):]
   420  
   421  		case strings.HasPrefix(arg, "--tsconfig=") && buildOpts != nil:
   422  			buildOpts.Tsconfig = arg[len("--tsconfig="):]
   423  
   424  		case strings.HasPrefix(arg, "--tsconfig-raw="):
   425  			if buildOpts != nil {
   426  				buildOpts.TsconfigRaw = arg[len("--tsconfig-raw="):]
   427  			} else {
   428  				transformOpts.TsconfigRaw = arg[len("--tsconfig-raw="):]
   429  			}
   430  
   431  		case strings.HasPrefix(arg, "--entry-names=") && buildOpts != nil:
   432  			buildOpts.EntryNames = arg[len("--entry-names="):]
   433  
   434  		case strings.HasPrefix(arg, "--chunk-names=") && buildOpts != nil:
   435  			buildOpts.ChunkNames = arg[len("--chunk-names="):]
   436  
   437  		case strings.HasPrefix(arg, "--asset-names=") && buildOpts != nil:
   438  			buildOpts.AssetNames = arg[len("--asset-names="):]
   439  
   440  		case strings.HasPrefix(arg, "--define:"):
   441  			value := arg[len("--define:"):]
   442  			equals := strings.IndexByte(value, '=')
   443  			if equals == -1 {
   444  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   445  					fmt.Sprintf("Missing \"=\" in %q", arg),
   446  					"You need to use \"=\" to specify both the original value and the replacement value. "+
   447  						"For example, \"--define:DEBUG=true\" replaces \"DEBUG\" with \"true\".",
   448  				)
   449  			}
   450  			if buildOpts != nil {
   451  				buildOpts.Define[value[:equals]] = value[equals+1:]
   452  			} else {
   453  				transformOpts.Define[value[:equals]] = value[equals+1:]
   454  			}
   455  
   456  		case strings.HasPrefix(arg, "--log-override:"):
   457  			value := arg[len("--log-override:"):]
   458  			equals := strings.IndexByte(value, '=')
   459  			if equals == -1 {
   460  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   461  					fmt.Sprintf("Missing \"=\" in %q", arg),
   462  					"You need to use \"=\" to specify both the message name and the log level. "+
   463  						"For example, \"--log-override:css-syntax-error=error\" turns all \"css-syntax-error\" log messages into errors.",
   464  				)
   465  			}
   466  			logLevel, err := parseLogLevel(value[equals+1:], arg)
   467  			if err != nil {
   468  				return parseOptionsExtras{}, err
   469  			}
   470  			if buildOpts != nil {
   471  				buildOpts.LogOverride[value[:equals]] = logLevel
   472  			} else {
   473  				transformOpts.LogOverride[value[:equals]] = logLevel
   474  			}
   475  
   476  		case strings.HasPrefix(arg, "--supported:"):
   477  			value := arg[len("--supported:"):]
   478  			equals := strings.IndexByte(value, '=')
   479  			if equals == -1 {
   480  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   481  					fmt.Sprintf("Missing \"=\" in %q", arg),
   482  					"You need to use \"=\" to specify both the name of the feature and whether it is supported or not. "+
   483  						"For example, \"--supported:arrow=false\" marks arrow functions as unsupported.",
   484  				)
   485  			}
   486  			if isSupported, err := parseBoolFlag(arg, true); err != nil {
   487  				return parseOptionsExtras{}, err
   488  			} else if buildOpts != nil {
   489  				buildOpts.Supported[value[:equals]] = isSupported
   490  			} else {
   491  				transformOpts.Supported[value[:equals]] = isSupported
   492  			}
   493  
   494  		case strings.HasPrefix(arg, "--pure:"):
   495  			value := arg[len("--pure:"):]
   496  			if buildOpts != nil {
   497  				buildOpts.Pure = append(buildOpts.Pure, value)
   498  			} else {
   499  				transformOpts.Pure = append(transformOpts.Pure, value)
   500  			}
   501  
   502  		case strings.HasPrefix(arg, "--loader:") && buildOpts != nil:
   503  			value := arg[len("--loader:"):]
   504  			equals := strings.IndexByte(value, '=')
   505  			if equals == -1 {
   506  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   507  					fmt.Sprintf("Missing \"=\" in %q", arg),
   508  					"You need to specify the file extension that the loader applies to. "+
   509  						"For example, \"--loader:.js=jsx\" applies the \"jsx\" loader to files with the \".js\" extension.",
   510  				)
   511  			}
   512  			ext, text := value[:equals], value[equals+1:]
   513  			loader, err := cli_helpers.ParseLoader(text)
   514  			if err != nil {
   515  				return parseOptionsExtras{}, err
   516  			}
   517  			buildOpts.Loader[ext] = loader
   518  
   519  		case strings.HasPrefix(arg, "--loader="):
   520  			value := arg[len("--loader="):]
   521  			loader, err := cli_helpers.ParseLoader(value)
   522  			if err != nil {
   523  				return parseOptionsExtras{}, err
   524  			}
   525  			if loader == api.LoaderFile || loader == api.LoaderCopy {
   526  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   527  					fmt.Sprintf("%q is not supported when transforming stdin", arg),
   528  					fmt.Sprintf("Using esbuild to transform stdin only generates one output file, so you cannot use the %q loader "+
   529  						"since that needs to generate two output files.", value),
   530  				)
   531  			}
   532  			if buildOpts != nil {
   533  				if buildOpts.Stdin == nil {
   534  					buildOpts.Stdin = &api.StdinOptions{}
   535  				}
   536  				buildOpts.Stdin.Loader = loader
   537  			} else {
   538  				transformOpts.Loader = loader
   539  			}
   540  
   541  		case strings.HasPrefix(arg, "--target="):
   542  			target, engines, err := parseTargets(splitWithEmptyCheck(arg[len("--target="):], ","), arg)
   543  			if err != nil {
   544  				return parseOptionsExtras{}, err
   545  			}
   546  			if buildOpts != nil {
   547  				buildOpts.Target = target
   548  				buildOpts.Engines = engines
   549  			} else {
   550  				transformOpts.Target = target
   551  				transformOpts.Engines = engines
   552  			}
   553  
   554  		case strings.HasPrefix(arg, "--out-extension:") && buildOpts != nil:
   555  			value := arg[len("--out-extension:"):]
   556  			equals := strings.IndexByte(value, '=')
   557  			if equals == -1 {
   558  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   559  					fmt.Sprintf("Missing \"=\" in %q", arg),
   560  					"You need to use either \"--out-extension:.js=...\" or \"--out-extension:.css=...\" "+
   561  						"to specify the file type that the output extension applies to .",
   562  				)
   563  			}
   564  			if buildOpts.OutExtension == nil {
   565  				buildOpts.OutExtension = make(map[string]string)
   566  			}
   567  			buildOpts.OutExtension[value[:equals]] = value[equals+1:]
   568  
   569  		case strings.HasPrefix(arg, "--platform="):
   570  			value := arg[len("--platform="):]
   571  			var platform api.Platform
   572  			switch value {
   573  			case "browser":
   574  				platform = api.PlatformBrowser
   575  			case "node":
   576  				platform = api.PlatformNode
   577  			case "neutral":
   578  				platform = api.PlatformNeutral
   579  			default:
   580  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   581  					fmt.Sprintf("Invalid value %q in %q", value, arg),
   582  					"Valid values are \"browser\", \"node\", or \"neutral\".",
   583  				)
   584  			}
   585  			if buildOpts != nil {
   586  				buildOpts.Platform = platform
   587  			} else {
   588  				transformOpts.Platform = platform
   589  			}
   590  
   591  		case strings.HasPrefix(arg, "--format="):
   592  			value := arg[len("--format="):]
   593  			var format api.Format
   594  			switch value {
   595  			case "iife":
   596  				format = api.FormatIIFE
   597  			case "cjs":
   598  				format = api.FormatCommonJS
   599  			case "esm":
   600  				format = api.FormatESModule
   601  			default:
   602  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   603  					fmt.Sprintf("Invalid value %q in %q", value, arg),
   604  					"Valid values are \"iife\", \"cjs\", or \"esm\".",
   605  				)
   606  			}
   607  			if buildOpts != nil {
   608  				buildOpts.Format = format
   609  			} else {
   610  				transformOpts.Format = format
   611  			}
   612  
   613  		case strings.HasPrefix(arg, "--packages=") && buildOpts != nil:
   614  			value := arg[len("--packages="):]
   615  			var packages api.Packages
   616  			if value == "external" {
   617  				packages = api.PackagesExternal
   618  			} else {
   619  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   620  					fmt.Sprintf("Invalid value %q in %q", value, arg),
   621  					"The only valid value is \"external\".",
   622  				)
   623  			}
   624  			buildOpts.Packages = packages
   625  
   626  		case strings.HasPrefix(arg, "--external:") && buildOpts != nil:
   627  			buildOpts.External = append(buildOpts.External, arg[len("--external:"):])
   628  
   629  		case strings.HasPrefix(arg, "--inject:") && buildOpts != nil:
   630  			buildOpts.Inject = append(buildOpts.Inject, arg[len("--inject:"):])
   631  
   632  		case strings.HasPrefix(arg, "--alias:") && buildOpts != nil:
   633  			value := arg[len("--alias:"):]
   634  			equals := strings.IndexByte(value, '=')
   635  			if equals == -1 {
   636  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   637  					fmt.Sprintf("Missing \"=\" in %q", arg),
   638  					"You need to use \"=\" to specify both the original package name and the replacement package name. "+
   639  						"For example, \"--alias:old=new\" replaces package \"old\" with package \"new\".",
   640  				)
   641  			}
   642  			if buildOpts.Alias == nil {
   643  				buildOpts.Alias = make(map[string]string)
   644  			}
   645  			buildOpts.Alias[value[:equals]] = value[equals+1:]
   646  
   647  		case strings.HasPrefix(arg, "--jsx="):
   648  			value := arg[len("--jsx="):]
   649  			var mode api.JSX
   650  			switch value {
   651  			case "transform":
   652  				mode = api.JSXTransform
   653  			case "preserve":
   654  				mode = api.JSXPreserve
   655  			case "automatic":
   656  				mode = api.JSXAutomatic
   657  			default:
   658  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   659  					fmt.Sprintf("Invalid value %q in %q", value, arg),
   660  					"Valid values are \"transform\", \"automatic\", or \"preserve\".",
   661  				)
   662  			}
   663  			if buildOpts != nil {
   664  				buildOpts.JSX = mode
   665  			} else {
   666  				transformOpts.JSX = mode
   667  			}
   668  
   669  		case strings.HasPrefix(arg, "--jsx-factory="):
   670  			value := arg[len("--jsx-factory="):]
   671  			if buildOpts != nil {
   672  				buildOpts.JSXFactory = value
   673  			} else {
   674  				transformOpts.JSXFactory = value
   675  			}
   676  
   677  		case strings.HasPrefix(arg, "--jsx-fragment="):
   678  			value := arg[len("--jsx-fragment="):]
   679  			if buildOpts != nil {
   680  				buildOpts.JSXFragment = value
   681  			} else {
   682  				transformOpts.JSXFragment = value
   683  			}
   684  
   685  		case strings.HasPrefix(arg, "--jsx-import-source="):
   686  			value := arg[len("--jsx-import-source="):]
   687  			if buildOpts != nil {
   688  				buildOpts.JSXImportSource = value
   689  			} else {
   690  				transformOpts.JSXImportSource = value
   691  			}
   692  
   693  		case isBoolFlag(arg, "--jsx-dev"):
   694  			if value, err := parseBoolFlag(arg, true); err != nil {
   695  				return parseOptionsExtras{}, err
   696  			} else if buildOpts != nil {
   697  				buildOpts.JSXDev = value
   698  			} else {
   699  				transformOpts.JSXDev = value
   700  			}
   701  
   702  		case isBoolFlag(arg, "--jsx-side-effects"):
   703  			if value, err := parseBoolFlag(arg, true); err != nil {
   704  				return parseOptionsExtras{}, err
   705  			} else if buildOpts != nil {
   706  				buildOpts.JSXSideEffects = value
   707  			} else {
   708  				transformOpts.JSXSideEffects = value
   709  			}
   710  
   711  		case strings.HasPrefix(arg, "--banner=") && transformOpts != nil:
   712  			transformOpts.Banner = arg[len("--banner="):]
   713  
   714  		case strings.HasPrefix(arg, "--footer=") && transformOpts != nil:
   715  			transformOpts.Footer = arg[len("--footer="):]
   716  
   717  		case strings.HasPrefix(arg, "--banner:") && buildOpts != nil:
   718  			value := arg[len("--banner:"):]
   719  			equals := strings.IndexByte(value, '=')
   720  			if equals == -1 {
   721  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   722  					fmt.Sprintf("Missing \"=\" in %q", arg),
   723  					"You need to use either \"--banner:js=...\" or \"--banner:css=...\" to specify the language that the banner applies to.",
   724  				)
   725  			}
   726  			buildOpts.Banner[value[:equals]] = value[equals+1:]
   727  
   728  		case strings.HasPrefix(arg, "--footer:") && buildOpts != nil:
   729  			value := arg[len("--footer:"):]
   730  			equals := strings.IndexByte(value, '=')
   731  			if equals == -1 {
   732  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   733  					fmt.Sprintf("Missing \"=\" in %q", arg),
   734  					"You need to use either \"--footer:js=...\" or \"--footer:css=...\" to specify the language that the footer applies to.",
   735  				)
   736  			}
   737  			buildOpts.Footer[value[:equals]] = value[equals+1:]
   738  
   739  		case strings.HasPrefix(arg, "--log-limit="):
   740  			value := arg[len("--log-limit="):]
   741  			limit, err := strconv.Atoi(value)
   742  			if err != nil || limit < 0 {
   743  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   744  					fmt.Sprintf("Invalid value %q in %q", value, arg),
   745  					"The log limit must be a non-negative integer.",
   746  				)
   747  			}
   748  			if buildOpts != nil {
   749  				buildOpts.LogLimit = limit
   750  			} else {
   751  				transformOpts.LogLimit = limit
   752  			}
   753  
   754  		case strings.HasPrefix(arg, "--line-limit="):
   755  			value := arg[len("--line-limit="):]
   756  			limit, err := strconv.Atoi(value)
   757  			if err != nil || limit < 0 {
   758  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   759  					fmt.Sprintf("Invalid value %q in %q", value, arg),
   760  					"The line limit must be a non-negative integer.",
   761  				)
   762  			}
   763  			if buildOpts != nil {
   764  				buildOpts.LineLimit = limit
   765  			} else {
   766  				transformOpts.LineLimit = limit
   767  			}
   768  
   769  			// Make sure this stays in sync with "PrintErrorToStderr"
   770  		case isBoolFlag(arg, "--color"):
   771  			if value, err := parseBoolFlag(arg, true); err != nil {
   772  				return parseOptionsExtras{}, err
   773  			} else {
   774  				var color *api.StderrColor
   775  				if buildOpts != nil {
   776  					color = &buildOpts.Color
   777  				} else {
   778  					color = &transformOpts.Color
   779  				}
   780  				if value {
   781  					*color = api.ColorAlways
   782  				} else {
   783  					*color = api.ColorNever
   784  				}
   785  			}
   786  
   787  		// Make sure this stays in sync with "PrintErrorToStderr"
   788  		case strings.HasPrefix(arg, "--log-level="):
   789  			value := arg[len("--log-level="):]
   790  			logLevel, err := parseLogLevel(value, arg)
   791  			if err != nil {
   792  				return parseOptionsExtras{}, err
   793  			}
   794  			if buildOpts != nil {
   795  				buildOpts.LogLevel = logLevel
   796  			} else {
   797  				transformOpts.LogLevel = logLevel
   798  			}
   799  
   800  		case strings.HasPrefix(arg, "'--"):
   801  			return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
   802  				fmt.Sprintf("Unexpected single quote character before flag: %s", arg),
   803  				"This typically happens when attempting to use single quotes to quote arguments with a shell that doesn't recognize single quotes. "+
   804  					"Try using double quote characters to quote arguments instead.",
   805  			)
   806  
   807  		case !strings.HasPrefix(arg, "-") && buildOpts != nil:
   808  			if equals := strings.IndexByte(arg, '='); equals != -1 {
   809  				buildOpts.EntryPointsAdvanced = append(buildOpts.EntryPointsAdvanced, api.EntryPoint{
   810  					OutputPath: arg[:equals],
   811  					InputPath:  arg[equals+1:],
   812  				})
   813  			} else {
   814  				buildOpts.EntryPoints = append(buildOpts.EntryPoints, arg)
   815  			}
   816  
   817  		default:
   818  			bare := map[string]bool{
   819  				"allow-overwrite":    true,
   820  				"bundle":             true,
   821  				"ignore-annotations": true,
   822  				"jsx-dev":            true,
   823  				"jsx-side-effects":   true,
   824  				"keep-names":         true,
   825  				"minify-identifiers": true,
   826  				"minify-syntax":      true,
   827  				"minify-whitespace":  true,
   828  				"minify":             true,
   829  				"preserve-symlinks":  true,
   830  				"sourcemap":          true,
   831  				"splitting":          true,
   832  				"watch":              true,
   833  			}
   834  
   835  			equals := map[string]bool{
   836  				"allow-overwrite":    true,
   837  				"asset-names":        true,
   838  				"banner":             true,
   839  				"bundle":             true,
   840  				"certfile":           true,
   841  				"charset":            true,
   842  				"chunk-names":        true,
   843  				"color":              true,
   844  				"conditions":         true,
   845  				"drop-labels":        true,
   846  				"entry-names":        true,
   847  				"footer":             true,
   848  				"format":             true,
   849  				"global-name":        true,
   850  				"ignore-annotations": true,
   851  				"jsx-factory":        true,
   852  				"jsx-fragment":       true,
   853  				"jsx-import-source":  true,
   854  				"jsx":                true,
   855  				"keep-names":         true,
   856  				"keyfile":            true,
   857  				"legal-comments":     true,
   858  				"loader":             true,
   859  				"log-level":          true,
   860  				"log-limit":          true,
   861  				"main-fields":        true,
   862  				"mangle-cache":       true,
   863  				"mangle-props":       true,
   864  				"mangle-quoted":      true,
   865  				"metafile":           true,
   866  				"minify-identifiers": true,
   867  				"minify-syntax":      true,
   868  				"minify-whitespace":  true,
   869  				"minify":             true,
   870  				"outbase":            true,
   871  				"outdir":             true,
   872  				"outfile":            true,
   873  				"packages":           true,
   874  				"platform":           true,
   875  				"preserve-symlinks":  true,
   876  				"public-path":        true,
   877  				"reserve-props":      true,
   878  				"resolve-extensions": true,
   879  				"serve-fallback":     true,
   880  				"serve":              true,
   881  				"servedir":           true,
   882  				"source-root":        true,
   883  				"sourcefile":         true,
   884  				"sourcemap":          true,
   885  				"sources-content":    true,
   886  				"splitting":          true,
   887  				"target":             true,
   888  				"tree-shaking":       true,
   889  				"tsconfig-raw":       true,
   890  				"tsconfig":           true,
   891  				"watch":              true,
   892  			}
   893  
   894  			colon := map[string]bool{
   895  				"alias":         true,
   896  				"banner":        true,
   897  				"define":        true,
   898  				"drop":          true,
   899  				"external":      true,
   900  				"footer":        true,
   901  				"inject":        true,
   902  				"loader":        true,
   903  				"log-override":  true,
   904  				"out-extension": true,
   905  				"pure":          true,
   906  				"supported":     true,
   907  			}
   908  
   909  			note := ""
   910  
   911  			// Try to provide helpful hints when we can recognize the mistake
   912  			switch {
   913  			case arg == "-o":
   914  				note = "Use \"--outfile=\" to configure the output file instead of \"-o\"."
   915  
   916  			case arg == "-v":
   917  				note = "Use \"--log-level=verbose\" to generate verbose logs instead of \"-v\"."
   918  
   919  			case strings.HasPrefix(arg, "--"):
   920  				if i := strings.IndexByte(arg, '='); i != -1 && colon[arg[2:i]] {
   921  					note = fmt.Sprintf("Use %q instead of %q. Flags that can be re-specified multiple times use \":\" instead of \"=\".",
   922  						arg[:i]+":"+arg[i+1:], arg)
   923  				}
   924  
   925  				if i := strings.IndexByte(arg, ':'); i != -1 && equals[arg[2:i]] {
   926  					note = fmt.Sprintf("Use %q instead of %q. Flags that can only be specified once use \"=\" instead of \":\".",
   927  						arg[:i]+"="+arg[i+1:], arg)
   928  				}
   929  
   930  			case strings.HasPrefix(arg, "-"):
   931  				isValid := bare[arg[1:]]
   932  				fix := "-" + arg
   933  
   934  				if i := strings.IndexByte(arg, '='); i != -1 && equals[arg[1:i]] {
   935  					isValid = true
   936  				} else if i != -1 && colon[arg[1:i]] {
   937  					isValid = true
   938  					fix = fmt.Sprintf("-%s:%s", arg[:i], arg[i+1:])
   939  				} else if i := strings.IndexByte(arg, ':'); i != -1 && colon[arg[1:i]] {
   940  					isValid = true
   941  				} else if i != -1 && equals[arg[1:i]] {
   942  					isValid = true
   943  					fix = fmt.Sprintf("-%s=%s", arg[:i], arg[i+1:])
   944  				}
   945  
   946  				if isValid {
   947  					note = fmt.Sprintf("Use %q instead of %q. Flags are always specified with two dashes instead of one dash.",
   948  						fix, arg)
   949  				}
   950  			}
   951  
   952  			if buildOpts != nil {
   953  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(fmt.Sprintf("Invalid build flag: %q", arg), note)
   954  			} else {
   955  				return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(fmt.Sprintf("Invalid transform flag: %q", arg), note)
   956  			}
   957  		}
   958  	}
   959  
   960  	// If we're building, the last source map flag is "--sourcemap", and there
   961  	// is no output path, change the source map option to "inline" because we're
   962  	// going to be writing to stdout which can only represent a single file.
   963  	if buildOpts != nil && hasBareSourceMapFlag && buildOpts.Outfile == "" && buildOpts.Outdir == "" {
   964  		buildOpts.Sourcemap = api.SourceMapInline
   965  	}
   966  
   967  	return
   968  }
   969  
   970  func parseTargets(targets []string, arg string) (target api.Target, engines []api.Engine, err *cli_helpers.ErrorWithNote) {
   971  	validTargets := map[string]api.Target{
   972  		"esnext": api.ESNext,
   973  		"es5":    api.ES5,
   974  		"es6":    api.ES2015,
   975  		"es2015": api.ES2015,
   976  		"es2016": api.ES2016,
   977  		"es2017": api.ES2017,
   978  		"es2018": api.ES2018,
   979  		"es2019": api.ES2019,
   980  		"es2020": api.ES2020,
   981  		"es2021": api.ES2021,
   982  		"es2022": api.ES2022,
   983  		"es2023": api.ES2023,
   984  	}
   985  
   986  outer:
   987  	for _, value := range targets {
   988  		if valid, ok := validTargets[strings.ToLower(value)]; ok {
   989  			target = valid
   990  			continue
   991  		}
   992  
   993  		for engine, name := range validEngines {
   994  			if strings.HasPrefix(value, engine) {
   995  				version := value[len(engine):]
   996  				if version == "" {
   997  					return 0, nil, cli_helpers.MakeErrorWithNote(
   998  						fmt.Sprintf("Target %q is missing a version number in %q", value, arg),
   999  						"",
  1000  					)
  1001  				}
  1002  				engines = append(engines, api.Engine{Name: name, Version: version})
  1003  				continue outer
  1004  			}
  1005  		}
  1006  
  1007  		engines := make([]string, 0, len(validEngines))
  1008  		engines = append(engines, "\"esN\"")
  1009  		for key := range validEngines {
  1010  			engines = append(engines, fmt.Sprintf("%q", key+"N"))
  1011  		}
  1012  		sort.Strings(engines)
  1013  		return 0, nil, cli_helpers.MakeErrorWithNote(
  1014  			fmt.Sprintf("Invalid target %q in %q", value, arg),
  1015  			fmt.Sprintf("Valid values are %s, or %s where N is a version number.",
  1016  				strings.Join(engines[:len(engines)-1], ", "), engines[len(engines)-1]),
  1017  		)
  1018  	}
  1019  	return
  1020  }
  1021  
  1022  // This returns either BuildOptions, TransformOptions, or an error
  1023  func parseOptionsForRun(osArgs []string) (*api.BuildOptions, *api.TransformOptions, parseOptionsExtras, *cli_helpers.ErrorWithNote) {
  1024  	// If there's an entry point or we're bundling, then we're building
  1025  	for _, arg := range osArgs {
  1026  		if !strings.HasPrefix(arg, "-") || arg == "--bundle" {
  1027  			options := newBuildOptions()
  1028  
  1029  			// Apply defaults appropriate for the CLI
  1030  			options.LogLimit = 6
  1031  			options.LogLevel = api.LogLevelInfo
  1032  			options.Write = true
  1033  
  1034  			extras, err := parseOptionsImpl(osArgs, &options, nil, kindInternal)
  1035  			if err != nil {
  1036  				return nil, nil, parseOptionsExtras{}, err
  1037  			}
  1038  			return &options, nil, extras, nil
  1039  		}
  1040  	}
  1041  
  1042  	// Otherwise, we're transforming
  1043  	options := newTransformOptions()
  1044  
  1045  	// Apply defaults appropriate for the CLI
  1046  	options.LogLimit = 6
  1047  	options.LogLevel = api.LogLevelInfo
  1048  
  1049  	_, err := parseOptionsImpl(osArgs, nil, &options, kindInternal)
  1050  	if err != nil {
  1051  		return nil, nil, parseOptionsExtras{}, err
  1052  	}
  1053  	if options.Sourcemap != api.SourceMapNone && options.Sourcemap != api.SourceMapInline {
  1054  		var sourceMapMode string
  1055  		switch options.Sourcemap {
  1056  		case api.SourceMapExternal:
  1057  			sourceMapMode = "external"
  1058  		case api.SourceMapInlineAndExternal:
  1059  			sourceMapMode = "both"
  1060  		case api.SourceMapLinked:
  1061  			sourceMapMode = "linked"
  1062  		}
  1063  		return nil, nil, parseOptionsExtras{}, cli_helpers.MakeErrorWithNote(
  1064  			fmt.Sprintf("Use \"--sourcemap\" instead of \"--sourcemap=%s\" when transforming stdin", sourceMapMode),
  1065  			fmt.Sprintf("Using esbuild to transform stdin only generates one output file. You cannot use the %q source map mode "+
  1066  				"since that needs to generate two output files.", sourceMapMode),
  1067  		)
  1068  	}
  1069  	return nil, &options, parseOptionsExtras{}, nil
  1070  }
  1071  
  1072  func splitWithEmptyCheck(s string, sep string) []string {
  1073  	// Special-case the empty string to return [] instead of [""]
  1074  	if s == "" {
  1075  		return []string{}
  1076  	}
  1077  
  1078  	return strings.Split(s, sep)
  1079  }
  1080  
  1081  type analyzeMode uint8
  1082  
  1083  const (
  1084  	analyzeDisabled analyzeMode = iota
  1085  	analyzeEnabled
  1086  	analyzeVerbose
  1087  )
  1088  
  1089  func filterAnalyzeFlags(osArgs []string) ([]string, analyzeMode) {
  1090  	analyze := analyzeDisabled
  1091  	end := 0
  1092  	for _, arg := range osArgs {
  1093  		switch arg {
  1094  		case "--analyze":
  1095  			analyze = analyzeEnabled
  1096  		case "--analyze=verbose":
  1097  			analyze = analyzeVerbose
  1098  		default:
  1099  			osArgs[end] = arg
  1100  			end++
  1101  		}
  1102  	}
  1103  	return osArgs[:end], analyze
  1104  }
  1105  
  1106  // Print metafile analysis after the build if it's enabled
  1107  func addAnalyzePlugin(buildOptions *api.BuildOptions, analyze analyzeMode, osArgs []string) {
  1108  	buildOptions.Plugins = append(buildOptions.Plugins, api.Plugin{
  1109  		Name: "PrintAnalysis",
  1110  		Setup: func(build api.PluginBuild) {
  1111  			color := logger.OutputOptionsForArgs(osArgs).Color
  1112  			build.OnEnd(func(result *api.BuildResult) (api.OnEndResult, error) {
  1113  				if result.Metafile != "" {
  1114  					logger.PrintTextWithColor(os.Stderr, color, func(colors logger.Colors) string {
  1115  						return api.AnalyzeMetafile(result.Metafile, api.AnalyzeMetafileOptions{
  1116  							Color:   colors != logger.Colors{},
  1117  							Verbose: analyze == analyzeVerbose,
  1118  						})
  1119  					})
  1120  					os.Stderr.WriteString("\n")
  1121  				}
  1122  				return api.OnEndResult{}, nil
  1123  			})
  1124  		},
  1125  	})
  1126  
  1127  	// Always generate a metafile if we're analyzing, even if it won't be written out
  1128  	buildOptions.Metafile = true
  1129  }
  1130  
  1131  func runImpl(osArgs []string) int {
  1132  	// Special-case running a server
  1133  	for _, arg := range osArgs {
  1134  		if arg == "--serve" ||
  1135  			strings.HasPrefix(arg, "--serve=") ||
  1136  			strings.HasPrefix(arg, "--servedir=") ||
  1137  			strings.HasPrefix(arg, "--serve-fallback=") {
  1138  			serveImpl(osArgs)
  1139  			return 1 // There was an error starting the server if we get here
  1140  		}
  1141  	}
  1142  
  1143  	osArgs, analyze := filterAnalyzeFlags(osArgs)
  1144  	buildOptions, transformOptions, extras, err := parseOptionsForRun(osArgs)
  1145  	if analyze != analyzeDisabled {
  1146  		addAnalyzePlugin(buildOptions, analyze, osArgs)
  1147  	}
  1148  
  1149  	switch {
  1150  	case buildOptions != nil:
  1151  		// Read the "NODE_PATH" from the environment. This is part of node's
  1152  		// module resolution algorithm. Documentation for this can be found here:
  1153  		// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
  1154  		if value, ok := os.LookupEnv("NODE_PATH"); ok {
  1155  			separator := ":"
  1156  			if fs.CheckIfWindows() {
  1157  				// On Windows, NODE_PATH is delimited by semicolons instead of colons
  1158  				separator = ";"
  1159  			}
  1160  			buildOptions.NodePaths = splitWithEmptyCheck(value, separator)
  1161  		}
  1162  
  1163  		// Read from stdin when there are no entry points
  1164  		if len(buildOptions.EntryPoints)+len(buildOptions.EntryPointsAdvanced) == 0 {
  1165  			if buildOptions.Stdin == nil {
  1166  				buildOptions.Stdin = &api.StdinOptions{}
  1167  			}
  1168  			bytes, err := ioutil.ReadAll(os.Stdin)
  1169  			if err != nil {
  1170  				logger.PrintErrorToStderr(osArgs, fmt.Sprintf(
  1171  					"Could not read from stdin: %s", err.Error()))
  1172  				return 1
  1173  			}
  1174  			buildOptions.Stdin.Contents = string(bytes)
  1175  			buildOptions.Stdin.ResolveDir, _ = os.Getwd()
  1176  		} else if buildOptions.Stdin != nil {
  1177  			if buildOptions.Stdin.Sourcefile != "" {
  1178  				logger.PrintErrorToStderr(osArgs,
  1179  					"\"sourcefile\" only applies when reading from stdin")
  1180  			} else {
  1181  				logger.PrintErrorToStderr(osArgs,
  1182  					"\"loader\" without extension only applies when reading from stdin")
  1183  			}
  1184  			return 1
  1185  		}
  1186  
  1187  		// Validate the metafile absolute path and directory ahead of time so we
  1188  		// don't write any output files if it's incorrect. That makes this API
  1189  		// option consistent with how we handle all other API options.
  1190  		var writeMetafile func(string)
  1191  		if extras.metafile != nil {
  1192  			var metafileAbsPath string
  1193  			var metafileAbsDir string
  1194  
  1195  			if buildOptions.Outfile == "" && buildOptions.Outdir == "" {
  1196  				// Cannot use "metafile" when writing to stdout
  1197  				logger.PrintErrorToStderr(osArgs, "Cannot use \"metafile\" without an output path")
  1198  				return 1
  1199  			}
  1200  			realFS, realFSErr := fs.RealFS(fs.RealFSOptions{AbsWorkingDir: buildOptions.AbsWorkingDir})
  1201  			if realFSErr == nil {
  1202  				absPath, ok := realFS.Abs(*extras.metafile)
  1203  				if !ok {
  1204  					logger.PrintErrorToStderr(osArgs, fmt.Sprintf("Invalid metafile path: %s", *extras.metafile))
  1205  					return 1
  1206  				}
  1207  				metafileAbsPath = absPath
  1208  				metafileAbsDir = realFS.Dir(absPath)
  1209  			} else {
  1210  				// Don't fail in this case since the error will be reported by "api.Build"
  1211  			}
  1212  
  1213  			writeMetafile = func(json string) {
  1214  				if json == "" || realFSErr != nil {
  1215  					return // Don't write out the metafile on build errors
  1216  				}
  1217  				if err != nil {
  1218  					// This should already have been checked above
  1219  					panic(err.Text)
  1220  				}
  1221  				fs.BeforeFileOpen()
  1222  				defer fs.AfterFileClose()
  1223  				if err := fs.MkdirAll(realFS, metafileAbsDir, 0755); err != nil {
  1224  					logger.PrintErrorToStderr(osArgs, fmt.Sprintf(
  1225  						"Failed to create output directory: %s", err.Error()))
  1226  				} else {
  1227  					if err := ioutil.WriteFile(metafileAbsPath, []byte(json), 0666); err != nil {
  1228  						logger.PrintErrorToStderr(osArgs, fmt.Sprintf(
  1229  							"Failed to write to output file: %s", err.Error()))
  1230  					}
  1231  				}
  1232  			}
  1233  		}
  1234  
  1235  		// Also validate the mangle cache absolute path and directory ahead of time
  1236  		// for the same reason
  1237  		var writeMangleCache func(map[string]interface{})
  1238  		if extras.mangleCache != nil {
  1239  			var mangleCacheAbsPath string
  1240  			var mangleCacheAbsDir string
  1241  			var mangleCacheOrder []string
  1242  			realFS, realFSErr := fs.RealFS(fs.RealFSOptions{AbsWorkingDir: buildOptions.AbsWorkingDir})
  1243  			if realFSErr == nil {
  1244  				absPath, ok := realFS.Abs(*extras.mangleCache)
  1245  				if !ok {
  1246  					logger.PrintErrorToStderr(osArgs, fmt.Sprintf("Invalid mangle cache path: %s", *extras.mangleCache))
  1247  					return 1
  1248  				}
  1249  				mangleCacheAbsPath = absPath
  1250  				mangleCacheAbsDir = realFS.Dir(absPath)
  1251  				buildOptions.MangleCache, mangleCacheOrder = parseMangleCache(osArgs, realFS, *extras.mangleCache)
  1252  				if buildOptions.MangleCache == nil {
  1253  					return 1 // Stop now if parsing failed
  1254  				}
  1255  			} else {
  1256  				// Don't fail in this case since the error will be reported by "api.Build"
  1257  			}
  1258  
  1259  			writeMangleCache = func(mangleCache map[string]interface{}) {
  1260  				if mangleCache == nil || realFSErr != nil {
  1261  					return // Don't write out the metafile on build errors
  1262  				}
  1263  				if err != nil {
  1264  					// This should already have been checked above
  1265  					panic(err.Text)
  1266  				}
  1267  				fs.BeforeFileOpen()
  1268  				defer fs.AfterFileClose()
  1269  				if err := fs.MkdirAll(realFS, mangleCacheAbsDir, 0755); err != nil {
  1270  					logger.PrintErrorToStderr(osArgs, fmt.Sprintf(
  1271  						"Failed to create output directory: %s", err.Error()))
  1272  				} else {
  1273  					bytes := printMangleCache(mangleCache, mangleCacheOrder, buildOptions.Charset == api.CharsetASCII)
  1274  					if err := ioutil.WriteFile(mangleCacheAbsPath, bytes, 0666); err != nil {
  1275  						logger.PrintErrorToStderr(osArgs, fmt.Sprintf(
  1276  							"Failed to write to output file: %s", err.Error()))
  1277  					}
  1278  				}
  1279  			}
  1280  		}
  1281  
  1282  		// Handle post-build actions with a plugin so they also work in watch mode
  1283  		buildOptions.Plugins = append(buildOptions.Plugins, api.Plugin{
  1284  			Name: "PostBuildActions",
  1285  			Setup: func(build api.PluginBuild) {
  1286  				build.OnEnd(func(result *api.BuildResult) (api.OnEndResult, error) {
  1287  					// Write the metafile to the file system
  1288  					if writeMetafile != nil {
  1289  						writeMetafile(result.Metafile)
  1290  					}
  1291  
  1292  					// Write the mangle cache to the file system
  1293  					if writeMangleCache != nil {
  1294  						writeMangleCache(result.MangleCache)
  1295  					}
  1296  
  1297  					return api.OnEndResult{}, nil
  1298  				})
  1299  			},
  1300  		})
  1301  
  1302  		// Handle watch mode
  1303  		if extras.watch {
  1304  			ctx, err := api.Context(*buildOptions)
  1305  
  1306  			// Only start watching if the build options passed validation
  1307  			if err != nil {
  1308  				return 1
  1309  			}
  1310  
  1311  			ctx.Watch(api.WatchOptions{})
  1312  
  1313  			// Do not exit if we're in watch mode
  1314  			<-make(chan struct{})
  1315  		}
  1316  
  1317  		// This prints the summary which the context API doesn't do
  1318  		result := api.Build(*buildOptions)
  1319  
  1320  		// Return a non-zero exit code if there were errors
  1321  		if len(result.Errors) > 0 {
  1322  			return 1
  1323  		}
  1324  
  1325  	case transformOptions != nil:
  1326  		// Read the input from stdin
  1327  		bytes, err := ioutil.ReadAll(os.Stdin)
  1328  		if err != nil {
  1329  			logger.PrintErrorToStderr(osArgs, fmt.Sprintf(
  1330  				"Could not read from stdin: %s", err.Error()))
  1331  			return 1
  1332  		}
  1333  
  1334  		// Run the transform and stop if there were errors
  1335  		result := api.Transform(string(bytes), *transformOptions)
  1336  		if len(result.Errors) > 0 {
  1337  			return 1
  1338  		}
  1339  
  1340  		// Write the output to stdout
  1341  		os.Stdout.Write(result.Code)
  1342  
  1343  	case err != nil:
  1344  		logger.PrintErrorWithNoteToStderr(osArgs, err.Text, err.Note)
  1345  		return 1
  1346  	}
  1347  
  1348  	return 0
  1349  }
  1350  
  1351  func parseServeOptionsImpl(osArgs []string) (api.ServeOptions, []string, error) {
  1352  	host := ""
  1353  	portText := "0"
  1354  	servedir := ""
  1355  	keyfile := ""
  1356  	certfile := ""
  1357  	fallback := ""
  1358  
  1359  	// Filter out server-specific flags
  1360  	filteredArgs := make([]string, 0, len(osArgs))
  1361  	for _, arg := range osArgs {
  1362  		if arg == "--serve" {
  1363  			// Just ignore this flag
  1364  		} else if strings.HasPrefix(arg, "--serve=") {
  1365  			portText = arg[len("--serve="):]
  1366  		} else if strings.HasPrefix(arg, "--servedir=") {
  1367  			servedir = arg[len("--servedir="):]
  1368  		} else if strings.HasPrefix(arg, "--keyfile=") {
  1369  			keyfile = arg[len("--keyfile="):]
  1370  		} else if strings.HasPrefix(arg, "--certfile=") {
  1371  			certfile = arg[len("--certfile="):]
  1372  		} else if strings.HasPrefix(arg, "--serve-fallback=") {
  1373  			fallback = arg[len("--serve-fallback="):]
  1374  		} else {
  1375  			filteredArgs = append(filteredArgs, arg)
  1376  		}
  1377  	}
  1378  
  1379  	// Specifying the host is optional
  1380  	if strings.ContainsRune(portText, ':') {
  1381  		var err error
  1382  		host, portText, err = net.SplitHostPort(portText)
  1383  		if err != nil {
  1384  			return api.ServeOptions{}, nil, err
  1385  		}
  1386  	}
  1387  
  1388  	// Parse the port
  1389  	port, err := strconv.ParseInt(portText, 10, 32)
  1390  	if err != nil {
  1391  		return api.ServeOptions{}, nil, err
  1392  	}
  1393  	if port < 0 || port > 0xFFFF {
  1394  		return api.ServeOptions{}, nil, fmt.Errorf("Invalid port number: %s", portText)
  1395  	}
  1396  
  1397  	return api.ServeOptions{
  1398  		Port:     uint16(port),
  1399  		Host:     host,
  1400  		Servedir: servedir,
  1401  		Keyfile:  keyfile,
  1402  		Certfile: certfile,
  1403  		Fallback: fallback,
  1404  	}, filteredArgs, nil
  1405  }
  1406  
  1407  func serveImpl(osArgs []string) {
  1408  	serveOptions, filteredArgs, err := parseServeOptionsImpl(osArgs)
  1409  	if err != nil {
  1410  		logger.PrintErrorWithNoteToStderr(osArgs, err.Error(), "")
  1411  		return
  1412  	}
  1413  
  1414  	options := newBuildOptions()
  1415  
  1416  	// Apply defaults appropriate for the CLI
  1417  	options.LogLimit = 5
  1418  	options.LogLevel = api.LogLevelInfo
  1419  
  1420  	filteredArgs, analyze := filterAnalyzeFlags(filteredArgs)
  1421  	extras, errWithNote := parseOptionsImpl(filteredArgs, &options, nil, kindInternal)
  1422  	if errWithNote != nil {
  1423  		logger.PrintErrorWithNoteToStderr(osArgs, errWithNote.Text, errWithNote.Note)
  1424  		return
  1425  	}
  1426  	if analyze != analyzeDisabled {
  1427  		addAnalyzePlugin(&options, analyze, osArgs)
  1428  	}
  1429  
  1430  	serveOptions.OnRequest = func(args api.ServeOnRequestArgs) {
  1431  		logger.PrintText(os.Stderr, logger.LevelInfo, filteredArgs, func(colors logger.Colors) string {
  1432  			statusColor := colors.Red
  1433  			if args.Status >= 200 && args.Status <= 299 {
  1434  				statusColor = colors.Green
  1435  			} else if args.Status >= 300 && args.Status <= 399 {
  1436  				statusColor = colors.Yellow
  1437  			}
  1438  			return fmt.Sprintf("%s%s - %q %s%d%s [%dms]%s\n",
  1439  				colors.Dim, args.RemoteAddress, args.Method+" "+args.Path,
  1440  				statusColor, args.Status, colors.Dim, args.TimeInMS, colors.Reset)
  1441  		})
  1442  	}
  1443  
  1444  	// Validate build options
  1445  	ctx, ctxErr := api.Context(options)
  1446  	if ctxErr != nil {
  1447  		return
  1448  	}
  1449  
  1450  	// Try to enable serve mode
  1451  	if _, err = ctx.Serve(serveOptions); err != nil {
  1452  		logger.PrintErrorWithNoteToStderr(osArgs, err.Error(), "")
  1453  		return
  1454  	}
  1455  
  1456  	// Also enable watch mode if it was requested
  1457  	if extras.watch {
  1458  		if err := ctx.Watch(api.WatchOptions{}); err != nil {
  1459  			logger.PrintErrorWithNoteToStderr(osArgs, err.Error(), "")
  1460  			return
  1461  		}
  1462  	}
  1463  
  1464  	// Do not exit if we're in serve mode
  1465  	<-make(chan struct{})
  1466  }
  1467  
  1468  func parseLogLevel(value string, arg string) (api.LogLevel, *cli_helpers.ErrorWithNote) {
  1469  	switch value {
  1470  	case "verbose":
  1471  		return api.LogLevelVerbose, nil
  1472  	case "debug":
  1473  		return api.LogLevelDebug, nil
  1474  	case "info":
  1475  		return api.LogLevelInfo, nil
  1476  	case "warning":
  1477  		return api.LogLevelWarning, nil
  1478  	case "error":
  1479  		return api.LogLevelError, nil
  1480  	case "silent":
  1481  		return api.LogLevelSilent, nil
  1482  	default:
  1483  		return api.LogLevelSilent, cli_helpers.MakeErrorWithNote(
  1484  			fmt.Sprintf("Invalid value %q in %q", value, arg),
  1485  			"Valid values are \"verbose\", \"debug\", \"info\", \"warning\", \"error\", or \"silent\".",
  1486  		)
  1487  	}
  1488  }