+build !js !wasm

package api

import (
	
	
	
	
	
	
	
	
	
	
	

	
	
	
)
////////////////////////////////////////////////////////////////////////////// Serve API
Build on another thread
			go func() {
				 := .rebuild()
				.rebuild = .Rebuild
				.result = 
				.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)
				.mutex.Lock()
				defer .mutex.Unlock()
				.currentBuild = nil
			}()
		}
		return .currentBuild
	}()
	.waitGroup.Wait()
	return .result
}

func ( string) string {
	 = strings.ReplaceAll(, "&", "&")
	 = strings.ReplaceAll(, "<", "&lt;")
	 = strings.ReplaceAll(, ">", "&gt;")
	return 
}

func ( string) string {
	 = escapeForHTML()
	 = strings.ReplaceAll(, "\"", "&quot;")
	 = strings.ReplaceAll(, "'", "&apos;")
	return 
}

func ( *apiHandler) ( time.Duration,  *http.Request,  int) {
	if .onRequest != nil {
		.onRequest(ServeOnRequestArgs{
			RemoteAddress: .RemoteAddr,
			Method:        .Method,
			Path:          .URL.Path,
			Status:        ,
			TimeInMS:      int(.Milliseconds()),
		})
	}
}

func ( []Message) string {
	 := logger.OutputOptions{IncludeSource: true}
	 := logger.TerminalInfo{}
	 := strings.Builder{}
	 := 5
	for ,  := range convertMessagesToInternal(nil, logger.Error, ) {
		if  ==  {
			.WriteString(fmt.Sprintf("%d out of %d errors shown\n", , len()))
			break
		}
		.WriteString(.String(, ))
	}
	return .String()
}

func ( *apiHandler) ( http.ResponseWriter,  *http.Request) {
	 := time.Now()
Handle get requests
	if .Method == "GET" && strings.HasPrefix(.URL.Path, "/") {
		.Header().Set("Access-Control-Allow-Origin", "*")
		 := path.Clean(.URL.Path)[1:]
		 := .build()
Requests fail if the build had errors
		if len(.Errors) > 0 {
			go .notifyRequest(time.Since(), , http.StatusServiceUnavailable)
			.Header().Set("Content-Type", "text/plain; charset=utf-8")
			.WriteHeader(http.StatusServiceUnavailable)
			.Write([]byte(errorsToString(.Errors)))
			return
		}

		var  fs.EntryKind
		var  []byte
		 := make(map[string]bool)
		 := make(map[string]bool)
Check for a match with the results if we're within the output directory
		if strings.HasPrefix(, .outdirPathPrefix) {
			 := [len(.outdirPathPrefix):]
			if strings.HasPrefix(, "/") {
				 = [1:]
			}
			,  = .matchQueryPathToResult(, &, , )
Create a fake directory entry for the output path so that it appears to be a real directory
			 := .outdirPathPrefix
			for  != "" {
				var  string
				var  string
				if  := strings.IndexByte(, '/');  == -1 {
					 = 
				} else {
					 = [:]
					 = [+1:]
				}
				if  ==  {
					 = fs.DirEntry
					[] = true
					break
				}
				 = 
			}
		}
Check for a file in the fallback directory
		if .servedir != "" &&  != fs.FileEntry {
			 := .fs.Join(.servedir, )
			if  := .fs.Dir();  !=  {
				if ,  := .fs.ReadDirectory();  == nil {
					if ,  := .Get(.fs.Base());  != nil && .Kind(.fs) == fs.FileEntry {
						if ,  := .fs.ReadFile();  == nil {
							 = []byte()
							 = fs.FileEntry
						} else if  != syscall.ENOENT {
							go .notifyRequest(time.Since(), , http.StatusInternalServerError)
							.WriteHeader(http.StatusInternalServerError)
							.Write([]byte(fmt.Sprintf("500 - Internal server error: %s", .Error())))
							return
						}
					}
				}
			}
		}
Check for a directory in the fallback directory
		var  string
		if .servedir != "" &&  != fs.FileEntry {
			if ,  := .fs.ReadDirectory(.fs.Join(.servedir, ));  == nil {
				 = fs.DirEntry
				for ,  := range .UnorderedKeys() {
					,  := .Get()
					switch .Kind(.fs) {
					case fs.DirEntry:
						[] = true
					case fs.FileEntry:
						[] = true
						if  == "index.html" {
							 = 
						}
					}
				}
			} else if  != syscall.ENOENT {
				go .notifyRequest(time.Since(), , http.StatusInternalServerError)
				.WriteHeader(http.StatusInternalServerError)
				.Write([]byte(fmt.Sprintf("500 - Internal server error: %s", .Error())))
				return
			}
		}
Redirect to a trailing slash for directories
		if  == fs.DirEntry && !strings.HasSuffix(.URL.Path, "/") {
			.Header().Set("Location", .URL.Path+"/")
			go .notifyRequest(time.Since(), , http.StatusFound)
			.WriteHeader(http.StatusFound)
			.Write(nil)
			return
		}
Serve a "index.html" file if present
		if  == fs.DirEntry &&  != "" {
			 += "/" + 
			,  := .fs.ReadFile(.fs.Join(.servedir, ))
			if  == nil {
				 = []byte()
				 = fs.FileEntry
			} else if  != syscall.ENOENT {
				go .notifyRequest(time.Since(), , http.StatusInternalServerError)
				.WriteHeader(http.StatusInternalServerError)
				.Write([]byte(fmt.Sprintf("500 - Internal server error: %s", .Error())))
				return
			}
		}
Serve a file
		if  == fs.FileEntry {
			if  := mime.TypeByExtension(path.Ext());  != "" {
				.Header().Set("Content-Type", )
			}
Handle range requests so that video playback works in Safari
			 := http.StatusOK
Note: The content range is inclusive so subtract 1 from the end
				.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", , -1, len()))
				 = [:]
				 = http.StatusPartialContent
			}

			.Header().Set("Content-Length", fmt.Sprintf("%d", len()))
			go .notifyRequest(time.Since(), , )
			.WriteHeader()
			.Write()
			return
		}
Serve a directory listing
		if  == fs.DirEntry {
			 := respondWithDirList(, , )
			.Header().Set("Content-Type", "text/html; charset=utf-8")
			.Header().Set("Content-Length", fmt.Sprintf("%d", len()))
			go .notifyRequest(time.Since(), , http.StatusOK)
			.Write()
			return
		}
	}
Default to a 404
	.Header().Set("Content-Type", "text/plain; charset=utf-8")
	go .notifyRequest(time.Since(), , http.StatusNotFound)
	.WriteHeader(http.StatusNotFound)
	.Write([]byte("404 - Not Found"))
}
Handle enough of the range specification so that video playback works in Safari
func ( string,  int) (int, int, bool) {
	if strings.HasPrefix(, "bytes=") {
		 = [len("bytes="):]
Note: The range is inclusive so the limit is deliberately "length - 1"
			if ,  := parseRangeInt([:], -1);  {
Note: The range is inclusive so a range of "0-1" is two bytes long
					return ,  + 1, true
				}
			}
		}
	}
	return 0, 0, false
}

func ( string,  int) (int, bool) {
	if  == "" {
		return 0, false
	}
	 := 0
	for ,  := range  {
		if  < '0' ||  > '9' {
			return 0, false
		}
		 = *10 + int(-'0')
		if  >  {
			return 0, false
		}
	}
	return , true
}

func ( *apiHandler) (
	 string,
	 *BuildResult,
	 map[string]bool,
	 map[string]bool,
) (fs.EntryKind, []byte) {
	 := false
	 := 
	if  != "" {
		 += "/"
	}
Check the output files for a match
	for ,  := range .OutputFiles {
		if ,  := .fs.Rel(.options.AbsOutputDir, .Path);  {
			 = strings.ReplaceAll(, "\\", "/")
An exact match
			if  ==  {
				return fs.FileEntry, .Contents
			}
A match inside this directory
			if strings.HasPrefix(, ) {
				 := [len():]
				 = true
				if  := strings.IndexByte(, '/');  == -1 {
					[] = true
				} else if  := [:]; ![] {
					[] = true
				}
			}
		}
	}
Treat this as a directory if it's non-empty
	if  {
		return fs.DirEntry, nil
	}

	return 0, nil
}

func ( string,  map[string]bool,  map[string]bool) []byte {
	 = "/" + 
	 := 
	if  != "/" {
		 += "/"
	}
	 := strings.Builder{}
	.WriteString(`<!doctype html>`)
	.WriteString(`<meta charset="utf8">`)
	.WriteString(`<title>Directory: `)
	.WriteString(escapeForHTML())
	.WriteString(`</title>`)
	.WriteString(`<h1>Directory: `)
	.WriteString(escapeForHTML())
	.WriteString(`</h1>`)
	.WriteString(`<ul>`)
Link to the parent directory
	if  != "/" {
		 := path.Dir()
		if  != "/" {
			 += "/"
		}
		.WriteString(fmt.Sprintf(`<li><a href="%s">../</a></li>`, escapeForAttribute()))
	}
Link to child directories
	 := make([]string, 0, len()+len())
	for  := range  {
		 = append(, )
	}
	sort.Strings()
	for ,  := range  {
		.WriteString(fmt.Sprintf(`<li><a href="%s/">%s/</a></li>`, escapeForAttribute(path.Join(, )), escapeForHTML()))
	}
Link to files in the directory
	 = [:0]
	for  := range  {
		 = append(, )
	}
	sort.Strings()
	for ,  := range  {
		.WriteString(fmt.Sprintf(`<li><a href="%s">%s</a></li>`, escapeForAttribute(path.Join(, )), escapeForHTML()))
	}

	.WriteString(`</ul>`)
	return []byte(.String())
}
This is used to make error messages platform-independent
func ( fs.FS,  string) string {
	if ,  := .Rel(.Cwd(), );  {
		return strings.ReplaceAll(, "\\", "/")
	}
	return 
}

func ( ServeOptions,  BuildOptions) (ServeResult, error) {
	,  := fs.RealFS(fs.RealFSOptions{
		AbsWorkingDir: .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  != nil {
		return ServeResult{}, 
	}
	.Incremental = true
	.Write = false
Watch and serve are both different ways of rebuilding, and cannot be combined
	if .Watch != nil {
		return ServeResult{}, fmt.Errorf("Cannot use \"watch\" with \"serve\"")
	}
Validate the fallback path
	if .Servedir != "" {
		if ,  := .Abs(.Servedir);  {
			.Servedir = 
		} else {
			return ServeResult{}, fmt.Errorf("Invalid serve path: %s", .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.
	 := ""
	if .Outdir == "" && .Outfile == "" {
		.Outdir = .Join(.Cwd(), "...")
Compute the output directory
		var  string
		if .Outdir != "" {
			if ,  := .Abs(.Outdir);  {
				 = 
			} else {
				return ServeResult{}, fmt.Errorf("Invalid outdir path: %s", .Outdir)
			}
		} else {
			if ,  := .Abs(.Outfile);  {
				 = .Dir()
			} else {
				return ServeResult{}, fmt.Errorf("Invalid outdir path: %s", .Outfile)
			}
		}
Make sure the output directory is contained in the fallback directory
		,  := .Rel(.Servedir, )
		if ! {
			return ServeResult{}, fmt.Errorf(
				"Cannot compute relative path from %q to %q\n", .Servedir, )
		}
		 = strings.ReplaceAll(, "\\", "/") // Fix paths on Windows
		if  == ".." || strings.HasPrefix(, "../") {
			return ServeResult{}, fmt.Errorf(
				"Output directory %q must be contained in serve directory %q",
				prettyPrintPath(, ),
				prettyPrintPath(, .Servedir),
			)
		}
		if  != "." {
			 = 
		}
	}
Determine the host
	var  net.Listener
	 := "tcp4"
	 := "0.0.0.0"
	if .Host != "" {
		 = .Host
Only use "tcp4" if this is an IPv4 address, otherwise use "tcp"
		if  := net.ParseIP();  == nil || .To4() == nil {
			 = "tcp"
		}
	}
Pick the port
Default to picking a "800X" port
		for  := 8000;  <= 8009; ++ {
			if ,  := net.Listen(, net.JoinHostPort(, fmt.Sprintf("%d", )));  == nil {
				 = 
				break
			}
		}
	}
Otherwise pick the provided port
		if ,  := net.Listen(, net.JoinHostPort(, fmt.Sprintf("%d", .Port)));  != nil {
			return ServeResult{}, 
		} else {
			 = 
		}
	}
Try listening on the provided port
	 := .Addr().String()
Extract the real port in case we passed a port of "0"
	var  ServeResult
	if , ,  := net.SplitHostPort();  == nil {
		if ,  := strconv.ParseInt(, 10, 32);  == nil {
			.Port = uint16()
			.Host = 
		}
	}
The first build will just build normally
	var  *apiHandler
	 = &apiHandler{
		onRequest:        .OnRequest,
		outdirPathPrefix: ,
		servedir:         .Servedir,
		rebuild: func() BuildResult {
			 := buildImpl()
			if .options == nil {
				.options = &.options
			}
			return .result
		},
		fs: ,
	}
Start the server
	 := &http.Server{Addr: , Handler: }
	 := make(chan error, 1)
	.Wait = func() error { return <- }
	.Stop = func() { .Close() }
	go func() {
		if  := .Serve();  != http.ErrServerClosed {
			 <- 
		} else {
			 <- nil
		}
	}()
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)
		.build()
	}()
	return , nil