diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 8c8d0fbfc7..5d5bdf24b7 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -38,7 +38,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0ff90715b1..0b7dd74cef 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,7 +11,7 @@ jobs: lint: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 @@ -39,7 +39,7 @@ jobs: go-version: "1.22.2" - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: "16" @@ -56,7 +56,7 @@ jobs: mingw-w64-x86_64-toolchain - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Linux dependencies if: matrix.os == 'ubuntu-22.04' @@ -151,7 +151,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: "0" @@ -167,7 +167,7 @@ jobs: path: build - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v5 with: distribution: goreleaser-pro version: v1.12.3 diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 17a880935b..5c3e7c593f 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -9,7 +9,7 @@ jobs: lint: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 @@ -37,7 +37,7 @@ jobs: go-version: "1.22.2" - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: "16" @@ -54,7 +54,7 @@ jobs: mingw-w64-x86_64-toolchain - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Linux dependencies if: matrix.os == 'ubuntu-22.04' diff --git a/.gitignore b/.gitignore index 672c066f1f..659fc01ffc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,8 @@ .DS_Store # Rendered Apps -examples/*.webp -examples/*.gif +examples/**/*.webp +examples/**/*.gif # Pixlet Binary pixlet diff --git a/README.md b/README.md index d998a22ec9..0fade81e60 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ def main(): Render and serve it with: ```console -curl https://raw.githubusercontent.com/tidbyt/pixlet/main/examples/hello_world.star | \ +curl https://raw.githubusercontent.com/tidbyt/pixlet/main/examples/hello_world/hello_world.star | \ pixlet serve /dev/stdin ``` @@ -135,7 +135,7 @@ to show the Bitcoin tracker on your Tidbyt: ```console # render the bitcoin example -pixlet render examples/bitcoin.star +pixlet render examples/bitcoin/bitcoin.star # login to your Tidbyt account pixlet login @@ -144,7 +144,7 @@ pixlet login pixlet devices # push to your favorite Tidbyt -pixlet push examples/bitcoin.webp +pixlet push examples/bitcoin/bitcoin.webp ``` To get the ID for a device, run `pixlet devices`. Alternatively, you can @@ -158,8 +158,8 @@ If all goes well, you should see the Bitcoin tracker appear on your Tidbyt: Pushing an applet to your Tidbyt without an installation ID simply displays your applet one time. If you would like your applet to continously display as part of the rotation, add an installation ID to the push command: ```console -pixlet render examples/bitcoin.star -pixlet push --installation-id examples/bitcoin.webp +pixlet render examples/bitcoin/bitcoin.star +pixlet push --installation-id examples/bitcoin/bitcoin.webp ``` For example, if we set the `installationID` to "Bitcoin", it would appear in the mobile app as follows: diff --git a/bundle/bundle.go b/bundle/bundle.go index 5c4c4c211b..e06c130c3d 100644 --- a/bundle/bundle.go +++ b/bundle/bundle.go @@ -2,13 +2,14 @@ package bundle import ( - "archive/tar" "bytes" "compress/gzip" "fmt" "io" + "io/fs" "os" - "path/filepath" + + "github.com/nlepage/go-tarfs" "tidbyt.dev/pixlet/manifest" ) @@ -26,16 +27,12 @@ const ( // AppBundle represents the unpacked bundle in our system. type AppBundle struct { - Source []byte Manifest *manifest.Manifest + Source fs.FS } -// InitFromPath translates a directory containing an app manifest and source -// into an AppBundle. -func InitFromPath(dir string) (*AppBundle, error) { - // Load manifest - path := filepath.Join(dir, manifest.ManifestFileName) - m, err := os.Open(path) +func FromFS(fs fs.FS) (*AppBundle, error) { + m, err := fs.Open(manifest.ManifestFileName) if err != nil { return nil, fmt.Errorf("could not open manifest: %w", err) } @@ -46,140 +43,37 @@ func InitFromPath(dir string) (*AppBundle, error) { return nil, fmt.Errorf("could not load manifest: %w", err) } - // Load source - path = filepath.Join(dir, man.FileName) - s, err := os.Open(path) - if err != nil { - return nil, fmt.Errorf("could not open app source: %w", err) - } - defer s.Close() - - src, err := io.ReadAll(s) - if err != nil { - return nil, fmt.Errorf("could not read app source: %w", err) - } - // Create app bundle struct return &AppBundle{ Manifest: man, - Source: src, + Source: fs, }, nil } +// FromDir translates a directory containing an app manifest and source +// into an AppBundle. +func FromDir(dir string) (*AppBundle, error) { + return FromFS(os.DirFS(dir)) +} + // LoadBundle loads a compressed archive into an AppBundle. func LoadBundle(in io.Reader) (*AppBundle, error) { gzr, err := gzip.NewReader(in) if err != nil { - return nil, fmt.Errorf("could not create gzip reader: %w", err) + return nil, fmt.Errorf("creating gzip reader: %w", err) } defer gzr.Close() - tr := tar.NewReader(gzr) - ab := &AppBundle{} - - for { - header, err := tr.Next() + // read the entire tarball into memory so that we can seek + // around it, and so that the underlying reader can be closed. + var b bytes.Buffer + io.Copy(&b, gzr) - switch { - case err == io.EOF: - // If there are no more files in the bundle, validate and return it. - if ab.Manifest == nil { - return nil, fmt.Errorf("could not find manifest in archive") - } - if ab.Source == nil { - return nil, fmt.Errorf("could not find source in archive") - } - return ab, nil - case err != nil: - // If there is an error, return immediately. - return nil, fmt.Errorf("could not read archive: %w", err) - case header == nil: - // If for some reason we end up with a blank header, continue to the - // next one. - continue - case header.Name == AppSourceName: - // Load the app source. - buff := make([]byte, header.Size) - _, err := io.ReadFull(tr, buff) - if err != nil { - return nil, fmt.Errorf("could not read source from archive: %w", err) - } - ab.Source = buff - case header.Name == manifest.ManifestFileName: - // Load the app manifest. - buff := make([]byte, header.Size) - _, err := io.ReadFull(tr, buff) - if err != nil { - return nil, fmt.Errorf("could not read manifest from archive: %w", err) - } - - man, err := manifest.LoadManifest(bytes.NewReader(buff)) - if err != nil { - return nil, fmt.Errorf("could not load manifest: %w", err) - } - ab.Manifest = man - } - } -} - -// WriteBundleToPath is a helper to be able to write the bundle to a provided -// directory. -func (b *AppBundle) WriteBundleToPath(dir string) error { - path := filepath.Join(dir, AppBundleName) - f, err := os.Create(path) - if err != nil { - return fmt.Errorf("could not create file for bundle: %w", err) - } - defer f.Close() - - return b.WriteBundle(f) -} - -// WriteBundle writes a compressed archive to the provided writer. -func (ab *AppBundle) WriteBundle(out io.Writer) error { - // Setup writers. - gzw := gzip.NewWriter(out) - defer gzw.Close() - - tw := tar.NewWriter(gzw) - defer tw.Close() - - // Write manifest. - buff := &bytes.Buffer{} - err := ab.Manifest.WriteManifest(buff) - if err != nil { - return fmt.Errorf("could not write manifest to buffer: %w", err) - } - b := buff.Bytes() - - hdr := &tar.Header{ - Name: manifest.ManifestFileName, - Mode: 0600, - Size: int64(len(b)), - } - err = tw.WriteHeader(hdr) - if err != nil { - return fmt.Errorf("could not write manifest header: %w", err) - } - _, err = tw.Write(b) - if err != nil { - return fmt.Errorf("could not write manifest to archive: %w", err) - } - - // Write source. - hdr = &tar.Header{ - Name: AppSourceName, - Mode: 0600, - Size: int64(len(ab.Source)), - } - err = tw.WriteHeader(hdr) - if err != nil { - return fmt.Errorf("could not write source header: %w", err) - } - _, err = tw.Write(ab.Source) + r := bytes.NewReader(b.Bytes()) + fs, err := tarfs.New(r) if err != nil { - return fmt.Errorf("could not write source to archive: %w", err) + return nil, fmt.Errorf("creating tarfs: %w", err) } - return nil + return FromFS(fs) } diff --git a/bundle/bundle_test.go b/bundle/bundle_test.go index 77e9ffbd37..0d35a6e97a 100644 --- a/bundle/bundle_test.go +++ b/bundle/bundle_test.go @@ -11,10 +11,10 @@ import ( func TestBundleWriteAndLoad(t *testing.T) { // Ensure we can load the bundle from an app. - ab, err := bundle.InitFromPath("testdata/testapp") + ab, err := bundle.FromDir("testdata/testapp") assert.NoError(t, err) assert.Equal(t, "test-app", ab.Manifest.ID) - assert.True(t, len(ab.Source) > 0) + assert.NotNil(t, ab.Source) // Create a temp directory. dir, err := os.MkdirTemp("", "") @@ -32,8 +32,63 @@ func TestBundleWriteAndLoad(t *testing.T) { newBun, err := bundle.LoadBundle(f) assert.NoError(t, err) assert.Equal(t, "test-app", newBun.Manifest.ID) - assert.True(t, len(ab.Source) > 0) + assert.NotNil(t, ab.Source) + + // Ensure the loaded bundle contains the files we expect. + filesExpected := []string{ + "manifest.yaml", + "test_app.star", + "test.txt", + "a_subdirectory/hi.jpg", + } + for _, file := range filesExpected { + _, err := newBun.Source.Open(file) + assert.NoError(t, err) + } + + // Ensure the loaded bundle does not contain any extra files. + _, err = newBun.Source.Open("unused.txt") + assert.ErrorIs(t, err, os.ErrNotExist) } + +func TestBundleWriteAndLoadWithoutRuntime(t *testing.T) { + ab, err := bundle.FromDir("testdata/testapp") + assert.NoError(t, err) + assert.Equal(t, "test-app", ab.Manifest.ID) + assert.NotNil(t, ab.Source) + + // Create a temp directory. + dir, err := os.MkdirTemp("", "") + assert.NoError(t, err) + + // Write bundle to the temp directory, without tree-shaking. + err = ab.WriteBundleToPath(dir, bundle.WithoutRuntime()) + assert.NoError(t, err) + + // Ensure we can load up the bundle just created. + path := filepath.Join(dir, bundle.AppBundleName) + f, err := os.Open(path) + assert.NoError(t, err) + defer f.Close() + newBun, err := bundle.LoadBundle(f) + assert.NoError(t, err) + assert.Equal(t, "test-app", newBun.Manifest.ID) + assert.NotNil(t, ab.Source) + + // Ensure the loaded bundle contains the files we expect. + filesExpected := []string{ + "manifest.yaml", + "test_app.star", + "test.txt", + "a_subdirectory/hi.jpg", + "unused.txt", + } + for _, file := range filesExpected { + _, err := newBun.Source.Open(file) + assert.NoError(t, err) + } +} + func TestLoadBundle(t *testing.T) { f, err := os.Open("testdata/bundle.tar.gz") assert.NoError(t, err) @@ -41,7 +96,7 @@ func TestLoadBundle(t *testing.T) { ab, err := bundle.LoadBundle(f) assert.NoError(t, err) assert.Equal(t, "test-app", ab.Manifest.ID) - assert.True(t, len(ab.Source) > 0) + assert.NotNil(t, ab.Source) } func TestLoadBundleExcessData(t *testing.T) { f, err := os.Open("testdata/excess-files.tar.gz") @@ -51,5 +106,5 @@ func TestLoadBundleExcessData(t *testing.T) { ab, err := bundle.LoadBundle(f) assert.NoError(t, err) assert.Equal(t, "test-app", ab.Manifest.ID) - assert.True(t, len(ab.Source) > 0) + assert.NotNil(t, ab.Source) } diff --git a/bundle/testdata/testapp/a_subdirectory/hi.jpg b/bundle/testdata/testapp/a_subdirectory/hi.jpg new file mode 100644 index 0000000000..8da8481f2f Binary files /dev/null and b/bundle/testdata/testapp/a_subdirectory/hi.jpg differ diff --git a/bundle/testdata/testapp/manifest.yaml b/bundle/testdata/testapp/manifest.yaml index f2b10a8a60..630cebcd79 100644 --- a/bundle/testdata/testapp/manifest.yaml +++ b/bundle/testdata/testapp/manifest.yaml @@ -4,5 +4,3 @@ name: Test App summary: For Testing desc: It's an app for testing. author: Test Dev -fileName: test_app.star -packageName: testapp diff --git a/bundle/testdata/testapp/test_app.star b/bundle/testdata/testapp/test_app.star index fc8ae9d840..cee0a6c5bc 100644 --- a/bundle/testdata/testapp/test_app.star +++ b/bundle/testdata/testapp/test_app.star @@ -5,11 +5,17 @@ Description: It's an app for testing. Author: Test Dev """ +load("a_subdirectory/hi.jpg", hi_jpeg = "file") load("render.star", "render") load("schema.star", "schema") +load("test.txt", test_txt = "file") DEFAULT_WHO = "world" +TEST_TXT_CONTENT = test_txt.readall() + +HI_JPEG_BYTES = hi_jpeg.readall("rb") + def main(config): who = config.str("who", DEFAULT_WHO) message = "Hello, {}!".format(who) diff --git a/bundle/testdata/testapp/unused.txt b/bundle/testdata/testapp/unused.txt new file mode 100644 index 0000000000..c3e8caa0b7 --- /dev/null +++ b/bundle/testdata/testapp/unused.txt @@ -0,0 +1 @@ +this file is not used in the app \ No newline at end of file diff --git a/bundle/write.go b/bundle/write.go new file mode 100644 index 0000000000..ae1188b863 --- /dev/null +++ b/bundle/write.go @@ -0,0 +1,142 @@ +// Package bundle provides primitives for bundling apps for portability. +package bundle + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "slices" + + "tidbyt.dev/pixlet/manifest" + "tidbyt.dev/pixlet/runtime" +) + +type WriteOption interface{} + +type withoutRuntimeOption struct{} + +// WithoutRuntime is a WriteOption that can be used to write the bundle without +// using the runtime to determine the files to include in the bundle. Instead, +// all files in the source FS will be included in the bundle. +// +// This is useful when writing a bundle that is known not to contain any +// unnecessary files, when loading and rewriting a bundle that was already +// tree-shaken, or when loading the entire runtime is not possible for +// performance or security reasons. +func WithoutRuntime() WriteOption { + return withoutRuntimeOption{} +} + +// WriteBundleToPath is a helper to be able to write the bundle to a provided +// directory. +func (b *AppBundle) WriteBundleToPath(dir string, opts ...WriteOption) error { + path := filepath.Join(dir, AppBundleName) + f, err := os.Create(path) + if err != nil { + return fmt.Errorf("could not create file for bundle: %w", err) + } + defer f.Close() + + return b.WriteBundle(f, opts...) +} + +// WriteBundle writes a compressed archive to the provided writer. +func (ab *AppBundle) WriteBundle(out io.Writer, opts ...WriteOption) error { + var bundleFiles []string + + if slices.Contains(opts, WithoutRuntime()) { + // we can't use the runtime to determine the files to include in the + // bundle, so we'll just include everything in the source FS. + err := fs.WalkDir(ab.Source, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return fmt.Errorf("walking directory: %w", err) + } + if !d.IsDir() { + bundleFiles = append(bundleFiles, path) + } + return nil + }) + if err != nil { + return fmt.Errorf("walking source FS: %w", err) + } + } else { + // we don't want to naively write the entire source FS to the tarball, + // since it could contain a lot of extraneous files. instead, run the + // applet and interrogate it for the files it needs to include in the + // bundle. + app, err := runtime.NewAppletFromFS(ab.Manifest.ID, ab.Source, runtime.WithPrintDisabled()) + if err != nil { + return fmt.Errorf("loading applet for bundling: %w", err) + } + bundleFiles = app.PathsForBundle() + } + + // Setup writers. + gzw := gzip.NewWriter(out) + defer gzw.Close() + + tw := tar.NewWriter(gzw) + defer tw.Close() + + // Write manifest. + buff := &bytes.Buffer{} + err := ab.Manifest.WriteManifest(buff) + if err != nil { + return fmt.Errorf("could not write manifest to buffer: %w", err) + } + b := buff.Bytes() + + hdr := &tar.Header{ + Name: manifest.ManifestFileName, + Mode: 0600, + Size: int64(len(b)), + } + err = tw.WriteHeader(hdr) + if err != nil { + return fmt.Errorf("could not write manifest header: %w", err) + } + _, err = tw.Write(b) + if err != nil { + return fmt.Errorf("could not write manifest to archive: %w", err) + } + + // write sources. + for _, path := range bundleFiles { + stat, err := fs.Stat(ab.Source, path) + if err != nil { + return fmt.Errorf("could not stat %s: %w", path, err) + } + + hdr, err := tar.FileInfoHeader(stat, "") + if err != nil { + return fmt.Errorf("creating header for %s: %w", path, err) + } + hdr.Name = filepath.ToSlash(path) + + err = tw.WriteHeader(hdr) + if err != nil { + return fmt.Errorf("writing header for %s: %w", path, err) + } + + if !stat.IsDir() { + file, err := ab.Source.Open(path) + if err != nil { + return fmt.Errorf("opening file %s: %w", path, err) + } + + written, err := io.Copy(tw, file) + if err != nil { + return fmt.Errorf("writing file %s: %w", path, err) + } else if written != stat.Size() { + return fmt.Errorf("did not write entire file %s: %w", path, err) + } + } + } + + return nil +} diff --git a/cmd/community/manifestprompt.go b/cmd/community/manifestprompt.go index b2ac75eb47..40c1c73e3c 100644 --- a/cmd/community/manifestprompt.go +++ b/cmd/community/manifestprompt.go @@ -51,12 +51,10 @@ func ManifestPrompt() (*manifest.Manifest, error) { } return &manifest.Manifest{ - ID: manifest.GenerateID(name), - Name: name, - Summary: summary, - Desc: desc, - Author: author, - FileName: manifest.GenerateFileName(name), - PackageName: manifest.GeneratePackageName(name), + ID: manifest.GenerateID(name), + Name: name, + Summary: summary, + Desc: desc, + Author: author, }, nil } diff --git a/cmd/community/validatemanifest.go b/cmd/community/validatemanifest.go index 35c0f90df3..ef475658a4 100644 --- a/cmd/community/validatemanifest.go +++ b/cmd/community/validatemanifest.go @@ -12,7 +12,6 @@ import ( var ValidateManifestAppFileName string func init() { - ValidateManifestCmd.Flags().StringVarP(&ValidateManifestAppFileName, "app-file-name", "a", "", "ensures the app file name is the same as the manifest") } var ValidateManifestCmd = &cobra.Command{ @@ -47,9 +46,5 @@ func ValidateManifest(cmd *cobra.Command, args []string) error { return fmt.Errorf("couldn't validate manifest: %w", err) } - if ValidateManifestAppFileName != "" && m.FileName != ValidateManifestAppFileName { - return fmt.Errorf("app name doesn't match: %s != %s", ValidateManifestAppFileName, m.FileName) - } - return nil } diff --git a/cmd/private/bundle.go b/cmd/private/bundle.go index 7bdc1c6338..634e35e747 100644 --- a/cmd/private/bundle.go +++ b/cmd/private/bundle.go @@ -42,7 +42,7 @@ be a gzip compressed tar file that can be uploaded to Tidbyt for deployment.`, return fmt.Errorf("output must be a directory") } - ab, err := bundle.InitFromPath(bundleInput) + ab, err := bundle.FromDir(bundleInput) if err != nil { return fmt.Errorf("could not init bundle: %w", err) } diff --git a/cmd/private/upload.go b/cmd/private/upload.go index 7a52df06a8..72f68cbc7d 100644 --- a/cmd/private/upload.go +++ b/cmd/private/upload.go @@ -63,7 +63,7 @@ flags.`, // Create bundle buf := &bytes.Buffer{} - ab, err := bundle.InitFromPath(uploadDir) + ab, err := bundle.FromDir(uploadDir) if err != nil { return fmt.Errorf("could not init bundle: %w", err) } diff --git a/cmd/render.go b/cmd/render.go index d5746ea5ec..d64327c78c 100644 --- a/cmd/render.go +++ b/cmd/render.go @@ -4,8 +4,11 @@ import ( "context" "fmt" "image" + "io/fs" "os" + "path/filepath" "strings" + "testing/fstest" "time" "github.com/spf13/cobra" @@ -68,23 +71,49 @@ func init() { } var RenderCmd = &cobra.Command{ - Use: "render [script] [=value>]...", - Short: "Run a Pixlet script with provided config parameters", + Use: "render [path] [=value>]...", + Short: "Run a Pixlet app with provided config parameters", Args: cobra.MinimumNArgs(1), RunE: render, + Long: `Render a Pixlet app with provided config parameters. + +The path argument should be the path to the Pixlet app to run. The +app can be a single file with the .star extension, or a directory +containing multiple Starlark files and resources. + `, } func render(cmd *cobra.Command, args []string) error { - script := args[0] + path := args[0] - globals.Width = width - globals.Height = height + // check if path exists, and whether it is a directory or a file + info, err := os.Stat(path) + if err != nil { + return fmt.Errorf("failed to stat %s: %w", path, err) + } + + var fs fs.FS + var outPath string + if info.IsDir() { + fs = os.DirFS(path) + outPath = filepath.Join(path, filepath.Base(path)) + } else { + if !strings.HasSuffix(path, ".star") { + return fmt.Errorf("script file must have suffix .star: %s", path) + } + + src, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to read file %s: %w", path, err) + } + + fs = fstest.MapFS{ + filepath.Base(path): {Data: src}, + } - if !strings.HasSuffix(script, ".star") { - return fmt.Errorf("script file must have suffix .star: %s", script) + outPath = strings.TrimSuffix(path, ".star") } - outPath := strings.TrimSuffix(script, ".star") if renderGif { outPath += ".gif" } else { @@ -94,6 +123,9 @@ func render(cmd *cobra.Command, args []string) error { outPath = output } + globals.Width = width + globals.Height = height + config := map[string]string{} for _, param := range args[1:] { split := strings.Split(param, "=") @@ -103,11 +135,6 @@ func render(cmd *cobra.Command, args []string) error { config[split[0]] = strings.Join(split[1:], "=") } - src, err := os.ReadFile(script) - if err != nil { - return fmt.Errorf("failed to read file %s: %w", script, err) - } - // Remove the print function from the starlark thread if the silent flag is // passed. var opts []runtime.AppletOption @@ -128,7 +155,7 @@ func render(cmd *cobra.Command, args []string) error { runtime.InitHTTP(cache) runtime.InitCache(cache) - applet, err := runtime.NewApplet(script, src, opts...) + applet, err := runtime.NewAppletFromFS(filepath.Base(path), fs, opts...) if err != nil { return fmt.Errorf("failed to load applet: %w", err) } diff --git a/cmd/serve.go b/cmd/serve.go index 10baa95127..b2bf0c71eb 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -15,16 +15,21 @@ var ( func init() { ServeCmd.Flags().StringVarP(&host, "host", "i", "127.0.0.1", "Host interface for serving rendered images") ServeCmd.Flags().IntVarP(&port, "port", "p", 8080, "Port for serving rendered images") - ServeCmd.Flags().BoolVarP(&watch, "watch", "w", true, "Reload scripts on change") + ServeCmd.Flags().BoolVarP(&watch, "watch", "w", true, "Reload scripts on change. Does not recurse sub-directories.") ServeCmd.Flags().IntVarP(&maxDuration, "max_duration", "d", 15000, "Maximum allowed animation duration (ms)") ServeCmd.Flags().IntVarP(&timeout, "timeout", "", 30000, "Timeout for execution (ms)") } var ServeCmd = &cobra.Command{ - Use: "serve [script]", + Use: "serve [path]", Short: "Serve a Pixlet app in a web server", Args: cobra.ExactArgs(1), RunE: serve, + Long: `Serve a Pixlet app in a web server. + +The path argument should be the path to the Pixlet program to run. The +program can be a single file with the .star extension, or a directory +containing multiple Starlark files and resources.`, } func serve(cmd *cobra.Command, args []string) error { diff --git a/docs/modules.md b/docs/modules.md index 90a80cef7f..9aad04657d 100644 --- a/docs/modules.md +++ b/docs/modules.md @@ -28,6 +28,7 @@ individual modules, please refer to the Starlib documentation. | Module | Description | | --- | --- | +| [`bsoup.star`](https://github.com/qri-io/starlib/blob/master/bsoup) | Beautiful Soup-like functions for HTML | | [`compress/gzip.star`](https://github.com/qri-io/starlib/blob/master/compress/gzip) | gzip decompressing | | [`compress/zipfile.star`](https://github.com/qri-io/starlib/blob/master/zipfile) | zip decompressing | | [`encoding/base64.star`](https://github.com/qri-io/starlib/tree/master/encoding/base64) | Base 64 encoding and decoding | @@ -111,7 +112,7 @@ The `humanize` module has formatters for units to human friendly sizes. Example: -See [examples/humanize.star](../examples/humanize.star) for an example. +See [examples/humanize/humanize.star](../examples/humanize/humanize.star) for an example. ## Pixlet module: XPath @@ -177,7 +178,7 @@ The schema module provides configuration options for your app. See the [schema d Example: -See [examples/schema_hello_world.star](../examples/schema_hello_world.star) for an example. +See [examples/schema_hello_world/schema_hello_world.star](../examples/schema_hello_world/schema_hello_world.star) for an example. ## Pixlet module: Secret @@ -210,7 +211,7 @@ The `sunrise` module calculates sunrise and sunset times for a given set of GPS Example: -See [examples/sunrise.star](../examples/sunrise.star) for an example. +See [examples/sunrise/sunrise.star](../examples/sunrise/sunrise.star) for an example. ## Pixlet module: Random diff --git a/docs/tutorial.md b/docs/tutorial.md index 4375942679..a8ce678e56 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -9,19 +9,19 @@ biased. You should be able to run the Pixlet CLI like so: -`$ pixlet render examples/clock.star` +`$ pixlet render examples/clock/clock.star` This should in turn run `clock.star` and produce a `clock.webp` file. ```console -$ file examples/clock.webp -examples/clock.webp: RIFF (little-endian) data, Web/P image +$ file examples/clock/clock.webp +examples/clock/clock.webp: RIFF (little-endian) data, Web/P image ``` For local development, its often convenient to run pixlet in "serve" mode: -`$ pixlet serve --watch examples/clock.star` +`$ pixlet serve --watch examples/clock/clock.star` Direct your web browser to http://localhost:8080, and your rendered app will appear. diff --git a/examples/bitcoin.star b/examples/bitcoin/bitcoin.star similarity index 67% rename from examples/bitcoin.star rename to examples/bitcoin/bitcoin.star index 19ea6748a1..f24e7ead42 100644 --- a/examples/bitcoin.star +++ b/examples/bitcoin/bitcoin.star @@ -1,15 +1,10 @@ -load("encoding/base64.star", "base64") load("http.star", "http") +load("icon.png", icon = "file") load("render.star", "render") COINDESK_PRICE_URL = "https://api.coindesk.com/v1/bpi/currentprice.json" -BTC_ICON = base64.decode(""" -iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAYAAAA7bUf6AAAAlklEQVQ4T2NkwAH+H2T/jy7FaP+ -TEZtyDEG4Zi0TTPXXzoDF0A1DMQRsADbN6MZdO4NiENwQbAbERh1lWLzMmgFGo5iFZBDYEFwuwG -sISCPUIKyGgDRjAyBXYXMNIz5XgDQga8TpLboYgux8DO/AwoUuLiEqTLBFMcmxQ7V0gssgklIsL -AYozjsoBoE45OZi5DRBSnkCAMLhlPBiQGHlAAAAAElFTkSuQmCC -""") +BTC_ICON = icon.readall() def main(): rep = http.get(COINDESK_PRICE_URL, ttl_seconds = 240) diff --git a/examples/bitcoin/icon.png b/examples/bitcoin/icon.png new file mode 100644 index 0000000000..8ffd207915 Binary files /dev/null and b/examples/bitcoin/icon.png differ diff --git a/examples/clock.star b/examples/clock/clock.star similarity index 100% rename from examples/clock.star rename to examples/clock/clock.star diff --git a/examples/font-preview.star b/examples/font-preview/font-preview.star similarity index 100% rename from examples/font-preview.star rename to examples/font-preview/font-preview.star diff --git a/examples/hello_world.star b/examples/hello_world/hello_world.star similarity index 100% rename from examples/hello_world.star rename to examples/hello_world/hello_world.star diff --git a/examples/humanize.star b/examples/humanize/humanize.star similarity index 100% rename from examples/humanize.star rename to examples/humanize/humanize.star diff --git a/examples/life/gosper_glider.txt b/examples/life/gosper_glider.txt new file mode 100644 index 0000000000..8ee81f9968 --- /dev/null +++ b/examples/life/gosper_glider.txt @@ -0,0 +1,42 @@ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 \ No newline at end of file diff --git a/examples/life.star b/examples/life/life.star similarity index 62% rename from examples/life.star rename to examples/life/life.star index d84274ad7b..0691ac0cfb 100644 --- a/examples/life.star +++ b/examples/life/life.star @@ -15,6 +15,7 @@ load("cache.star", "cache") load("encoding/base64.star", "base64") +load("gosper_glider.txt", gosper_glider = "file") load("render.star", "render") load("time.star", "time") @@ -476,5 +477,4 @@ def get_seeded_gosper_glider_board(): method and then base64 encoded. This was a trade off in repeatability with efficiency. I originally rendered this by using points mapped to create the Gosper Glider Gun and running through a few iterations until it looked nice. """ - encoded_board = "MCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwxLDEsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwxLDAsMCwxLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDEsMCwxLDAsMCwwLDAsMCwwLDAsMCwwLDEsMCwwLDAsMCwwLDAsMCwxLDEsMSwxLDEsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwxLDAsMCwwLDEsMCwwLDAsMCwwLDAsMCwxLDAsMCwwLDAsMCwwLDEsMCwwLDAsMCwwLDEsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwxLDAsMCwwLDAsMCwwLDAsMSwwLDAsMCwwLDAsMCwwLDEsMSwwLDAsMCwxLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwxLDEsMCwwLDAsMCwwLDAsMCwwLDEsMCwwLDAsMCwxLDAsMCwwLDAsMCwwLDAsMSwwLDAsMSwwLDAsMCwwLDAsMCwwLDEsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMSwxLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDEsMCwwLDAsMCwwLDAsMCwwLDAsMCwxLDEsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMSwwLDAsMCwxLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDEsMCwxLDAsMCwwLDAsMCwwLDAsMCwxLDAsMSwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwxLDEsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMSwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDEsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDEsMSwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMSwxLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwxLDAsMSwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwxLDEsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMSwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAKMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwCjAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAowLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDA=" - return decode(base64.decode(encoded_board)) + return decode(gosper_glider.readall()) diff --git a/examples/qrcode.star b/examples/qrcode/qrcode.star similarity index 100% rename from examples/qrcode.star rename to examples/qrcode/qrcode.star diff --git a/examples/quadrants.star b/examples/quadrants/quadrants.star similarity index 100% rename from examples/quadrants.star rename to examples/quadrants/quadrants.star diff --git a/examples/schema_hello_world.star b/examples/schema_hello_world/schema_hello_world.star similarity index 100% rename from examples/schema_hello_world.star rename to examples/schema_hello_world/schema_hello_world.star diff --git a/examples/sunrise.star b/examples/sunrise/sunrise.star similarity index 100% rename from examples/sunrise.star rename to examples/sunrise/sunrise.star diff --git a/go.mod b/go.mod index ef7399c541..6ccccc8e20 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.22.2 require ( github.com/Code-Hex/Neo-cowsay/v2 v2.0.4 - github.com/antchfx/xmlquery v1.3.18 + github.com/antchfx/xmlquery v1.4.0 github.com/bazelbuild/buildtools v0.0.0-20230425225026-3dcc8d67e8ea github.com/client9/misspell v0.3.4 github.com/dustin/go-humanize v1.0.1 @@ -14,9 +14,9 @@ require ( github.com/fatih/color v1.16.0 github.com/fsnotify/fsnotify v1.7.0 github.com/gitsight/go-vcsurl v1.0.1 - github.com/go-git/go-git/v5 v5.11.0 + github.com/go-git/go-git/v5 v5.12.0 github.com/go-playground/validator/v10 v10.15.1 - github.com/google/pprof v0.0.0-20240416155748-26353dc0451f + github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 github.com/google/tink/go v1.7.0 github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.1 @@ -26,6 +26,7 @@ require ( github.com/newm4n/go-dfe v0.0.0-20210113055126-9d5f01722db9 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20220510032225-4f9f17eaec4c + github.com/nlepage/go-tarfs v1.2.1 github.com/nlepage/go-wasm-http-server v1.1.0 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/qri-io/starlib v0.5.1-0.20220611014110-7fb7ff9ec804 @@ -41,7 +42,7 @@ require ( github.com/zachomedia/go-bdf v0.0.0-20220611021443-a3af701111be go.starlark.net v0.0.0-20240411212711-9b43f0afd521 golang.org/x/image v0.15.0 - golang.org/x/oauth2 v0.18.0 + golang.org/x/oauth2 v0.19.0 golang.org/x/sync v0.7.0 golang.org/x/text v0.14.0 gopkg.in/yaml.v3 v3.0.1 @@ -51,14 +52,15 @@ require ( dario.cat/mergo v1.0.0 // indirect github.com/Code-Hex/go-wordwrap v1.0.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect + github.com/ProtonMail/go-crypto v1.0.0 // indirect github.com/PuerkitoBio/goquery v1.5.1 // indirect github.com/andybalholm/cascadia v1.1.0 // indirect - github.com/antchfx/xpath v1.2.4 // indirect + github.com/antchfx/xpath v1.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect - github.com/cloudflare/circl v1.3.3 // indirect + github.com/cloudflare/circl v1.3.7 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect @@ -87,8 +89,8 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/sergi/go-diff v1.1.0 // indirect - github.com/skeema/knownhosts v1.2.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.2.2 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect @@ -100,11 +102,10 @@ require ( go.uber.org/multierr v1.9.0 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/mod v0.12.0 // indirect + golang.org/x/mod v0.16.0 // indirect golang.org/x/net v0.22.0 // indirect golang.org/x/sys v0.18.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/appengine v1.6.7 // indirect + golang.org/x/tools v0.19.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index 195d01f9c5..5db58c01df 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,8 @@ github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -21,10 +21,10 @@ github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5z github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/antchfx/xmlquery v1.3.18 h1:FSQ3wMuphnPPGJOFhvc+cRQ2CT/rUj4cyQXkJcjOwz0= -github.com/antchfx/xmlquery v1.3.18/go.mod h1:Afkq4JIeXut75taLSuI31ISJ/zeq+3jG7TunF7noreA= -github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY= -github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= +github.com/antchfx/xmlquery v1.4.0 h1:xg2HkfcRK2TeTbdb0m1jxCYnvsPaGY/oeZWTGqX/0hA= +github.com/antchfx/xmlquery v1.4.0/go.mod h1:Ax2aeaeDjfIw3CwXKDQ0GkwZ6QlxoChlIBP+mGnDFjI= +github.com/antchfx/xpath v1.3.0 h1:nTMlzGAK3IJ0bPpME2urTuFL76o4A96iYvoKFHRXJgc= +github.com/antchfx/xpath v1.3.0/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= @@ -53,8 +53,9 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -72,6 +73,7 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e h1:44fmjqDtdCiUNlSjJVp+w1AOs6na3Y6Ai0aIeseFjkI= github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e/go.mod h1:CgNC6SGbT+Xb8wGGvzilttZL1mc5sQ/5KkcxsZttMIk= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= @@ -93,16 +95,16 @@ github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gitsight/go-vcsurl v1.0.1 h1:wkijKsbVg9R2IBP97U7wOANeIW9WJJKkBwS9XqllzWo= github.com/gitsight/go-vcsurl v1.0.1/go.mod h1:qRFdKDa/0Lh9MT0xE+qQBYZ/01+mY1H40rZUHR24X9U= -github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= +github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= +github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= -github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -155,10 +157,10 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f h1:f00RU+zOX+B3rLAmMMkzHUF2h1z4DeYR9tTCvEq2REY= -github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/pprof v0.0.0-20240416155748-26353dc0451f h1:WpZiq8iqvGjJ3m3wzAVKL6+0vz7VkE79iSy9GII00II= github.com/google/pprof v0.0.0-20240416155748-26353dc0451f/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w= github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -233,6 +235,8 @@ github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20220510032225-4f9f17eaec4 github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20220510032225-4f9f17eaec4c/go.mod h1:DvuJJ/w1Y59rG8UTDxsMk5U+UJXJwuvUgbiJSm9yhX8= github.com/nlepage/go-js-promise v1.0.0 h1:K7OmJ3+0BgWJ2LfXchg2sI6RDr7AW/KWR8182epFwGQ= github.com/nlepage/go-js-promise v1.0.0/go.mod h1:bdOP0wObXu34euibyK39K1hoBCtlgTKXGc56AGflaRo= +github.com/nlepage/go-tarfs v1.2.1 h1:o37+JPA+ajllGKSPfy5+YpsNHDjZnAoyfvf5GsUa+Ks= +github.com/nlepage/go-tarfs v1.2.1/go.mod h1:rno18mpMy9aEH1IiJVftFsqPyIpwqSUiAOpJYjlV2NA= github.com/nlepage/go-wasm-http-server v1.1.0 h1:phw2NtSp71m/6NmGjE2veQ41PBPzWFcnE614cKucy5M= github.com/nlepage/go-wasm-http-server v1.1.0/go.mod h1:xpffUeN97vuv8CTlMJ2oC5tPsftfPoG9HkAgI9gkiPI= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -277,14 +281,14 @@ github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgY github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= -github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= +github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -348,8 +352,6 @@ github.com/zachomedia/go-bdf v0.0.0-20220611021443-a3af701111be/go.mod h1:FWqHpm go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.starlark.net v0.0.0-20210223155950-e043a3d3c984/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= go.starlark.net v0.0.0-20210602144842-1cdb82c9e17a/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= -go.starlark.net v0.0.0-20240329153429-e6e8e7ce1b7a h1:Oe+v9w90BBIxQZ4U39+axR8KxrBbxqnRudPPcBIlP3o= -go.starlark.net v0.0.0-20240329153429-e6e8e7ce1b7a/go.mod h1:YKMCv9b1WrfWmeqdV5MAuEHWsu5iC+fe6kYl2sQjdI8= go.starlark.net v0.0.0-20240411212711-9b43f0afd521 h1:1Ufp2S2fPpj0RHIQ4rbzpCdPLCPkzdK7BaVFH3nkYBQ= go.starlark.net v0.0.0-20240411212711-9b43f0afd521/go.mod h1:YKMCv9b1WrfWmeqdV5MAuEHWsu5iC+fe6kYl2sQjdI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -378,8 +380,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -388,7 +390,6 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -402,8 +403,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= +golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -447,7 +448,6 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -467,15 +467,13 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= @@ -512,8 +510,8 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index f2f64cfbc0..153d62edba 100644 --- a/main.go +++ b/main.go @@ -18,7 +18,6 @@ var ( ) func init() { - rootCmd.AddCommand(cmd.ServeCmd) rootCmd.AddCommand(cmd.RenderCmd) rootCmd.AddCommand(cmd.PushCmd) rootCmd.AddCommand(cmd.EncryptCmd) diff --git a/main_nonjs.go b/main_nonjs.go index 2f82271f3a..5125a866ac 100644 --- a/main_nonjs.go +++ b/main_nonjs.go @@ -10,4 +10,5 @@ import ( func init() { rootCmd.AddCommand(private.PrivateCmd) rootCmd.AddCommand(cmd.CreateCmd) + rootCmd.AddCommand(cmd.ServeCmd) } diff --git a/manifest/manifest.go b/manifest/manifest.go index bfa0281318..9b32885aa3 100644 --- a/manifest/manifest.go +++ b/manifest/manifest.go @@ -16,21 +16,22 @@ type Manifest struct { // ID is the unique identifier of this app. It has to be globally unique, // which means it cannot conflict with any of our private apps. ID string `json:"id" yaml:"id"` + // Name is the name of the applet. Ex. "Fuzzy Clock" Name string `json:"name" yaml:"name"` + // Summary is the short form of what this applet does. Ex. "Human readable // time". Summary string `json:"summary" yaml:"summary"` + // Desc is the long form of what this applet does. Ex. "Display the time in // a groovy, human-readable way." Desc string `json:"desc" yaml:"desc"` + // Author is the person or organization who contributed this applet. Ex, // "Max Timkovich" Author string `json:"author" yaml:"author"` - // FileName is the name of the starlark source file. - FileName string `json:"fileName" yaml:"fileName"` - // PackageName is the name of the go package where this app lives. - PackageName string `json:"packageName" yaml:"packageName"` + // Source is the starlark source code for this applet using the go `embed` // module. Source []byte `json:"-" yaml:"-"` @@ -100,24 +101,14 @@ func (m Manifest) Validate() error { return err } - err = ValidateFileName(m.FileName) - if err != nil { - return err - } - - err = ValidatePackageName(m.PackageName) - if err != nil { - return err - } - return nil } -// GeneratePackageName creates a suitable go package name from an app name. -func GeneratePackageName(name string) string { - packageName := strings.ReplaceAll(name, "-", "") - packageName = strings.ReplaceAll(packageName, "_", "") - return strings.ToLower(strings.Join(strings.Fields(packageName), "")) +// GenerateDirName creates a suitable directory name from an app name. +func GenerateDirName(name string) string { + dir := strings.ReplaceAll(name, "-", "") + dir = strings.ReplaceAll(dir, "_", "") + return strings.ToLower(strings.Join(strings.Fields(dir), "")) } // GenerateID creates a suitable ID from an app name. diff --git a/manifest/manifest_test.go b/manifest/manifest_test.go index 57d8429d91..a29101046b 100644 --- a/manifest/manifest_test.go +++ b/manifest/manifest_test.go @@ -21,20 +21,16 @@ name: Foo Tracker summary: Track realtime foo desc: The foo tracker provides realtime feeds for foo. author: Tidbyt -fileName: foo_tracker.star -packageName: footracker ` func TestManifest(t *testing.T) { m := manifest.Manifest{ - ID: "foo-tracker", - Name: "Foo Tracker", - Summary: "Track realtime foo", - Desc: "The foo tracker provides realtime feeds for foo.", - Author: "Tidbyt", - FileName: "foo_tracker.star", - PackageName: "footracker", - Source: source, + ID: "foo-tracker", + Name: "Foo Tracker", + Summary: "Track realtime foo", + Desc: "The foo tracker provides realtime feeds for foo.", + Author: "Tidbyt", + Source: source, } expected, err := os.ReadFile("testdata/source.star") @@ -56,20 +52,16 @@ func TestLoadManifest(t *testing.T) { assert.Equal(t, m.Author, "Max Timkovich") assert.Equal(t, m.Summary, "Human readable time") assert.Equal(t, m.Desc, "Display the time in a groovy, human-readable way.") - assert.Equal(t, m.FileName, "fuzzy_clock.star") - assert.Equal(t, m.PackageName, "fuzzyclock") } func TestWriteManifest(t *testing.T) { m := manifest.Manifest{ - ID: "foo-tracker", - Name: "Foo Tracker", - Summary: "Track realtime foo", - Desc: "The foo tracker provides realtime feeds for foo.", - Author: "Tidbyt", - FileName: "foo_tracker.star", - PackageName: "footracker", - Source: source, + ID: "foo-tracker", + Name: "Foo Tracker", + Summary: "Track realtime foo", + Desc: "The foo tracker provides realtime feeds for foo.", + Author: "Tidbyt", + Source: source, } buff := bytes.Buffer{} @@ -96,7 +88,7 @@ func TestGeneratePackageName(t *testing.T) { } for _, tc := range tests { - got := manifest.GeneratePackageName(tc.input) + got := manifest.GenerateDirName(tc.input) assert.Equal(t, tc.want, got) } } diff --git a/manifest/testdata/manifest.yaml b/manifest/testdata/manifest.yaml index 7f3254a77d..6e2f0e2f0c 100644 --- a/manifest/testdata/manifest.yaml +++ b/manifest/testdata/manifest.yaml @@ -4,5 +4,3 @@ name: Fuzzy Clock summary: Human readable time desc: Display the time in a groovy, human-readable way. author: Max Timkovich -fileName: fuzzy_clock.star -packageName: fuzzyclock diff --git a/manifest/validate.go b/manifest/validate.go index 27f2e3650c..fddc036b0d 100644 --- a/manifest/validate.go +++ b/manifest/validate.go @@ -119,49 +119,6 @@ func ValidateAuthor(author string) error { return nil } -func ValidatePackageName(packageName string) error { - if packageName == "" { - return fmt.Errorf("package names cannot be empty") - } - - if packageName != strings.ToLower(packageName) { - return fmt.Errorf("package names should be lower case") - } - - for _, r := range packageName { - if !(unicode.IsLetter(r) || unicode.IsNumber(r)) { - return fmt.Errorf("package names can only contain letters, numbers, or an underscore character") - } - } - return nil -} - -// ValidateFileName ensures the file name appears appropriately for starlark -// source code. -func ValidateFileName(fileName string) error { - if fileName == "" { - return fmt.Errorf("fileName cannot be empty") - } - - if !strings.HasSuffix(fileName, ".star") { - return fmt.Errorf("file names should end in .star: '%s'", fileName) - } - - testName := strings.TrimSuffix(fileName, ".star") - - if testName != strings.ToLower(testName) { - return fmt.Errorf("file names should be lower case") - } - - for _, r := range testName { - if !(unicode.IsLetter(r) || unicode.IsNumber(r) || r == underscore) { - return fmt.Errorf("file names can only contain letters, numbers, or an underscore character") - } - } - - return nil -} - // ValidateID ensures the id will parse when we go to add it to our database // internally. func ValidateID(id string) error { diff --git a/manifest/validate_test.go b/manifest/validate_test.go index f960d4dfa5..e5d5f2648a 100644 --- a/manifest/validate_test.go +++ b/manifest/validate_test.go @@ -111,55 +111,3 @@ func TestValidateID(t *testing.T) { } } - -func TestValidateFileName(t *testing.T) { - type test struct { - input string - shouldErr bool - } - - tests := []test{ - {input: "foo_bar.star", shouldErr: false}, - {input: "foo_bar", shouldErr: true}, - {input: "FooBar.star", shouldErr: true}, - {input: "foo$.star", shouldErr: true}, - {input: "", shouldErr: true}, - } - - for _, tc := range tests { - err := manifest.ValidateFileName(tc.input) - - if tc.shouldErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - } - -} - -func TestValidatePackageName(t *testing.T) { - type test struct { - input string - shouldErr bool - } - - tests := []test{ - {input: "foobar", shouldErr: false}, - {input: "foo_bar", shouldErr: true}, - {input: "FooBar", shouldErr: true}, - {input: "foo$", shouldErr: true}, - {input: "", shouldErr: true}, - } - - for _, tc := range tests { - err := manifest.ValidatePackageName(tc.input) - - if tc.shouldErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - } - -} diff --git a/package-lock.json b/package-lock.json index 8cd47fe572..0606583615 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,13 +13,13 @@ "@emotion/styled": "11.11.5", "@fontsource/barlow": "4.5.9", "@fontsource/material-icons": "4.5.4", - "@fortawesome/fontawesome-svg-core": "6.5.1", - "@fortawesome/free-brands-svg-icons": "6.5.1", - "@fortawesome/free-solid-svg-icons": "6.5.1", + "@fortawesome/fontawesome-svg-core": "6.5.2", + "@fortawesome/free-brands-svg-icons": "6.5.2", + "@fortawesome/free-solid-svg-icons": "6.5.2", "@fortawesome/react-fontawesome": "0.2.0", - "@mui/icons-material": "5.15.14", - "@mui/lab": "5.0.0-alpha.169", - "@mui/material": "5.15.14", + "@mui/icons-material": "5.15.15", + "@mui/lab": "5.0.0-alpha.170", + "@mui/material": "5.15.15", "@mui/x-date-pickers": "5.0.20", "@reduxjs/toolkit": "1.9.7", "axios": "1.6.8", @@ -33,8 +33,8 @@ "react-dom": "17.0.2", "react-easy-crop": "4.7.4", "react-redux": "8.1.3", - "react-router": "6.22.3", - "react-router-dom": "6.22.3", + "react-router": "6.23.0", + "react-router-dom": "6.23.0", "react-simple-oauth2-login": "0.5.4" }, "devDependencies": { @@ -44,7 +44,7 @@ "@svgr/webpack": "6.5.1", "babel-loader": "9.1.3", "babel-preset-react": "6.24.1", - "css-loader": "6.10.0", + "css-loader": "6.11.0", "file-loader": "6.2.0", "html-webpack-plugin": "5.6.0", "style-loader": "3.3.4", @@ -2102,45 +2102,45 @@ "integrity": "sha512-YGmXkkEdu6EIgpFKNmB/nIXzZocwSmbI01Ninpmml8x8BT0M6RR++V1KqOfpzZ6Cw/FQ2/KYonQ3x4IY/4VRRA==" }, "node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz", - "integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz", + "integrity": "sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==", "hasInstallScript": true, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz", - "integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.2.tgz", + "integrity": "sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==", "hasInstallScript": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "@fortawesome/fontawesome-common-types": "6.5.2" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-brands-svg-icons": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.1.tgz", - "integrity": "sha512-093l7DAkx0aEtBq66Sf19MgoZewv1zeY9/4C7vSKPO4qMwEsW/2VYTUTpBtLwfb9T2R73tXaRDPmE4UqLCYHfg==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.2.tgz", + "integrity": "sha512-zi5FNYdmKLnEc0jc0uuHH17kz/hfYTg4Uei0wMGzcoCL/4d3WM3u1VMc0iGGa31HuhV5i7ZK8ZlTCQrHqRHSGQ==", "hasInstallScript": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "@fortawesome/fontawesome-common-types": "6.5.2" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz", - "integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz", + "integrity": "sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==", "hasInstallScript": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "@fortawesome/fontawesome-common-types": "6.5.2" }, "engines": { "node": ">=6" @@ -2415,18 +2415,18 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.14.tgz", - "integrity": "sha512-on75VMd0XqZfaQW+9pGjSNiqW+ghc5E2ZSLRBXwcXl/C4YzjfyjrLPhrEpKnR9Uym9KXBvxrhoHfPcczYHweyA==", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.15.tgz", + "integrity": "sha512-aXnw29OWQ6I5A47iuWEI6qSSUfH6G/aCsW9KmW3LiFqr7uXZBK4Ks+z8G+qeIub8k0T5CMqlT2q0L+ZJTMrqpg==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" } }, "node_modules/@mui/icons-material": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.14.tgz", - "integrity": "sha512-vj/51k7MdFmt+XVw94sl30SCvGx6+wJLsNYjZRgxhS6y3UtnWnypMOsm3Kmg8TN+P0dqwsjy4/fX7B1HufJIhw==", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.15.tgz", + "integrity": "sha512-kkeU/pe+hABcYDH6Uqy8RmIsr2S/y5bP2rp+Gat4CcRjCcVne6KudS1NrZQhUCRysrTDCAhcbcf9gt+/+pGO2g==", "dependencies": { "@babel/runtime": "^7.23.9" }, @@ -2449,13 +2449,13 @@ } }, "node_modules/@mui/lab": { - "version": "5.0.0-alpha.169", - "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.169.tgz", - "integrity": "sha512-h6xe1K6ISKUbyxTDgdvql4qoDP6+q8ad5fg9nXQxGLUrIeT2jVrBuT/jRECSTufbnhzP+V5kulvYxaMfM8rEdA==", + "version": "5.0.0-alpha.170", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.170.tgz", + "integrity": "sha512-0bDVECGmrNjd3+bLdcLiwYZ0O4HP5j5WSQm5DV6iA/Z9kr8O6AnvZ1bv9ImQbbX7Gj3pX4o43EKwCutj3EQxQg==", "dependencies": { "@babel/runtime": "^7.23.9", "@mui/base": "5.0.0-beta.40", - "@mui/system": "^5.15.14", + "@mui/system": "^5.15.15", "@mui/types": "^7.2.14", "@mui/utils": "^5.15.14", "clsx": "^2.1.0", @@ -2497,14 +2497,14 @@ } }, "node_modules/@mui/material": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.14.tgz", - "integrity": "sha512-kEbRw6fASdQ1SQ7LVdWR5OlWV3y7Y54ZxkLzd6LV5tmz+NpO3MJKZXSfgR0LHMP7meKsPiMm4AuzV0pXDpk/BQ==", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.15.tgz", + "integrity": "sha512-3zvWayJ+E1kzoIsvwyEvkTUKVKt1AjchFFns+JtluHCuvxgKcLSRJTADw37k0doaRtVAsyh8bz9Afqzv+KYrIA==", "dependencies": { "@babel/runtime": "^7.23.9", "@mui/base": "5.0.0-beta.40", - "@mui/core-downloads-tracker": "^5.15.14", - "@mui/system": "^5.15.14", + "@mui/core-downloads-tracker": "^5.15.15", + "@mui/system": "^5.15.15", "@mui/types": "^7.2.14", "@mui/utils": "^5.15.14", "@types/react-transition-group": "^4.4.10", @@ -2611,9 +2611,9 @@ } }, "node_modules/@mui/system": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.14.tgz", - "integrity": "sha512-auXLXzUaCSSOLqJXmsAaq7P96VPRXg2Rrz6OHNV7lr+kB8lobUF+/N84Vd9C4G/wvCXYPs5TYuuGBRhcGbiBGg==", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.15.tgz", + "integrity": "sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==", "dependencies": { "@babel/runtime": "^7.23.9", "@mui/private-theming": "^5.15.14", @@ -2825,9 +2825,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", - "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.0.tgz", + "integrity": "sha512-Quz1KOffeEf/zwkCBM3kBtH4ZoZ+pT3xIXBG4PPW/XFtDP7EGhtTiC2+gpL9GnR7+Qdet5Oa6cYSvwKYg6kN9Q==", "engines": { "node": ">=14.0.0" } @@ -4475,16 +4475,16 @@ } }, "node_modules/css-loader": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz", - "integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.4", - "postcss-modules-scope": "^3.1.1", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", "semver": "^7.5.4" @@ -6945,9 +6945,9 @@ } }, "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, "engines": { "node": "^10 || ^12 || >= 14" @@ -6957,9 +6957,9 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz", - "integrity": "sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", "dev": true, "dependencies": { "icss-utils": "^5.0.0", @@ -6974,9 +6974,9 @@ } }, "node_modules/postcss-modules-scope": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz", - "integrity": "sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.4" @@ -7247,11 +7247,11 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/react-router": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", - "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.0.tgz", + "integrity": "sha512-wPMZ8S2TuPadH0sF5irFGjkNLIcRvOSaEe7v+JER8508dyJumm6XZB1u5kztlX0RVq6AzRVndzqcUh6sFIauzA==", "dependencies": { - "@remix-run/router": "1.15.3" + "@remix-run/router": "1.16.0" }, "engines": { "node": ">=14.0.0" @@ -7261,12 +7261,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", - "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.0.tgz", + "integrity": "sha512-Q9YaSYvubwgbal2c9DJKfx6hTNoBp3iJDsl+Duva/DwxoJH+OTXkxGpql4iUK2sla/8z4RpjAm6EWx1qUDuopQ==", "dependencies": { - "@remix-run/router": "1.15.3", - "react-router": "6.22.3" + "@remix-run/router": "1.16.0", + "react-router": "6.23.0" }, "engines": { "node": ">=14.0.0" @@ -10117,32 +10117,32 @@ "integrity": "sha512-YGmXkkEdu6EIgpFKNmB/nIXzZocwSmbI01Ninpmml8x8BT0M6RR++V1KqOfpzZ6Cw/FQ2/KYonQ3x4IY/4VRRA==" }, "@fortawesome/fontawesome-common-types": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz", - "integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==" + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz", + "integrity": "sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==" }, "@fortawesome/fontawesome-svg-core": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz", - "integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.2.tgz", + "integrity": "sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==", "requires": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "@fortawesome/fontawesome-common-types": "6.5.2" } }, "@fortawesome/free-brands-svg-icons": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.1.tgz", - "integrity": "sha512-093l7DAkx0aEtBq66Sf19MgoZewv1zeY9/4C7vSKPO4qMwEsW/2VYTUTpBtLwfb9T2R73tXaRDPmE4UqLCYHfg==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.2.tgz", + "integrity": "sha512-zi5FNYdmKLnEc0jc0uuHH17kz/hfYTg4Uei0wMGzcoCL/4d3WM3u1VMc0iGGa31HuhV5i7ZK8ZlTCQrHqRHSGQ==", "requires": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "@fortawesome/fontawesome-common-types": "6.5.2" } }, "@fortawesome/free-solid-svg-icons": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz", - "integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz", + "integrity": "sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==", "requires": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "@fortawesome/fontawesome-common-types": "6.5.2" } }, "@fortawesome/react-fontawesome": { @@ -10320,26 +10320,26 @@ } }, "@mui/core-downloads-tracker": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.14.tgz", - "integrity": "sha512-on75VMd0XqZfaQW+9pGjSNiqW+ghc5E2ZSLRBXwcXl/C4YzjfyjrLPhrEpKnR9Uym9KXBvxrhoHfPcczYHweyA==" + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.15.tgz", + "integrity": "sha512-aXnw29OWQ6I5A47iuWEI6qSSUfH6G/aCsW9KmW3LiFqr7uXZBK4Ks+z8G+qeIub8k0T5CMqlT2q0L+ZJTMrqpg==" }, "@mui/icons-material": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.14.tgz", - "integrity": "sha512-vj/51k7MdFmt+XVw94sl30SCvGx6+wJLsNYjZRgxhS6y3UtnWnypMOsm3Kmg8TN+P0dqwsjy4/fX7B1HufJIhw==", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.15.tgz", + "integrity": "sha512-kkeU/pe+hABcYDH6Uqy8RmIsr2S/y5bP2rp+Gat4CcRjCcVne6KudS1NrZQhUCRysrTDCAhcbcf9gt+/+pGO2g==", "requires": { "@babel/runtime": "^7.23.9" } }, "@mui/lab": { - "version": "5.0.0-alpha.169", - "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.169.tgz", - "integrity": "sha512-h6xe1K6ISKUbyxTDgdvql4qoDP6+q8ad5fg9nXQxGLUrIeT2jVrBuT/jRECSTufbnhzP+V5kulvYxaMfM8rEdA==", + "version": "5.0.0-alpha.170", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.170.tgz", + "integrity": "sha512-0bDVECGmrNjd3+bLdcLiwYZ0O4HP5j5WSQm5DV6iA/Z9kr8O6AnvZ1bv9ImQbbX7Gj3pX4o43EKwCutj3EQxQg==", "requires": { "@babel/runtime": "^7.23.9", "@mui/base": "5.0.0-beta.40", - "@mui/system": "^5.15.14", + "@mui/system": "^5.15.15", "@mui/types": "^7.2.14", "@mui/utils": "^5.15.14", "clsx": "^2.1.0", @@ -10354,14 +10354,14 @@ } }, "@mui/material": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.14.tgz", - "integrity": "sha512-kEbRw6fASdQ1SQ7LVdWR5OlWV3y7Y54ZxkLzd6LV5tmz+NpO3MJKZXSfgR0LHMP7meKsPiMm4AuzV0pXDpk/BQ==", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.15.tgz", + "integrity": "sha512-3zvWayJ+E1kzoIsvwyEvkTUKVKt1AjchFFns+JtluHCuvxgKcLSRJTADw37k0doaRtVAsyh8bz9Afqzv+KYrIA==", "requires": { "@babel/runtime": "^7.23.9", "@mui/base": "5.0.0-beta.40", - "@mui/core-downloads-tracker": "^5.15.14", - "@mui/system": "^5.15.14", + "@mui/core-downloads-tracker": "^5.15.15", + "@mui/system": "^5.15.15", "@mui/types": "^7.2.14", "@mui/utils": "^5.15.14", "@types/react-transition-group": "^4.4.10", @@ -10406,9 +10406,9 @@ } }, "@mui/system": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.14.tgz", - "integrity": "sha512-auXLXzUaCSSOLqJXmsAaq7P96VPRXg2Rrz6OHNV7lr+kB8lobUF+/N84Vd9C4G/wvCXYPs5TYuuGBRhcGbiBGg==", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.15.tgz", + "integrity": "sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==", "requires": { "@babel/runtime": "^7.23.9", "@mui/private-theming": "^5.15.14", @@ -10510,9 +10510,9 @@ } }, "@remix-run/router": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", - "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==" + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.0.tgz", + "integrity": "sha512-Quz1KOffeEf/zwkCBM3kBtH4ZoZ+pT3xIXBG4PPW/XFtDP7EGhtTiC2+gpL9GnR7+Qdet5Oa6cYSvwKYg6kN9Q==" }, "@svgr/babel-plugin-add-jsx-attribute": { "version": "6.5.1", @@ -11794,16 +11794,16 @@ } }, "css-loader": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz", - "integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", "dev": true, "requires": { "icss-utils": "^5.1.0", "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.4", - "postcss-modules-scope": "^3.1.1", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", "semver": "^7.5.4" @@ -13590,16 +13590,16 @@ } }, "postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, "requires": {} }, "postcss-modules-local-by-default": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz", - "integrity": "sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", "dev": true, "requires": { "icss-utils": "^5.0.0", @@ -13608,9 +13608,9 @@ } }, "postcss-modules-scope": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz", - "integrity": "sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", "dev": true, "requires": { "postcss-selector-parser": "^6.0.4" @@ -13801,20 +13801,20 @@ } }, "react-router": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", - "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.0.tgz", + "integrity": "sha512-wPMZ8S2TuPadH0sF5irFGjkNLIcRvOSaEe7v+JER8508dyJumm6XZB1u5kztlX0RVq6AzRVndzqcUh6sFIauzA==", "requires": { - "@remix-run/router": "1.15.3" + "@remix-run/router": "1.16.0" } }, "react-router-dom": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", - "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.0.tgz", + "integrity": "sha512-Q9YaSYvubwgbal2c9DJKfx6hTNoBp3iJDsl+Duva/DwxoJH+OTXkxGpql4iUK2sla/8z4RpjAm6EWx1qUDuopQ==", "requires": { - "@remix-run/router": "1.15.3", - "react-router": "6.22.3" + "@remix-run/router": "1.16.0", + "react-router": "6.23.0" } }, "react-simple-oauth2-login": { diff --git a/package.json b/package.json index f352a2e312..58ab54e08a 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "@svgr/webpack": "6.5.1", "babel-loader": "9.1.3", "babel-preset-react": "6.24.1", - "css-loader": "6.10.0", + "css-loader": "6.11.0", "file-loader": "6.2.0", "html-webpack-plugin": "5.6.0", "style-loader": "3.3.4", @@ -47,13 +47,13 @@ "@emotion/styled": "11.11.5", "@fontsource/barlow": "4.5.9", "@fontsource/material-icons": "4.5.4", - "@fortawesome/fontawesome-svg-core": "6.5.1", - "@fortawesome/free-brands-svg-icons": "6.5.1", - "@fortawesome/free-solid-svg-icons": "6.5.1", + "@fortawesome/fontawesome-svg-core": "6.5.2", + "@fortawesome/free-brands-svg-icons": "6.5.2", + "@fortawesome/free-solid-svg-icons": "6.5.2", "@fortawesome/react-fontawesome": "0.2.0", - "@mui/icons-material": "5.15.14", - "@mui/lab": "5.0.0-alpha.169", - "@mui/material": "5.15.14", + "@mui/icons-material": "5.15.15", + "@mui/lab": "5.0.0-alpha.170", + "@mui/material": "5.15.15", "@mui/x-date-pickers": "5.0.20", "@reduxjs/toolkit": "1.9.7", "axios": "1.6.8", @@ -67,8 +67,8 @@ "react-dom": "17.0.2", "react-easy-crop": "4.7.4", "react-redux": "8.1.3", - "react-router": "6.22.3", - "react-router-dom": "6.22.3", + "react-router": "6.23.0", + "react-router-dom": "6.23.0", "react-simple-oauth2-login": "0.5.4" } } diff --git a/runtime/applet.go b/runtime/applet.go index fc8d731cea..2bac67fe01 100644 --- a/runtime/applet.go +++ b/runtime/applet.go @@ -2,14 +2,18 @@ package runtime import ( "context" - "crypto/md5" "encoding/json" "fmt" + "io/fs" + "path" + "slices" "strings" "testing" + "testing/fstest" starlibgzip "github.com/qri-io/starlib/compress/gzip" starlibbase64 "github.com/qri-io/starlib/encoding/base64" + starlibbsoup "github.com/qri-io/starlib/bsoup" starlibcsv "github.com/qri-io/starlib/encoding/csv" starlibhash "github.com/qri-io/starlib/hash" starlibhtml "github.com/qri-io/starlib/html" @@ -51,13 +55,16 @@ type ThreadInitializer func(thread *starlark.Thread) *starlark.Thread type Applet struct { ID string - src []byte loader ModuleLoader initializers []ThreadInitializer + loadedPaths map[string]bool - globals starlark.StringDict - main *starlark.Function + globals map[string]starlark.StringDict + mainFile string + mainFun *starlark.Function + + schemaFile string schema *schema.Schema schemaJSON []byte } @@ -69,13 +76,6 @@ func WithModuleLoader(loader ModuleLoader) AppletOption { } } -func WithThreadInitializer(initializer ThreadInitializer) AppletOption { - return func(a *Applet) error { - a.initializers = append(a.initializers, initializer) - return nil - } -} - func WithSecretDecryptionKey(key *SecretDecryptionKey) AppletOption { return func(a *Applet) error { if decrypter, err := key.decrypterForApp(a); err != nil { @@ -105,9 +105,25 @@ func WithPrintDisabled() AppletOption { } func NewApplet(id string, src []byte, opts ...AppletOption) (*Applet, error) { + fn := id + if !strings.HasSuffix(fn, ".star") { + fn += ".star" + } + + vfs := fstest.MapFS{ + fn: &fstest.MapFile{ + Data: src, + }, + } + + return NewAppletFromFS(id, vfs, opts...) +} + +func NewAppletFromFS(id string, fsys fs.FS, opts ...AppletOption) (*Applet, error) { a := &Applet{ - ID: id, - src: src, + ID: id, + globals: make(map[string]starlark.StringDict), + loadedPaths: make(map[string]bool), } for _, opt := range opts { @@ -116,70 +132,13 @@ func NewApplet(id string, src []byte, opts ...AppletOption) (*Applet, error) { } } - if err := a.load(); err != nil { + if err := a.load(fsys); err != nil { return nil, err } return a, nil } -func (a *Applet) load() (err error) { - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("panic while executing %s: %v", a.ID, r) - } - }() - - predeclared := starlark.StringDict{ - "struct": starlark.NewBuiltin("struct", starlarkstruct.Make), - } - - globals, err := starlark.ExecFileOptions( - &syntax.FileOptions{ - Set: true, - Recursion: true, - }, - a.newThread(context.Background()), - a.ID, - a.src, - predeclared, - ) - if err != nil { - return fmt.Errorf("starlark.ExecFile: %v", err) - } - a.globals = globals - - mainFun, found := globals["main"] - if !found { - return fmt.Errorf("%s didn't export a main() function", a.ID) - } - main, ok := mainFun.(*starlark.Function) - if !ok { - return fmt.Errorf("%s exported a main() that is not function", a.ID) - } - a.main = main - - schemaFun, _ := a.globals[schema.SchemaFunctionName].(*starlark.Function) - if schemaFun != nil { - schemaVal, err := a.Call(context.Background(), schemaFun) - if err != nil { - return fmt.Errorf("calling schema function for %s: %w", a.ID, err) - } - - a.schema, err = schema.FromStarlark(schemaVal, a.globals) - if err != nil { - return fmt.Errorf("parsing schema for %s: %w", a.ID, err) - } - - a.schemaJSON, err = json.Marshal(a.schema) - if err != nil { - return fmt.Errorf("serializing schema to JSON for %s: %w", a.ID, err) - } - } - - return nil -} - // Run executes the applet's main function. It returns the render roots that are // returned by the applet. func (a *Applet) Run(ctx context.Context) (roots []render.Root, err error) { @@ -190,12 +149,12 @@ func (a *Applet) Run(ctx context.Context) (roots []render.Root, err error) { // starlark dict. It returns the render roots that are returned by the applet. func (a *Applet) RunWithConfig(ctx context.Context, config map[string]string) (roots []render.Root, err error) { var args starlark.Tuple - if a.main.NumParams() > 0 { + if a.mainFun.NumParams() > 0 { starlarkConfig := AppletConfig(config) args = starlark.Tuple{starlarkConfig} } - returnValue, err := a.Call(ctx, a.main, args...) + returnValue, err := a.Call(ctx, a.mainFun, args...) if err != nil { return nil, err } @@ -253,7 +212,7 @@ func (app *Applet) CallSchemaHandler(ctx context.Context, handlerName, parameter return options, nil case schema.ReturnSchema: - sch, err := schema.FromStarlark(resultVal, app.globals) + sch, err := schema.FromStarlark(resultVal, app.globals[app.schemaFile]) if err != nil { return "", err } @@ -291,18 +250,19 @@ func (app *Applet) RunTests(t *testing.T) { return thread }) - for name, global := range app.globals { - if !strings.HasPrefix(name, "test_") { - continue - } + for file, globals := range app.globals { + for name, global := range globals { + if !strings.HasPrefix(name, "test_") { + continue + } - fun, ok := global.(*starlark.Function) - if ok && fun != app.main { - t.Run(name, func(t *testing.T) { - if _, err := app.Call(context.Background(), fun); err != nil { - t.Error(err) - } - }) + if fun, ok := global.(*starlark.Function); ok { + t.Run(fmt.Sprintf("%s/%s", file, name), func(t *testing.T) { + if _, err := app.Call(context.Background(), fun); err != nil { + t.Error(err) + } + }) + } } } } @@ -317,6 +277,7 @@ func (a *Applet) Call(ctx context.Context, callable *starlark.Function, args ... }() t := a.newThread(ctx) + defer starlarkutil.RunOnExitFuncs(t) context.AfterFunc(ctx, func() { t.Cancel(context.Cause(ctx).Error()) @@ -339,6 +300,190 @@ func (a *Applet) Call(ctx context.Context, callable *starlark.Function, args ... return resultVal, nil } +// PathsForBundle returns a list of all the paths that have been loaded by the +// applet. This is useful for creating a bundle of the applet. +func (a *Applet) PathsForBundle() []string { + paths := make([]string, 0, len(a.loadedPaths)) + for path := range a.loadedPaths { + paths = append(paths, path) + } + return paths +} + +func (a *Applet) load(fsys fs.FS) (err error) { + if err := fs.WalkDir(fsys, ".", func(pathToLoad string, d fs.DirEntry, walkDirErr error) error { + if walkDirErr != nil { + return walkDirErr + } + + if d.IsDir() || path.Dir(pathToLoad) != "." { + // only process files in the root directory + return nil + } + + if !strings.HasSuffix(pathToLoad, ".star") { + // not a starlark file + return nil + } + + return a.ensureLoaded(fsys, pathToLoad) + }); err != nil { + return err + } + + if a.mainFun == nil { + return fmt.Errorf("no main() function found in %s", a.ID) + } + + return nil +} + +func (a *Applet) ensureLoaded(fsys fs.FS, pathToLoad string, currentlyLoading ...string) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic while executing %s: %v", a.ID, r) + } + }() + + // normalize path so that it can be used as a key + pathToLoad = path.Clean(pathToLoad) + if _, ok := a.globals[pathToLoad]; ok { + // already loaded, good to go + return nil + } + + // use the currentlyLoading slice to detect circular dependencies + if slices.Contains(currentlyLoading, pathToLoad) { + return fmt.Errorf("circular dependency detected: %s -> %s", strings.Join(currentlyLoading, " -> "), pathToLoad) + } else { + // mark this file as currently loading. if we encounter it again, + // we have a circular dependency. + currentlyLoading = append(currentlyLoading, pathToLoad) + + // also mark the file as loaded to keep track of all of the files + // that have been loaded + a.loadedPaths[pathToLoad] = true + } + + src, err := fs.ReadFile(fsys, pathToLoad) + if err != nil { + return fmt.Errorf("reading %s: %v", pathToLoad, err) + } + + predeclared := starlark.StringDict{ + "struct": starlark.NewBuiltin("struct", starlarkstruct.Make), + } + + thread := a.newThread(context.Background()) + defer starlarkutil.RunOnExitFuncs(thread) + + // override loader to allow loading starlark files + thread.Load = func(thread *starlark.Thread, module string) (starlark.StringDict, error) { + // normalize module path + modulePath := path.Clean(module) + + // if the module exists on the filesystem, load it + if _, err := fs.Stat(fsys, modulePath); err == nil { + // ensure the module is loaded, and pass the currentlyLoading slice + // to detect circular dependencies + if err := a.ensureLoaded(fsys, modulePath, currentlyLoading...); err != nil { + return nil, err + } + + if g, ok := a.globals[modulePath]; !ok { + return nil, fmt.Errorf("module %s not loaded", modulePath) + } else { + return g, nil + } + } + + // fallback to default loader + return a.loadModule(thread, module) + } + + switch path.Ext(pathToLoad) { + case ".star": + globals, err := starlark.ExecFileOptions( + &syntax.FileOptions{ + Set: true, + Recursion: true, + }, + thread, + path.Join(a.ID, pathToLoad), + src, + predeclared, + ) + if err != nil { + return fmt.Errorf("starlark.ExecFile: %v", err) + } + a.globals[pathToLoad] = globals + + // if the file is in the root directory, check for the main function + // and schema function + mainFun, _ := globals["main"].(*starlark.Function) + if mainFun != nil { + if a.mainFile != "" { + return fmt.Errorf("multiple files with a main() function:\n- %s\n- %s", pathToLoad, a.mainFile) + } + + a.mainFile = pathToLoad + a.mainFun = mainFun + } + + schemaFun, _ := globals[schema.SchemaFunctionName].(*starlark.Function) + if schemaFun != nil { + if a.schemaFile != "" { + return fmt.Errorf("multiple files with a %s() function:\n- %s\n- %s", schema.SchemaFunctionName, pathToLoad, a.schemaFile) + } + a.schemaFile = pathToLoad + + schemaVal, err := a.Call(context.Background(), schemaFun) + if err != nil { + return fmt.Errorf("calling schema function for %s: %w", a.ID, err) + } + + a.schema, err = schema.FromStarlark(schemaVal, globals) + if err != nil { + return fmt.Errorf("parsing schema for %s: %w", a.ID, err) + } + + a.schemaJSON, err = json.Marshal(a.schema) + if err != nil { + return fmt.Errorf("serializing schema to JSON for %s: %w", a.ID, err) + } + } + + default: + a.globals[pathToLoad] = starlark.StringDict{ + "file": File{ + fsys: fsys, + path: pathToLoad, + }.Struct(), + } + } + + return nil +} + +func (a *Applet) newThread(ctx context.Context) *starlark.Thread { + t := &starlark.Thread{ + Name: a.ID, + Load: a.loadModule, + Print: func(thread *starlark.Thread, msg string) { + fmt.Printf("[%s] %s\n", a.ID, msg) + }, + } + + starlarkutil.AttachThreadContext(ctx, t) + random.AttachToThread(t) + + for _, init := range a.initializers { + t = init(t) + } + + return t +} + func (a *Applet) loadModule(thread *starlark.Thread, module string) (starlark.StringDict, error) { if a.loader != nil { mod, err := a.loader(thread, module) @@ -366,6 +511,9 @@ func (a *Applet) loadModule(thread *starlark.Thread, module string) (starlark.St case "xpath.star": return xpath.LoadXPathModule() + case "bsoup.star": + return starlibbsoup.LoadModule() + case "compress/gzip.star": return starlark.StringDict{ starlibgzip.Module.Name: starlibgzip.Module, @@ -439,22 +587,3 @@ func (a *Applet) loadModule(thread *starlark.Thread, module string) (starlark.St return nil, fmt.Errorf("invalid module: %s", module) } } - -func (a *Applet) newThread(ctx context.Context) *starlark.Thread { - t := &starlark.Thread{ - Name: fmt.Sprintf("%s/%x", a.ID, md5.Sum(a.src)), - Load: a.loadModule, - Print: func(thread *starlark.Thread, msg string) { - fmt.Printf("[%s] %s\n", a.ID, msg) - }, - } - - starlarkutil.AttachThreadContext(ctx, t) - random.AttachToThread(t) - - for _, init := range a.initializers { - t = init(t) - } - - return t -} diff --git a/runtime/applet_test.go b/runtime/applet_test.go index 7997d03331..bcb121f99a 100644 --- a/runtime/applet_test.go +++ b/runtime/applet_test.go @@ -4,13 +4,16 @@ import ( "archive/zip" "bytes" "context" + "encoding/json" "fmt" "testing" + "testing/fstest" starlibbase64 "github.com/qri-io/starlib/encoding/base64" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.starlark.net/starlark" + "tidbyt.dev/pixlet/schema" ) func TestLoadEmptySrc(t *testing.T) { @@ -173,10 +176,52 @@ def main(config): assert.Equal(t, 3, len(roots)) } +func TestLoadMultipleFiles(t *testing.T) { + mainSrc := ` +load("render.star", "render") +def main(): + return render.Root(child=render.Box()) +` + schemaDefSrc := ` +load("schema.star", "schema") +def get_schema(): + return schema.Schema( + version = "1", + fields = [], + ) +` + vfs := fstest.MapFS{ + "main.star": {Data: []byte(mainSrc)}, + "schema_def.star": {Data: []byte(schemaDefSrc)}, + } + + app, err := NewAppletFromFS("multiple_files", vfs) + require.NoError(t, err) + require.NotNil(t, app) + + jsonSchema := app.GetSchema() + var s schema.Schema + json.Unmarshal([]byte(jsonSchema), &s) + assert.Equal(t, "1", s.Version) + + roots, err := app.Run(context.Background()) + assert.NoError(t, err) + assert.NotNil(t, roots) + assert.Equal(t, 1, len(roots)) + + // multiple main functions should fail + vfs["main2.star"] = &fstest.MapFile{ + Data: []byte(mainSrc), + } + _, err = NewAppletFromFS("multiple_files_multiple_mains", vfs) + assert.Error(t, err) +} + func TestModuleLoading(t *testing.T) { // Our basic set of modules can be imported src := ` load("render.star", "render") +load("bsoup.star", "bsoup") load("encoding/base64.star", "base64") load("encoding/json.star", "json") load("http.star", "http") @@ -201,6 +246,8 @@ def main(): fail("re broken") if time.parse_duration("10s").seconds != 10: fail("time broken") + if bsoup.parseHtml("

foo

").find("h1").get_text() != "foo": + fail("bsoup broken") return render.Root(child=render.Box()) ` app, err := NewApplet("test.star", []byte(src)) @@ -230,33 +277,79 @@ def main(): roots, err = app.Run(context.Background()) assert.NoError(t, err) assert.Equal(t, 1, len(roots)) - } -func TestThreadInitializer(t *testing.T) { +func TestDependency(t *testing.T) { + // src.star depends on hello.star src := ` load("render.star", "render") +load("hello.star", "hello") + def main(): - print('foobar') - return render.Root(child=render.Box()) + if hello.world() != "hello world": + fail("something went wrong") + return render.Root(child=render.Box()) ` - // override the print function of the thread - var printedText string - initializer := func(thread *starlark.Thread) *starlark.Thread { - thread.Print = func(thread *starlark.Thread, msg string) { - printedText += msg - } - return thread + + helloSrc := ` +def _world(): + return "hello world" + +hello = struct( + world = _world, +) +` + + vfs := fstest.MapFS{ + "src.star": {Data: []byte(src)}, + "hello.star": {Data: []byte(helloSrc)}, } - app, err := NewApplet("test.star", []byte(src), WithThreadInitializer(initializer)) - assert.NoError(t, err) - assert.NotNil(t, app) - _, err = app.Run(context.Background()) + app, err := NewAppletFromFS("test", vfs) assert.NoError(t, err) + if assert.NotNil(t, app) { + roots, err := app.Run(context.Background()) + assert.NoError(t, err) + assert.Equal(t, 1, len(roots)) + } - // our print function should have been called - assert.Equal(t, "foobar", printedText) + // src2.star shouldn't be able to access private function from hello.star + src2 := ` +load("render.star", "render") +load("hello.star", "_world") + +def main(): + return render.Root(child=render.Box()) + ` + + vfs2 := fstest.MapFS{ + "src2.star": {Data: []byte(src2)}, + "hello.star": {Data: []byte(helloSrc)}, + } + + _, err = NewAppletFromFS("test", vfs2) + assert.ErrorContains(t, err, "not exported") +} + +func TestCircularDependency(t *testing.T) { + // Module A depends on module B + srcA := ` +load("b.star", "b") +def a(): + return b.b() +` + // Module B depends on module A + srcB := ` +load("a.star", "a") +def b(): + return a.a() +` + vfs := fstest.MapFS{ + "a.star": {Data: []byte(srcA)}, + "b.star": {Data: []byte(srcB)}, + } + _, err := NewAppletFromFS("circular_dependency", vfs) + assert.ErrorContains(t, err, "circular dependency") } func TestTimezoneDatabase(t *testing.T) { diff --git a/runtime/file.go b/runtime/file.go new file mode 100644 index 0000000000..c06a1115b3 --- /dev/null +++ b/runtime/file.go @@ -0,0 +1,112 @@ +package runtime + +import ( + "fmt" + "io" + "io/fs" + + "go.starlark.net/starlark" + "go.starlark.net/starlarkstruct" +) + +type File struct { + fsys fs.FS + path string +} + +func (f File) Struct() *starlarkstruct.Struct { + return starlarkstruct.FromStringDict(starlark.String("File"), starlark.StringDict{ + "path": starlark.String(f.path), + "readall": starlark.NewBuiltin("readall", f.readall), + }) +} + +func (f File) readall(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + var mode starlark.String + if err := starlark.UnpackArgs("readall", args, kwargs, "mode?", &mode); err != nil { + return nil, err + } + + r, err := f.reader(string(mode)) + if err != nil { + return nil, err + } + defer r.Close() + + return r.read(thread, nil, nil, nil) +} + +func (f File) reader(mode string) (*Reader, error) { + var binaryMode bool + switch mode { + case "", "r", "rt": + binaryMode = false + + case "rb": + binaryMode = true + + default: + return nil, fmt.Errorf("unsupported mode: %s", mode) + } + + fl, err := f.fsys.Open(f.path) + if err != nil { + return nil, err + } + return &Reader{fl, binaryMode}, nil +} + +type Reader struct { + io.ReadCloser + binaryMode bool +} + +func (r Reader) Struct() *starlarkstruct.Struct { + return starlarkstruct.FromStringDict(starlark.String("Reader"), starlark.StringDict{ + "read": starlark.NewBuiltin("read", r.read), + "close": starlark.NewBuiltin("close", func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + return nil, r.Close() + }), + }) +} + +// read reads the contents of the file. The Starlark signature is: +// +// read(size=-1) -> bytes +func (r Reader) read(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + starlarkSize := starlark.MakeInt(-1) + if err := starlark.UnpackArgs("read", args, kwargs, "size?", &starlarkSize); err != nil { + return nil, err + } + + var size int + if err := starlark.AsInt(starlarkSize, &size); err != nil { + return nil, fmt.Errorf("size is not an int") + } + + returnType := func(buf []byte) starlark.Value { + if r.binaryMode { + return starlark.Bytes(buf) + } else { + return starlark.String(buf) + } + } + + if size < 0 { + // read and return all bytes + buf, err := io.ReadAll(r) + if err != nil { + return nil, err + } + + return returnType(buf), nil + } else { + // read and return size bytes + buf := make([]byte, size) + _, err := r.Read(buf) + if err != nil { + return nil, err + } + return returnType(buf), nil + } +} diff --git a/runtime/file_test.go b/runtime/file_test.go new file mode 100644 index 0000000000..4bd2bb6d1e --- /dev/null +++ b/runtime/file_test.go @@ -0,0 +1,39 @@ +package runtime + +import ( + "testing" + "testing/fstest" + + "github.com/stretchr/testify/require" +) + +func TestReadFile(t *testing.T) { + src := ` +load("hello.txt", hello = "file") + +def assert_eq(message, actual, expected): + if not expected == actual: + fail(message, "-", "expected", expected, "actual", actual) + +def test_readall(): + assert_eq("readall", hello.readall(), "hello world") + +def test_readall_binary(): + assert_eq("readall_binary", hello.readall("rb"), b"hello world") + +def main(): + pass + +` + + helloTxt := `hello world` + + vfs := &fstest.MapFS{ + "main.star": {Data: []byte(src)}, + "hello.txt": {Data: []byte(helloTxt)}, + } + + app, err := NewAppletFromFS("test_read_file", vfs) + require.NoError(t, err) + app.RunTests(t) +} diff --git a/runtime/httpcache.go b/runtime/httpcache.go index 6c77cef996..40502058ab 100644 --- a/runtime/httpcache.go +++ b/runtime/httpcache.go @@ -23,6 +23,7 @@ const ( HTTPTimeout = 5 * time.Second MaxResponseBytes = 20 * 1024 * 1024 // 20MB HTTPCachePrefix = "httpcache" + TTLHeader = "X-Tidbyt-Cache-Seconds" ) // Status codes that are cacheable as defined here: @@ -106,10 +107,15 @@ func (c *cacheClient) RoundTrip(req *http.Request) (*http.Response, error) { } func cacheKey(req *http.Request) (string, error) { + ttl := req.Header.Get(TTLHeader) + req.Header.Del(TTLHeader) r, err := httputil.DumpRequest(req, true) if err != nil { return "", fmt.Errorf("%s: %w", "failed to serialize request", err) } + if ttl != "" { + req.Header.Set(TTLHeader, ttl) + } h := sha256.Sum256(r) key := hex.EncodeToString(h[:]) diff --git a/runtime/render_test.go b/runtime/render_test.go index 961c58b3dd..68117e8816 100644 --- a/runtime/render_test.go +++ b/runtime/render_test.go @@ -161,7 +161,7 @@ def main(): app, err := NewApplet(filename, []byte(src)) assert.NoError(t, err) - b := app.globals["b"] + b := app.globals["test_box.star"]["b"] assert.IsType(t, &render_runtime.Box{}, b) widget := b.(*render_runtime.Box).AsRenderWidget() @@ -196,7 +196,7 @@ def main(): app, err := NewApplet(filename, []byte(src)) assert.NoError(t, err) - txt := app.globals["t"] + txt := app.globals["test_text.star"]["t"] assert.IsType(t, &render_runtime.Text{}, txt) widget := txt.(*render_runtime.Text).AsRenderWidget() @@ -240,7 +240,7 @@ def main(): app, err := NewApplet(filename, []byte(src)) assert.NoError(t, err) - starlarkP := app.globals["img"] + starlarkP := app.globals["test_png.star"]["img"] require.IsType(t, &render_runtime.Image{}, starlarkP) actualIm := render.PaintWidget(starlarkP.(*render_runtime.Image).AsRenderWidget(), image.Rect(0, 0, 64, 32), 0) diff --git a/runtime/testdata/httpcache.star b/runtime/testdata/httpcache.star index 0a1cbe18a5..5bb5bc8c86 100644 --- a/runtime/testdata/httpcache.star +++ b/runtime/testdata/httpcache.star @@ -18,7 +18,7 @@ def main(config): resp = http.get( url = "https://example.com", - ttl_seconds = 60, + ttl_seconds = 3, ) assert.eq(resp.headers.get("Tidbyt-Cache-Status"), "HIT") @@ -32,7 +32,7 @@ def main(config): url = "https://example.com", ttl_seconds = 60, ) - assert.eq(resp.headers.get("Tidbyt-Cache-Status"), "MISS") + assert.eq(resp.headers.get("Tidbyt-Cache-Status"), "HIT") resp = http.post( url = "https://example.com", diff --git a/schema/schema.go b/schema/schema.go index 63527edf30..122cd46c2d 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -42,7 +42,7 @@ type SchemaField struct { Visibility *SchemaVisibility `json:"visibility,omitempty" validate:"omitempty,dive"` Default string `json:"default,omitempty" validate:"required_for=dropdown onoff radio"` - Options []SchemaOption `json:"options,omitempty" validate:"required_for=dropdown radio,dive"` + Options []SchemaOption `json:"options,omitempty" validate:"required_for=dropdown radio"` Palette []string `json:"palette,omitempty"` Source string `json:"source,omitempty" validate:"required_for=generated"` diff --git a/server/loader/loader.go b/server/loader/loader.go index 32ae9322b1..103a157e45 100644 --- a/server/loader/loader.go +++ b/server/loader/loader.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "io/fs" "log" "time" @@ -18,7 +19,7 @@ import ( // Loader is a structure to provide applet loading when a file changes or on // demand. type Loader struct { - filename string + fs fs.FS fileChanges chan bool watch bool applet runtime.Applet @@ -42,7 +43,7 @@ type Update struct { // encoded WebP strings. If watch is enabled, both file changes and on demand // requests will send updates over the updatesChan. func NewLoader( - filename string, + fs fs.FS, watch bool, fileChanges chan bool, updatesChan chan Update, @@ -51,7 +52,7 @@ func NewLoader( ) (*Loader, error) { l := &Loader{ - filename: filename, + fs: fs, fileChanges: fileChanges, watch: watch, applet: runtime.Applet{}, @@ -69,7 +70,7 @@ func NewLoader( runtime.InitCache(cache) if !l.watch { - app, err := loadScript("app-id", l.filename) + app, err := loadScript("app-id", l.fs) l.markInitialLoadComplete() if err != nil { return nil, err @@ -106,7 +107,7 @@ func (l *Loader) Run() error { l.updatesChan <- up l.resultsChan <- up case <-l.fileChanges: - log.Printf("detected updates for %s, reloading\n", l.filename) + log.Println("detected updates, reloading") up := Update{} webp, err := l.loadApplet(config) @@ -156,7 +157,7 @@ func (l *Loader) CallSchemaHandler(ctx context.Context, handlerName, parameter s func (l *Loader) loadApplet(config map[string]string) (string, error) { if l.watch { - app, err := loadScript("app-id", l.filename) + app, err := loadScript("app-id", l.fs) l.markInitialLoadComplete() if err != nil { return "", err diff --git a/server/loader/script.go b/server/loader/script.go index 4a27f3ff97..88cc74b42f 100644 --- a/server/loader/script.go +++ b/server/loader/script.go @@ -1,19 +1,11 @@ -//go:build !js && !wasm - package loader import ( - "fmt" - "os" + "io/fs" "tidbyt.dev/pixlet/runtime" ) -func loadScript(appID string, filename string) (*runtime.Applet, error) { - src, err := os.ReadFile(filename) - if err != nil { - return nil, fmt.Errorf("failed to read file %s: %w", filename, err) - } - - return runtime.NewApplet(appID, src) +func loadScript(appID string, fs fs.FS) (*runtime.Applet, error) { + return runtime.NewAppletFromFS(appID, fs) } diff --git a/server/loader/script_js.go b/server/loader/script_js.go deleted file mode 100644 index c07b9b4fa1..0000000000 --- a/server/loader/script_js.go +++ /dev/null @@ -1,26 +0,0 @@ -//go:build js && wasm - -package loader - -import ( - "fmt" - "io" - "net/http" - - "tidbyt.dev/pixlet/runtime" -) - -func loadScript(appID string, filename string) (*runtime.Applet, error) { - res, err := http.Get(filename) - if err != nil { - return nil, fmt.Errorf("failed to fetch file %s: %w", filename, err) - } - defer res.Body.Close() - - src, err := io.ReadAll(res.Body) - if err != nil { - return nil, fmt.Errorf("failed to read file %s: %w", filename, err) - } - - return runtime.NewApplet(appID, src) -} diff --git a/server/server.go b/server/server.go index 165228775f..6ff5459674 100644 --- a/server/server.go +++ b/server/server.go @@ -2,35 +2,58 @@ package server import ( "fmt" + "io/fs" + "os" + "path/filepath" + "strings" "golang.org/x/sync/errgroup" "tidbyt.dev/pixlet/server/browser" "tidbyt.dev/pixlet/server/loader" - "tidbyt.dev/pixlet/server/watcher" + "tidbyt.dev/pixlet/tools" ) // Server provides functionality to serve Starlark over HTTP. It has // functionality to watch a file and hot reload the browser on changes. type Server struct { - watcher *watcher.Watcher + watcher *Watcher browser *browser.Browser loader *loader.Loader watch bool } // NewServer creates a new server initialized with the applet. -func NewServer(host string, port int, watch bool, filename string, maxDuration int, timeout int) (*Server, error) { +func NewServer(host string, port int, watch bool, path string, maxDuration int, timeout int) (*Server, error) { fileChanges := make(chan bool, 100) - w := watcher.NewWatcher(filename, fileChanges) + + // check if path exists, and whether it is a directory or a file + info, err := os.Stat(path) + if err != nil { + return nil, fmt.Errorf("failed to stat %s: %w", path, err) + } + + var fs fs.FS + var w *Watcher + if info.IsDir() { + fs = os.DirFS(path) + w = NewWatcher(path, fileChanges) + } else { + if !strings.HasSuffix(path, ".star") { + return nil, fmt.Errorf("script file must have suffix .star: %s", path) + } + + fs = tools.NewSingleFileFS(path) + w = NewWatcher(filepath.Dir(path), fileChanges) + } updatesChan := make(chan loader.Update, 100) - l, err := loader.NewLoader(filename, watch, fileChanges, updatesChan, maxDuration, timeout) + l, err := loader.NewLoader(fs, watch, fileChanges, updatesChan, maxDuration, timeout) if err != nil { return nil, err } addr := fmt.Sprintf("%s:%d", host, port) - b, err := browser.NewBrowser(addr, filename, watch, updatesChan, l) + b, err := browser.NewBrowser(addr, filepath.Base(path), watch, updatesChan, l) if err != nil { return nil, err } diff --git a/server/watcher/fs.go b/server/watcher.go similarity index 63% rename from server/watcher/fs.go rename to server/watcher.go index d7dec016c5..de50bbdb73 100644 --- a/server/watcher/fs.go +++ b/server/watcher.go @@ -1,14 +1,29 @@ -//go:build !js && !wasm - -package watcher +package server import ( "fmt" + "log" "path/filepath" + "strings" "github.com/fsnotify/fsnotify" ) +// Watcher is a structure to watch a file for changes and notify a channel. +type Watcher struct { + path string + fileChanges chan bool +} + +// NewWatcher instantiates a new watcher with the provided filename and changes +// channel. +func NewWatcher(filename string, fileChanges chan bool) *Watcher { + return &Watcher{ + path: filepath.FromSlash(filename), + fileChanges: fileChanges, + } +} + // Run starts the file watcher in a blocking fashion. This watches an entire // directory and only notifies the channel when the specified file is changed. // If there is an error, it's returned. It's up to the caller to respawn the @@ -27,7 +42,7 @@ func (w *Watcher) Run() error { } defer watcher.Close() - watcher.Add(filepath.Dir(w.filename)) + watcher.Add(w.path) for { select { @@ -35,7 +50,8 @@ func (w *Watcher) Run() error { if !ok { return fmt.Errorf("something is weird with the file watcher") } - if event.Name == w.filename && (event.Op&(fsnotify.Write|fsnotify.Create)) != 0 { + log.Println(event.Name) + if strings.HasPrefix(event.Name, w.path) && shouldNotify(event.Op) { w.fileChanges <- true } @@ -47,3 +63,9 @@ func (w *Watcher) Run() error { } } } + +func shouldNotify(op fsnotify.Op) bool { + // notify on all ops except for chmod, since that is discouraged + // in the fsnotify docs. + return op.Has(fsnotify.Write | fsnotify.Create | fsnotify.Remove | fsnotify.Rename) +} diff --git a/server/watcher/fs_js.go b/server/watcher/fs_js.go deleted file mode 100644 index 639c6caaa2..0000000000 --- a/server/watcher/fs_js.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build js && wasm - -package watcher - -import ( - "fmt" -) - -func (w *Watcher) Run() error { - return fmt.Errorf("file watching not supported in WASM") -} diff --git a/server/watcher/watcher.go b/server/watcher/watcher.go deleted file mode 100644 index 0090c41862..0000000000 --- a/server/watcher/watcher.go +++ /dev/null @@ -1,21 +0,0 @@ -// Package watcher provides a simple mechanism to watch a file for changes. -package watcher - -import ( - "path/filepath" -) - -// Watcher is a structure to watch a file for changes and notify a channel. -type Watcher struct { - filename string - fileChanges chan bool -} - -// NewWatcher instantiates a new watcher with the provided filename and changes -// channel. -func NewWatcher(filename string, fileChanges chan bool) *Watcher { - return &Watcher{ - filename: filepath.FromSlash(filename), - fileChanges: fileChanges, - } -} diff --git a/starlarkutil/onexit.go b/starlarkutil/onexit.go new file mode 100644 index 0000000000..d3ee2a6cdc --- /dev/null +++ b/starlarkutil/onexit.go @@ -0,0 +1,27 @@ +package starlarkutil + +import "go.starlark.net/starlark" + +const ( + // ThreadOnExitKey is the key used to store functions that should be called + // when a thread exits. + ThreadOnExitKey = "tidbyt.dev/pixlet/runtime/on_exit" +) + +type threadOnExitFunc func() + +func AddOnExit(thread *starlark.Thread, fn threadOnExitFunc) { + if onExit, ok := thread.Local(ThreadOnExitKey).(*[]threadOnExitFunc); ok { + *onExit = append(*onExit, fn) + } else { + thread.SetLocal(ThreadOnExitKey, &[]threadOnExitFunc{fn}) + } +} + +func RunOnExitFuncs(thread *starlark.Thread) { + if onExit, ok := thread.Local(ThreadOnExitKey).(*[]threadOnExitFunc); ok { + for _, fn := range *onExit { + fn() + } + } +} diff --git a/tools/generator/generator.go b/tools/generator/generator.go index 132e41a3d3..39c3a3add6 100644 --- a/tools/generator/generator.go +++ b/tools/generator/generator.go @@ -83,12 +83,12 @@ func (g *Generator) RemoveApp(app *manifest.Manifest) error { } func (g *Generator) createDir(app *manifest.Manifest) error { - p := path.Join(g.root, appsDir, app.PackageName) + p := path.Join(g.root, appsDir, manifest.GenerateDirName(app.Name)) return os.MkdirAll(p, os.ModePerm) } func (g *Generator) removeDir(app *manifest.Manifest) error { - p := path.Join(g.root, appsDir, app.PackageName) + p := path.Join(g.root, appsDir, manifest.GenerateDirName(app.Name)) return os.RemoveAll(p) } @@ -96,7 +96,7 @@ func (g *Generator) writeManifest(app *manifest.Manifest) error { var p string switch g.appType { case Community, Internal: - p = path.Join(g.root, appsDir, app.PackageName, manifestName) + p = path.Join(g.root, appsDir, manifest.GenerateDirName(app.Name), manifestName) default: p = path.Join(g.root, manifestName) } @@ -111,12 +111,15 @@ func (g *Generator) writeManifest(app *manifest.Manifest) error { } func (g *Generator) generateStarlark(app *manifest.Manifest) (string, error) { + dir := manifest.GenerateDirName(app.Name) + fn := manifest.GenerateFileName(app.Name) + var p string switch g.appType { case Community, Internal: - p = path.Join(g.root, appsDir, app.PackageName, app.FileName) + p = path.Join(g.root, appsDir, dir, fn) default: - p = path.Join(g.root, app.FileName) + p = path.Join(g.root, fn) } file, err := os.Create(p) diff --git a/tools/singlefilefs.go b/tools/singlefilefs.go new file mode 100644 index 0000000000..87ab8e1f19 --- /dev/null +++ b/tools/singlefilefs.go @@ -0,0 +1,27 @@ +package tools + +import ( + "io/fs" + "os" + "path/filepath" +) + +type SingleFileFS struct { + Path string + baseFS fs.FS +} + +func NewSingleFileFS(filePath string) *SingleFileFS { + return &SingleFileFS{ + Path: filePath, + baseFS: os.DirFS(filepath.Dir(filePath)), + } +} + +func (sfs *SingleFileFS) Open(name string) (fs.File, error) { + if name != "." && name != filepath.Base(sfs.Path) { + return nil, fs.ErrNotExist + } + + return sfs.baseFS.Open(name) +}