Add support for copying files and folders.

This commit is contained in:
2021-12-19 14:31:57 +01:00
parent 161cb79b88
commit 311339685c
450 changed files with 232338 additions and 3 deletions

566
vendor/github.com/evanw/esbuild/pkg/api/api.go generated vendored Normal file
View File

@ -0,0 +1,566 @@
// This API exposes esbuild's two main operations: building and transforming.
// It's intended for integrating esbuild into other tools as a library.
//
// If you are just trying to run esbuild from Go without the overhead of
// creating a child process, there is also an API for the command-line
// interface itself: https://godoc.org/github.com/evanw/esbuild/pkg/cli.
//
// Build API
//
// This function runs an end-to-end build operation. It takes an array of file
// paths as entry points, parses them and all of their dependencies, and
// returns the output files to write to the file system. The available options
// roughly correspond to esbuild's command-line flags.
//
// Example usage:
//
// package main
//
// import (
// "os"
//
// "github.com/evanw/esbuild/pkg/api"
// )
//
// func main() {
// result := api.Build(api.BuildOptions{
// EntryPoints: []string{"input.js"},
// Outfile: "output.js",
// Bundle: true,
// Write: true,
// LogLevel: api.LogLevelInfo,
// })
//
// if len(result.Errors) > 0 {
// os.Exit(1)
// }
// }
//
// Transform API
//
// This function transforms a string of source code into JavaScript. It can be
// used to minify JavaScript, convert TypeScript/JSX to JavaScript, or convert
// newer JavaScript to older JavaScript. The available options roughly
// correspond to esbuild's command-line flags.
//
// Example usage:
//
// package main
//
// import (
// "fmt"
// "os"
//
// "github.com/evanw/esbuild/pkg/api"
// )
//
// func main() {
// jsx := `
// import * as React from 'react'
// import * as ReactDOM from 'react-dom'
//
// ReactDOM.render(
// <h1>Hello, world!</h1>,
// document.getElementById('root')
// );
// `
//
// result := api.Transform(jsx, api.TransformOptions{
// Loader: api.LoaderJSX,
// })
//
// fmt.Printf("%d errors and %d warnings\n",
// len(result.Errors), len(result.Warnings))
//
// os.Stdout.Write(result.Code)
// }
//
package api
type SourceMap uint8
const (
SourceMapNone SourceMap = iota
SourceMapInline
SourceMapLinked
SourceMapExternal
SourceMapInlineAndExternal
)
type SourcesContent uint8
const (
SourcesContentInclude SourcesContent = iota
SourcesContentExclude
)
type LegalComments uint8
const (
LegalCommentsDefault LegalComments = iota
LegalCommentsNone
LegalCommentsInline
LegalCommentsEndOfFile
LegalCommentsLinked
LegalCommentsExternal
)
type JSXMode uint8
const (
JSXModeTransform JSXMode = iota
JSXModePreserve
)
type Target uint8
const (
DefaultTarget Target = iota
ESNext
ES5
ES2015
ES2016
ES2017
ES2018
ES2019
ES2020
ES2021
)
type Loader uint8
const (
LoaderNone Loader = iota
LoaderJS
LoaderJSX
LoaderTS
LoaderTSX
LoaderJSON
LoaderText
LoaderBase64
LoaderDataURL
LoaderFile
LoaderBinary
LoaderCSS
LoaderDefault
)
type Platform uint8
const (
PlatformBrowser Platform = iota
PlatformNode
PlatformNeutral
)
type Format uint8
const (
FormatDefault Format = iota
FormatIIFE
FormatCommonJS
FormatESModule
)
type EngineName uint8
const (
EngineChrome EngineName = iota
EngineEdge
EngineFirefox
EngineIOS
EngineNode
EngineSafari
)
type Engine struct {
Name EngineName
Version string
}
type Location struct {
File string
Namespace string
Line int // 1-based
Column int // 0-based, in bytes
Length int // in bytes
LineText string
Suggestion string
}
type Message struct {
PluginName string
Text string
Location *Location
Notes []Note
// Optional user-specified data that is passed through unmodified. You can
// use this to stash the original error, for example.
Detail interface{}
}
type Note struct {
Text string
Location *Location
}
type StderrColor uint8
const (
ColorIfTerminal StderrColor = iota
ColorNever
ColorAlways
)
type LogLevel uint8
const (
LogLevelSilent LogLevel = iota
LogLevelVerbose
LogLevelDebug
LogLevelInfo
LogLevelWarning
LogLevelError
)
type Charset uint8
const (
CharsetDefault Charset = iota
CharsetASCII
CharsetUTF8
)
type TreeShaking uint8
const (
TreeShakingDefault TreeShaking = iota
TreeShakingFalse
TreeShakingTrue
)
////////////////////////////////////////////////////////////////////////////////
// Build API
type BuildOptions struct {
Color StderrColor // Documentation: https://esbuild.github.io/api/#color
LogLimit int // Documentation: https://esbuild.github.io/api/#log-limit
LogLevel LogLevel // Documentation: https://esbuild.github.io/api/#log-level
Sourcemap SourceMap // Documentation: https://esbuild.github.io/api/#sourcemap
SourceRoot string // Documentation: https://esbuild.github.io/api/#source-root
SourcesContent SourcesContent // Documentation: https://esbuild.github.io/api/#sources-content
Target Target // Documentation: https://esbuild.github.io/api/#target
Engines []Engine // Documentation: https://esbuild.github.io/api/#target
MinifyWhitespace bool // Documentation: https://esbuild.github.io/api/#minify
MinifyIdentifiers bool // Documentation: https://esbuild.github.io/api/#minify
MinifySyntax bool // Documentation: https://esbuild.github.io/api/#minify
Charset Charset // Documentation: https://esbuild.github.io/api/#charset
TreeShaking TreeShaking // Documentation: https://esbuild.github.io/api/#tree-shaking
IgnoreAnnotations bool // Documentation: https://esbuild.github.io/api/#ignore-annotations
LegalComments LegalComments // Documentation: https://esbuild.github.io/api/#legal-comments
JSXMode JSXMode // Documentation: https://esbuild.github.io/api/#jsx-mode
JSXFactory string // Documentation: https://esbuild.github.io/api/#jsx-factory
JSXFragment string // Documentation: https://esbuild.github.io/api/#jsx-fragment
Define map[string]string // Documentation: https://esbuild.github.io/api/#define
Pure []string // Documentation: https://esbuild.github.io/api/#pure
KeepNames bool // Documentation: https://esbuild.github.io/api/#keep-names
GlobalName string // Documentation: https://esbuild.github.io/api/#global-name
Bundle bool // Documentation: https://esbuild.github.io/api/#bundle
PreserveSymlinks bool // Documentation: https://esbuild.github.io/api/#preserve-symlinks
Splitting bool // Documentation: https://esbuild.github.io/api/#splitting
Outfile string // Documentation: https://esbuild.github.io/api/#outfile
Metafile bool // Documentation: https://esbuild.github.io/api/#metafile
Outdir string // Documentation: https://esbuild.github.io/api/#outdir
Outbase string // Documentation: https://esbuild.github.io/api/#outbase
AbsWorkingDir string // Documentation: https://esbuild.github.io/api/#working-directory
Platform Platform // Documentation: https://esbuild.github.io/api/#platform
Format Format // Documentation: https://esbuild.github.io/api/#format
External []string // Documentation: https://esbuild.github.io/api/#external
MainFields []string // Documentation: https://esbuild.github.io/api/#main-fields
Conditions []string // Documentation: https://esbuild.github.io/api/#conditions
Loader map[string]Loader // Documentation: https://esbuild.github.io/api/#loader
ResolveExtensions []string // Documentation: https://esbuild.github.io/api/#resolve-extensions
Tsconfig string // Documentation: https://esbuild.github.io/api/#tsconfig
OutExtensions map[string]string // Documentation: https://esbuild.github.io/api/#out-extension
PublicPath string // Documentation: https://esbuild.github.io/api/#public-path
Inject []string // Documentation: https://esbuild.github.io/api/#inject
Banner map[string]string // Documentation: https://esbuild.github.io/api/#banner
Footer map[string]string // Documentation: https://esbuild.github.io/api/#footer
NodePaths []string // Documentation: https://esbuild.github.io/api/#node-paths
EntryNames string // Documentation: https://esbuild.github.io/api/#entry-names
ChunkNames string // Documentation: https://esbuild.github.io/api/#chunk-names
AssetNames string // Documentation: https://esbuild.github.io/api/#asset-names
EntryPoints []string // Documentation: https://esbuild.github.io/api/#entry-points
EntryPointsAdvanced []EntryPoint // Documentation: https://esbuild.github.io/api/#entry-points
Stdin *StdinOptions // Documentation: https://esbuild.github.io/api/#stdin
Write bool // Documentation: https://esbuild.github.io/api/#write
AllowOverwrite bool // Documentation: https://esbuild.github.io/api/#allow-overwrite
Incremental bool // Documentation: https://esbuild.github.io/api/#incremental
Plugins []Plugin // Documentation: https://esbuild.github.io/plugins/
Watch *WatchMode // Documentation: https://esbuild.github.io/api/#watch
}
type EntryPoint struct {
InputPath string
OutputPath string
}
type WatchMode struct {
OnRebuild func(BuildResult)
}
type StdinOptions struct {
Contents string
ResolveDir string
Sourcefile string
Loader Loader
}
type BuildResult struct {
Errors []Message
Warnings []Message
OutputFiles []OutputFile
Metafile string
Rebuild func() BuildResult // Only when "Incremental: true"
Stop func() // Only when "Watch: true"
}
type OutputFile struct {
Path string
Contents []byte
}
// Documentation: https://esbuild.github.io/api/#build-api
func Build(options BuildOptions) BuildResult {
return buildImpl(options).result
}
////////////////////////////////////////////////////////////////////////////////
// Transform API
type TransformOptions struct {
Color StderrColor // Documentation: https://esbuild.github.io/api/#color
LogLimit int // Documentation: https://esbuild.github.io/api/#log-limit
LogLevel LogLevel // Documentation: https://esbuild.github.io/api/#log-level
Sourcemap SourceMap // Documentation: https://esbuild.github.io/api/#sourcemap
SourceRoot string // Documentation: https://esbuild.github.io/api/#source-root
SourcesContent SourcesContent // Documentation: https://esbuild.github.io/api/#sources-content
Target Target // Documentation: https://esbuild.github.io/api/#target
Engines []Engine // Documentation: https://esbuild.github.io/api/#target
Format Format // Documentation: https://esbuild.github.io/api/#format
GlobalName string // Documentation: https://esbuild.github.io/api/#global-name
MinifyWhitespace bool // Documentation: https://esbuild.github.io/api/#minify
MinifyIdentifiers bool // Documentation: https://esbuild.github.io/api/#minify
MinifySyntax bool // Documentation: https://esbuild.github.io/api/#minify
Charset Charset // Documentation: https://esbuild.github.io/api/#charset
TreeShaking TreeShaking // Documentation: https://esbuild.github.io/api/#tree-shaking
IgnoreAnnotations bool // Documentation: https://esbuild.github.io/api/#ignore-annotations
LegalComments LegalComments // Documentation: https://esbuild.github.io/api/#legal-comments
JSXMode JSXMode // Documentation: https://esbuild.github.io/api/#jsx
JSXFactory string // Documentation: https://esbuild.github.io/api/#jsx-factory
JSXFragment string // Documentation: https://esbuild.github.io/api/#jsx-fragment
TsconfigRaw string // Documentation: https://esbuild.github.io/api/#tsconfig-raw
Banner string // Documentation: https://esbuild.github.io/api/#banner
Footer string // Documentation: https://esbuild.github.io/api/#footer
Define map[string]string // Documentation: https://esbuild.github.io/api/#define
Pure []string // Documentation: https://esbuild.github.io/api/#pure
KeepNames bool // Documentation: https://esbuild.github.io/api/#keep-names
Sourcefile string // Documentation: https://esbuild.github.io/api/#sourcefile
Loader Loader // Documentation: https://esbuild.github.io/api/#loader
}
type TransformResult struct {
Errors []Message
Warnings []Message
Code []byte
Map []byte
}
// Documentation: https://esbuild.github.io/api/#transform-api
func Transform(input string, options TransformOptions) TransformResult {
return transformImpl(input, options)
}
////////////////////////////////////////////////////////////////////////////////
// Serve API
// Documentation: https://esbuild.github.io/api/#serve-arguments
type ServeOptions struct {
Port uint16
Host string
Servedir string
OnRequest func(ServeOnRequestArgs)
}
type ServeOnRequestArgs struct {
RemoteAddress string
Method string
Path string
Status int
TimeInMS int // The time to generate the response, not to send it
}
// Documentation: https://esbuild.github.io/api/#serve-return-values
type ServeResult struct {
Port uint16
Host string
Wait func() error
Stop func()
}
// Documentation: https://esbuild.github.io/api/#serve
func Serve(serveOptions ServeOptions, buildOptions BuildOptions) (ServeResult, error) {
return serveImpl(serveOptions, buildOptions)
}
////////////////////////////////////////////////////////////////////////////////
// Plugin API
type SideEffects uint8
const (
SideEffectsTrue SideEffects = iota
SideEffectsFalse
)
type Plugin struct {
Name string
Setup func(PluginBuild)
}
type PluginBuild struct {
InitialOptions *BuildOptions
OnStart func(callback func() (OnStartResult, error))
OnEnd func(callback func(result *BuildResult))
OnResolve func(options OnResolveOptions, callback func(OnResolveArgs) (OnResolveResult, error))
OnLoad func(options OnLoadOptions, callback func(OnLoadArgs) (OnLoadResult, error))
}
type OnStartResult struct {
Errors []Message
Warnings []Message
}
type OnResolveOptions struct {
Filter string
Namespace string
}
type OnResolveArgs struct {
Path string
Importer string
Namespace string
ResolveDir string
Kind ResolveKind
PluginData interface{}
}
type OnResolveResult struct {
PluginName string
Errors []Message
Warnings []Message
Path string
External bool
SideEffects SideEffects
Namespace string
Suffix string
PluginData interface{}
WatchFiles []string
WatchDirs []string
}
type OnLoadOptions struct {
Filter string
Namespace string
}
type OnLoadArgs struct {
Path string
Namespace string
Suffix string
PluginData interface{}
}
type OnLoadResult struct {
PluginName string
Errors []Message
Warnings []Message
Contents *string
ResolveDir string
Loader Loader
PluginData interface{}
WatchFiles []string
WatchDirs []string
}
type ResolveKind uint8
const (
ResolveEntryPoint ResolveKind = iota
ResolveJSImportStatement
ResolveJSRequireCall
ResolveJSDynamicImport
ResolveJSRequireResolve
ResolveCSSImportRule
ResolveCSSURLToken
)
////////////////////////////////////////////////////////////////////////////////
// FormatMessages API
type MessageKind uint8
const (
ErrorMessage MessageKind = iota
WarningMessage
)
type FormatMessagesOptions struct {
TerminalWidth int
Kind MessageKind
Color bool
}
func FormatMessages(msgs []Message, opts FormatMessagesOptions) []string {
return formatMsgsImpl(msgs, opts)
}
////////////////////////////////////////////////////////////////////////////////
// AnalyzeMetafile API
type AnalyzeMetafileOptions struct {
Color bool
Verbose bool
}
// Documentation: https://esbuild.github.io/api/#analyze
func AnalyzeMetafile(metafile string, opts AnalyzeMetafileOptions) string {
return analyzeMetafileImpl(metafile, opts)
}

1949
vendor/github.com/evanw/esbuild/pkg/api/api_impl.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

620
vendor/github.com/evanw/esbuild/pkg/api/serve_other.go generated vendored Normal file
View File

@ -0,0 +1,620 @@
//go:build !js || !wasm
// +build !js !wasm
package api
import (
"fmt"
"net"
"net/http"
"path"
"sort"
"strconv"
"strings"
"sync"
"syscall"
"time"
"github.com/evanw/esbuild/internal/config"
"github.com/evanw/esbuild/internal/fs"
"github.com/evanw/esbuild/internal/helpers"
"github.com/evanw/esbuild/internal/logger"
)
////////////////////////////////////////////////////////////////////////////////
// Serve API
type apiHandler struct {
mutex sync.Mutex
outdirPathPrefix string
servedir string
options *config.Options
onRequest func(ServeOnRequestArgs)
rebuild func() BuildResult
currentBuild *runningBuild
fs fs.FS
serveWaitGroup sync.WaitGroup
serveError error
}
type runningBuild struct {
waitGroup sync.WaitGroup
result BuildResult
}
func (h *apiHandler) build() BuildResult {
build := func() *runningBuild {
h.mutex.Lock()
defer h.mutex.Unlock()
if h.currentBuild == nil {
build := &runningBuild{}
build.waitGroup.Add(1)
h.currentBuild = build
// Build on another thread
go func() {
result := h.rebuild()
h.rebuild = result.Rebuild
build.result = result
build.waitGroup.Done()
// Build results stay valid for a little bit afterward since a page
// load may involve multiple requests and don't want to rebuild
// separately for each of those requests.
time.Sleep(250 * time.Millisecond)
h.mutex.Lock()
defer h.mutex.Unlock()
h.currentBuild = nil
}()
}
return h.currentBuild
}()
build.waitGroup.Wait()
return build.result
}
func escapeForHTML(text string) string {
text = strings.ReplaceAll(text, "&", "&amp;")
text = strings.ReplaceAll(text, "<", "&lt;")
text = strings.ReplaceAll(text, ">", "&gt;")
return text
}
func escapeForAttribute(text string) string {
text = escapeForHTML(text)
text = strings.ReplaceAll(text, "\"", "&quot;")
text = strings.ReplaceAll(text, "'", "&apos;")
return text
}
func (h *apiHandler) notifyRequest(duration time.Duration, req *http.Request, status int) {
if h.onRequest != nil {
h.onRequest(ServeOnRequestArgs{
RemoteAddress: req.RemoteAddr,
Method: req.Method,
Path: req.URL.Path,
Status: status,
TimeInMS: int(duration.Milliseconds()),
})
}
}
func errorsToString(errors []Message) string {
stderrOptions := logger.OutputOptions{IncludeSource: true}
terminalOptions := logger.TerminalInfo{}
sb := strings.Builder{}
limit := 5
for i, msg := range convertMessagesToInternal(nil, logger.Error, errors) {
if i == limit {
sb.WriteString(fmt.Sprintf("%d out of %d errors shown\n", limit, len(errors)))
break
}
sb.WriteString(msg.String(stderrOptions, terminalOptions))
}
return sb.String()
}
func (h *apiHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
start := time.Now()
// Handle get requests
if req.Method == "GET" && strings.HasPrefix(req.URL.Path, "/") {
res.Header().Set("Access-Control-Allow-Origin", "*")
queryPath := path.Clean(req.URL.Path)[1:]
result := h.build()
// Requests fail if the build had errors
if len(result.Errors) > 0 {
go h.notifyRequest(time.Since(start), req, http.StatusServiceUnavailable)
res.Header().Set("Content-Type", "text/plain; charset=utf-8")
res.WriteHeader(http.StatusServiceUnavailable)
res.Write([]byte(errorsToString(result.Errors)))
return
}
var kind fs.EntryKind
var fileContents fs.OpenedFile
dirEntries := make(map[string]bool)
fileEntries := make(map[string]bool)
// Check for a match with the results if we're within the output directory
if strings.HasPrefix(queryPath, h.outdirPathPrefix) {
outdirQueryPath := queryPath[len(h.outdirPathPrefix):]
if strings.HasPrefix(outdirQueryPath, "/") {
outdirQueryPath = outdirQueryPath[1:]
}
resultKind, inMemoryBytes := h.matchQueryPathToResult(outdirQueryPath, &result, dirEntries, fileEntries)
kind = resultKind
fileContents = &fs.InMemoryOpenedFile{Contents: inMemoryBytes}
} else {
// Create a fake directory entry for the output path so that it appears to be a real directory
p := h.outdirPathPrefix
for p != "" {
var dir string
var base string
if slash := strings.IndexByte(p, '/'); slash == -1 {
base = p
} else {
dir = p[:slash]
base = p[slash+1:]
}
if dir == queryPath {
kind = fs.DirEntry
dirEntries[base] = true
break
}
p = dir
}
}
// Check for a file in the fallback directory
if h.servedir != "" && kind != fs.FileEntry {
absPath := h.fs.Join(h.servedir, queryPath)
if absDir := h.fs.Dir(absPath); absDir != absPath {
if entries, err, _ := h.fs.ReadDirectory(absDir); err == nil {
if entry, _ := entries.Get(h.fs.Base(absPath)); entry != nil && entry.Kind(h.fs) == fs.FileEntry {
if contents, err, _ := h.fs.OpenFile(absPath); err == nil {
defer contents.Close()
fileContents = contents
kind = fs.FileEntry
} else if err != syscall.ENOENT {
go h.notifyRequest(time.Since(start), req, http.StatusInternalServerError)
res.WriteHeader(http.StatusInternalServerError)
res.Write([]byte(fmt.Sprintf("500 - Internal server error: %s", err.Error())))
return
}
}
}
}
}
// Check for a directory in the fallback directory
var fallbackIndexName string
if h.servedir != "" && kind != fs.FileEntry {
if entries, err, _ := h.fs.ReadDirectory(h.fs.Join(h.servedir, queryPath)); err == nil {
kind = fs.DirEntry
for _, name := range entries.SortedKeys() {
entry, _ := entries.Get(name)
switch entry.Kind(h.fs) {
case fs.DirEntry:
dirEntries[name] = true
case fs.FileEntry:
fileEntries[name] = true
if name == "index.html" {
fallbackIndexName = name
}
}
}
} else if err != syscall.ENOENT {
go h.notifyRequest(time.Since(start), req, http.StatusInternalServerError)
res.WriteHeader(http.StatusInternalServerError)
res.Write([]byte(fmt.Sprintf("500 - Internal server error: %s", err.Error())))
return
}
}
// Redirect to a trailing slash for directories
if kind == fs.DirEntry && !strings.HasSuffix(req.URL.Path, "/") {
res.Header().Set("Location", req.URL.Path+"/")
go h.notifyRequest(time.Since(start), req, http.StatusFound)
res.WriteHeader(http.StatusFound)
res.Write(nil)
return
}
// Serve a "index.html" file if present
if kind == fs.DirEntry && fallbackIndexName != "" {
queryPath += "/" + fallbackIndexName
if contents, err, _ := h.fs.OpenFile(h.fs.Join(h.servedir, queryPath)); err == nil {
defer contents.Close()
fileContents = contents
kind = fs.FileEntry
} else if err != syscall.ENOENT {
go h.notifyRequest(time.Since(start), req, http.StatusInternalServerError)
res.WriteHeader(http.StatusInternalServerError)
res.Write([]byte(fmt.Sprintf("500 - Internal server error: %s", err.Error())))
return
}
}
// Serve a file
if kind == fs.FileEntry {
// Default to serving the whole file
status := http.StatusOK
fileContentsLen := fileContents.Len()
begin := 0
end := fileContentsLen
isRange := false
// Handle range requests so that video playback works in Safari
if rangeBegin, rangeEnd, ok := parseRangeHeader(req.Header.Get("Range"), fileContentsLen); ok && rangeBegin < rangeEnd {
// Note: The content range is inclusive so subtract 1 from the end
isRange = true
begin = rangeBegin
end = rangeEnd
status = http.StatusPartialContent
}
// Try to read the range from the file, which may fail
fileBytes, err := fileContents.Read(begin, end)
if err != nil {
go h.notifyRequest(time.Since(start), req, http.StatusInternalServerError)
res.WriteHeader(http.StatusInternalServerError)
res.Write([]byte(fmt.Sprintf("500 - Internal server error: %s", err.Error())))
return
}
// If we get here, the request was successful
if contentType := helpers.MimeTypeByExtension(path.Ext(queryPath)); contentType != "" {
res.Header().Set("Content-Type", contentType)
} else {
res.Header().Set("Content-Type", "application/octet-stream")
}
if isRange {
res.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", begin, end-1, fileContentsLen))
}
res.Header().Set("Content-Length", fmt.Sprintf("%d", len(fileBytes)))
go h.notifyRequest(time.Since(start), req, status)
res.WriteHeader(status)
res.Write(fileBytes)
return
}
// Serve a directory listing
if kind == fs.DirEntry {
html := respondWithDirList(queryPath, dirEntries, fileEntries)
res.Header().Set("Content-Type", "text/html; charset=utf-8")
res.Header().Set("Content-Length", fmt.Sprintf("%d", len(html)))
go h.notifyRequest(time.Since(start), req, http.StatusOK)
res.Write(html)
return
}
}
// Default to a 404
res.Header().Set("Content-Type", "text/plain; charset=utf-8")
go h.notifyRequest(time.Since(start), req, http.StatusNotFound)
res.WriteHeader(http.StatusNotFound)
res.Write([]byte("404 - Not Found"))
}
// Handle enough of the range specification so that video playback works in Safari
func parseRangeHeader(r string, contentLength int) (int, int, bool) {
if strings.HasPrefix(r, "bytes=") {
r = r[len("bytes="):]
if dash := strings.IndexByte(r, '-'); dash != -1 {
// Note: The range is inclusive so the limit is deliberately "length - 1"
if begin, ok := parseRangeInt(r[:dash], contentLength-1); ok {
if end, ok := parseRangeInt(r[dash+1:], contentLength-1); ok {
// Note: The range is inclusive so a range of "0-1" is two bytes long
return begin, end + 1, true
}
}
}
}
return 0, 0, false
}
func parseRangeInt(text string, maxValue int) (int, bool) {
if text == "" {
return 0, false
}
value := 0
for _, c := range text {
if c < '0' || c > '9' {
return 0, false
}
value = value*10 + int(c-'0')
if value > maxValue {
return 0, false
}
}
return value, true
}
func (h *apiHandler) matchQueryPathToResult(
queryPath string,
result *BuildResult,
dirEntries map[string]bool,
fileEntries map[string]bool,
) (fs.EntryKind, []byte) {
queryIsDir := false
queryDir := queryPath
if queryDir != "" {
queryDir += "/"
}
// Check the output files for a match
for _, file := range result.OutputFiles {
if relPath, ok := h.fs.Rel(h.options.AbsOutputDir, file.Path); ok {
relPath = strings.ReplaceAll(relPath, "\\", "/")
// An exact match
if relPath == queryPath {
return fs.FileEntry, file.Contents
}
// A match inside this directory
if strings.HasPrefix(relPath, queryDir) {
entry := relPath[len(queryDir):]
queryIsDir = true
if slash := strings.IndexByte(entry, '/'); slash == -1 {
fileEntries[entry] = true
} else if dir := entry[:slash]; !dirEntries[dir] {
dirEntries[dir] = true
}
}
}
}
// Treat this as a directory if it's non-empty
if queryIsDir {
return fs.DirEntry, nil
}
return 0, nil
}
func respondWithDirList(queryPath string, dirEntries map[string]bool, fileEntries map[string]bool) []byte {
queryPath = "/" + queryPath
queryDir := queryPath
if queryDir != "/" {
queryDir += "/"
}
html := strings.Builder{}
html.WriteString(`<!doctype html>`)
html.WriteString(`<meta charset="utf8">`)
html.WriteString(`<title>Directory: `)
html.WriteString(escapeForHTML(queryDir))
html.WriteString(`</title>`)
html.WriteString(`<h1>Directory: `)
html.WriteString(escapeForHTML(queryDir))
html.WriteString(`</h1>`)
html.WriteString(`<ul>`)
// Link to the parent directory
if queryPath != "/" {
parentDir := path.Dir(queryPath)
if parentDir != "/" {
parentDir += "/"
}
html.WriteString(fmt.Sprintf(`<li><a href="%s">../</a></li>`, escapeForAttribute(parentDir)))
}
// Link to child directories
strings := make([]string, 0, len(dirEntries)+len(fileEntries))
for entry := range dirEntries {
strings = append(strings, entry)
}
sort.Strings(strings)
for _, entry := range strings {
html.WriteString(fmt.Sprintf(`<li><a href="%s/">%s/</a></li>`, escapeForAttribute(path.Join(queryPath, entry)), escapeForHTML(entry)))
}
// Link to files in the directory
strings = strings[:0]
for entry := range fileEntries {
strings = append(strings, entry)
}
sort.Strings(strings)
for _, entry := range strings {
html.WriteString(fmt.Sprintf(`<li><a href="%s">%s</a></li>`, escapeForAttribute(path.Join(queryPath, entry)), escapeForHTML(entry)))
}
html.WriteString(`</ul>`)
return []byte(html.String())
}
// This is used to make error messages platform-independent
func prettyPrintPath(fs fs.FS, path string) string {
if relPath, ok := fs.Rel(fs.Cwd(), path); ok {
return strings.ReplaceAll(relPath, "\\", "/")
}
return path
}
func serveImpl(serveOptions ServeOptions, buildOptions BuildOptions) (ServeResult, error) {
realFS, err := fs.RealFS(fs.RealFSOptions{
AbsWorkingDir: buildOptions.AbsWorkingDir,
// This is a long-lived file system object so do not cache calls to
// ReadDirectory() (they are normally cached for the duration of a build
// for performance).
DoNotCache: true,
})
if err != nil {
return ServeResult{}, err
}
buildOptions.Incremental = true
buildOptions.Write = false
// Watch and serve are both different ways of rebuilding, and cannot be combined
if buildOptions.Watch != nil {
return ServeResult{}, fmt.Errorf("Cannot use \"watch\" with \"serve\"")
}
// Validate the fallback path
if serveOptions.Servedir != "" {
if absPath, ok := realFS.Abs(serveOptions.Servedir); ok {
serveOptions.Servedir = absPath
} else {
return ServeResult{}, fmt.Errorf("Invalid serve path: %s", serveOptions.Servedir)
}
}
// If there is no output directory, set the output directory to something so
// the build doesn't try to write to stdout. Make sure not to set this to a
// path that may contain the user's files in it since we don't want to get
// errors about overwriting input files.
outdirPathPrefix := ""
if buildOptions.Outdir == "" && buildOptions.Outfile == "" {
buildOptions.Outdir = realFS.Join(realFS.Cwd(), "...")
} else if serveOptions.Servedir != "" {
// Compute the output directory
var outdir string
if buildOptions.Outdir != "" {
if absPath, ok := realFS.Abs(buildOptions.Outdir); ok {
outdir = absPath
} else {
return ServeResult{}, fmt.Errorf("Invalid outdir path: %s", buildOptions.Outdir)
}
} else {
if absPath, ok := realFS.Abs(buildOptions.Outfile); ok {
outdir = realFS.Dir(absPath)
} else {
return ServeResult{}, fmt.Errorf("Invalid outdir path: %s", buildOptions.Outfile)
}
}
// Make sure the output directory is contained in the fallback directory
relPath, ok := realFS.Rel(serveOptions.Servedir, outdir)
if !ok {
return ServeResult{}, fmt.Errorf(
"Cannot compute relative path from %q to %q\n", serveOptions.Servedir, outdir)
}
relPath = strings.ReplaceAll(relPath, "\\", "/") // Fix paths on Windows
if relPath == ".." || strings.HasPrefix(relPath, "../") {
return ServeResult{}, fmt.Errorf(
"Output directory %q must be contained in serve directory %q",
prettyPrintPath(realFS, outdir),
prettyPrintPath(realFS, serveOptions.Servedir),
)
}
if relPath != "." {
outdirPathPrefix = relPath
}
}
// Determine the host
var listener net.Listener
network := "tcp4"
host := "0.0.0.0"
if serveOptions.Host != "" {
host = serveOptions.Host
// Only use "tcp4" if this is an IPv4 address, otherwise use "tcp"
if ip := net.ParseIP(host); ip == nil || ip.To4() == nil {
network = "tcp"
}
}
// Pick the port
if serveOptions.Port == 0 {
// Default to picking a "800X" port
for port := 8000; port <= 8009; port++ {
if result, err := net.Listen(network, net.JoinHostPort(host, fmt.Sprintf("%d", port))); err == nil {
listener = result
break
}
}
}
if listener == nil {
// Otherwise pick the provided port
if result, err := net.Listen(network, net.JoinHostPort(host, fmt.Sprintf("%d", serveOptions.Port))); err != nil {
return ServeResult{}, err
} else {
listener = result
}
}
// Try listening on the provided port
addr := listener.Addr().String()
// Extract the real port in case we passed a port of "0"
var result ServeResult
if host, text, err := net.SplitHostPort(addr); err == nil {
if port, err := strconv.ParseInt(text, 10, 32); err == nil {
result.Port = uint16(port)
result.Host = host
}
}
var stoppingMutex sync.Mutex
isStopping := false
// The first build will just build normally
var handler *apiHandler
handler = &apiHandler{
onRequest: serveOptions.OnRequest,
outdirPathPrefix: outdirPathPrefix,
servedir: serveOptions.Servedir,
rebuild: func() BuildResult {
stoppingMutex.Lock()
defer stoppingMutex.Unlock()
// Don't start more rebuilds if we were told to stop
if isStopping {
return BuildResult{}
}
build := buildImpl(buildOptions)
if handler.options == nil {
handler.options = &build.options
}
return build.result
},
fs: realFS,
}
// When wait is called, block until the server's call to "Serve()" returns
result.Wait = func() error {
handler.serveWaitGroup.Wait()
return handler.serveError
}
// Create the server
server := &http.Server{Addr: addr, Handler: handler}
// When stop is called, block further rebuilds and then close the server
result.Stop = func() {
stoppingMutex.Lock()
defer stoppingMutex.Unlock()
// Only try to close the server once
if isStopping {
return
}
isStopping = true
// Close the server and wait for it to close
server.Close()
handler.serveWaitGroup.Wait()
}
// Start the server and signal on "serveWaitGroup" when it stops
handler.serveWaitGroup.Add(1)
go func() {
if err := server.Serve(listener); err != http.ErrServerClosed {
handler.serveError = err
}
handler.serveWaitGroup.Done()
}()
// Start the first build shortly after this function returns (but not
// immediately so that stuff we print right after this will come first)
go func() {
time.Sleep(10 * time.Millisecond)
handler.build()
}()
return result, nil
}

12
vendor/github.com/evanw/esbuild/pkg/api/serve_wasm.go generated vendored Normal file
View File

@ -0,0 +1,12 @@
//go:build js && wasm
// +build js,wasm
package api
import "fmt"
// Remove the serve API in the WebAssembly build. This removes 2.7mb of stuff.
func serveImpl(serveOptions ServeOptions, buildOptions BuildOptions) (ServeResult, error) {
return ServeResult{}, fmt.Errorf("The \"serve\" API is not supported when using WebAssembly")
}