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 }