Borg/cmd/serve.go
google-labs-jules[bot] efee04bfdb feat: Add PWA download and serve commands
This commit introduces two new commands: `pwa` and `serve`.

The `pwa` command downloads a Progressive Web Application (PWA) from a given URL. It discovers the PWA's manifest, downloads the assets referenced in the manifest (start URL and icons), and packages them into a single `.tar` file.

The `serve` command takes a `.tar` file created by the `pwa` command and serves its contents using a standard Go HTTP file server. It unpacks the tarball into an in-memory filesystem, making it a self-contained and efficient way to host the downloaded PWA.
2025-10-31 20:32:46 +00:00

169 lines
3.3 KiB
Go

package cmd
import (
"archive/tar"
"bytes"
"fmt"
"io"
"io/fs"
"net/http"
"os"
"path"
"strings"
"time"
"github.com/spf13/cobra"
)
// serveCmd represents the serve command
var serveCmd = &cobra.Command{
Use: "serve [file]",
Short: "Serve a packaged PWA file",
Long: `Serves the contents of a packaged PWA file using a static file server.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
pwaFile := args[0]
port, _ := cmd.Flags().GetString("port")
pwaData, err := os.ReadFile(pwaFile)
if err != nil {
fmt.Printf("Error reading PWA file: %v\n", err)
return
}
memFS, err := newMemoryFS(pwaData)
if err != nil {
fmt.Printf("Error creating in-memory filesystem: %v\n", err)
return
}
http.Handle("/", http.FileServer(http.FS(memFS)))
fmt.Printf("Serving PWA on http://localhost:%s\n", port)
err = http.ListenAndServe(":"+port, nil)
if err != nil {
fmt.Printf("Error starting server: %v\n", err)
return
}
},
}
// memoryFS is an in-memory filesystem that implements fs.FS
type memoryFS struct {
files map[string]*memoryFile
}
func newMemoryFS(tarball []byte) (*memoryFS, error) {
memFS := &memoryFS{files: make(map[string]*memoryFile)}
tarReader := tar.NewReader(bytes.NewReader(tarball))
for {
header, err := tarReader.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
if header.Typeflag == tar.TypeReg {
data, err := io.ReadAll(tarReader)
if err != nil {
return nil, err
}
name := strings.TrimPrefix(header.Name, "/")
memFS.files[name] = &memoryFile{
name: name,
content: data,
modTime: header.ModTime,
}
}
}
return memFS, nil
}
func (m *memoryFS) Open(name string) (fs.File, error) {
name = strings.TrimPrefix(name, "/")
if name == "" {
name = "index.html"
}
if file, ok := m.files[name]; ok {
return &memoryFileReader{file: file}, nil
}
return nil, fs.ErrNotExist
}
// memoryFile represents a file in the in-memory filesystem
type memoryFile struct {
name string
content []byte
modTime time.Time
}
func (m *memoryFile) Stat() (fs.FileInfo, error) {
return &memoryFileInfo{file: m}, nil
}
func (m *memoryFile) Read(p []byte) (int, error) {
return 0, nil // This is implemented by memoryFileReader
}
func (m *memoryFile) Close() error {
return nil
}
// memoryFileInfo implements fs.FileInfo for a memoryFile
type memoryFileInfo struct {
file *memoryFile
}
func (m *memoryFileInfo) Name() string {
return path.Base(m.file.name)
}
func (m *memoryFileInfo) Size() int64 {
return int64(len(m.file.content))
}
func (m *memoryFileInfo) Mode() fs.FileMode {
return 0444
}
func (m *memoryFileInfo) ModTime() time.Time {
return m.file.modTime
}
func (m *memoryFileInfo) IsDir() bool {
return false
}
func (m *memoryFileInfo) Sys() interface{} {
return nil
}
// memoryFileReader implements fs.File for a memoryFile
type memoryFileReader struct {
file *memoryFile
reader *bytes.Reader
}
func (m *memoryFileReader) Stat() (fs.FileInfo, error) {
return m.file.Stat()
}
func (m *memoryFileReader) Read(p []byte) (int, error) {
if m.reader == nil {
m.reader = bytes.NewReader(m.file.content)
}
return m.reader.Read(p)
}
func (m *memoryFileReader) Close() error {
return nil
}
func init() {
rootCmd.AddCommand(serveCmd)
serveCmd.PersistentFlags().String("port", "8080", "Port to serve the PWA on")
}