diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 561f11bf0e..4085491848 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: "1.22.2" @@ -33,8 +33,11 @@ jobs: runs-on: ${{ matrix.os }} steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: "1.22.2" @@ -55,9 +58,6 @@ jobs: mingw-w64-x86_64-go mingw-w64-x86_64-toolchain - - name: Checkout code - uses: actions/checkout@v4 - - name: Install Linux dependencies if: matrix.os == 'ubuntu-22.04' run: sudo ./scripts/setup-linux.sh @@ -80,9 +80,16 @@ jobs: - name: Build frontend run: npm run build - - name: Build + - name: Build Linux run: make build - if: matrix.os == 'ubuntu-22.04' || matrix.os == 'macos-latest' + if: matrix.os == 'ubuntu-22.04' + + - name: Build macOS + run: make build + if: matrix.os == 'macos-latest' + env: + LIBRARY_PATH: "/opt/homebrew/lib" + CGO_CPPFLAGS: "-I/opt/homebrew/include" - name: Build Windows shell: msys2 {0} @@ -91,9 +98,16 @@ jobs: make build if: matrix.os == 'windows-latest' - - name: Test + - name: Test Linux run: make test - if: matrix.os == 'ubuntu-22.04' || matrix.os == 'macos-latest' + if: matrix.os == 'ubuntu-22.04' + + - name: Test macOS + run: make test + if: matrix.os == 'macos-latest' + env: + LIBRARY_PATH: "/opt/homebrew/lib" + CGO_CPPFLAGS: "-I/opt/homebrew/include" - name: Test Windows shell: msys2 {0} @@ -126,6 +140,8 @@ jobs: run: make release-macos env: PIXLET_VERSION: ${{ steps.vars.outputs.tag }} + LIBRARY_PATH: "/opt/homebrew/lib" + CGO_CPPFLAGS: "-I/opt/homebrew/include" - name: Build Release Windows if: matrix.os == 'windows-latest' @@ -156,7 +172,7 @@ jobs: fetch-depth: "0" - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: "1.22.2" diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 26374ffd50..d5014c8811 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: "1.22.2" @@ -31,8 +31,11 @@ jobs: runs-on: ${{ matrix.os }} steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: "1.22.2" @@ -53,9 +56,6 @@ jobs: mingw-w64-x86_64-go mingw-w64-x86_64-toolchain - - name: Checkout code - uses: actions/checkout@v4 - - name: Install Linux dependencies if: matrix.os == 'ubuntu-22.04' run: sudo apt-get install -y libwebp-dev @@ -78,9 +78,16 @@ jobs: - name: Build frontend run: npm run build - - name: Build + - name: Build Linux run: make build - if: matrix.os == 'ubuntu-22.04' || matrix.os == 'macos-latest' + if: matrix.os == 'ubuntu-22.04' + + - name: Build macOS + run: make build + if: matrix.os == 'macos-latest' + env: + LIBRARY_PATH: "/opt/homebrew/lib" + CGO_CPPFLAGS: "-I/opt/homebrew/include" - name: Build Windows shell: msys2 {0} @@ -89,9 +96,16 @@ jobs: make build if: matrix.os == 'windows-latest' - - name: Test + - name: Test Linux + run: make test + if: matrix.os == 'ubuntu-22.04' + + - name: Test macOS run: make test - if: matrix.os == 'ubuntu-22.04' || matrix.os == 'macos-latest' + if: matrix.os == 'macos-latest' + env: + LIBRARY_PATH: "/opt/homebrew/lib" + CGO_CPPFLAGS: "-I/opt/homebrew/include" - name: Test Windows shell: msys2 {0} diff --git a/cmd/check.go b/cmd/check.go index 2706850098..25d1c11bd0 100644 --- a/cmd/check.go +++ b/cmd/check.go @@ -2,16 +2,17 @@ package cmd import ( "fmt" + "io/fs" "os" "path/filepath" "strings" "time" - "github.com/bazelbuild/buildtools/buildifier/utils" "github.com/fatih/color" "github.com/spf13/cobra" "tidbyt.dev/pixlet/cmd/community" "tidbyt.dev/pixlet/manifest" + "tidbyt.dev/pixlet/tools" ) var maxRenderTime = time.Duration(1 * time.Second) @@ -22,10 +23,16 @@ func init() { } var CheckCmd = &cobra.Command{ - Use: "check ...", - Example: ` pixlet check app.star`, - Short: "Checks if an app is ready to publish", - Long: `The check command runs a series of checks to ensure your app is ready + Use: "check ...", + Example: `pixlet check examples/clock`, + Short: "Check if an app is ready to publish", + Long: `Check if an app is ready to publish. + +The path argument should be the path to the Pixlet app to check. The +app can be a single file with the .star extension, or a directory +containing multiple Starlark files and resources. + +The check command runs a series of checks to ensure your app is ready to publish in the community repo. Every failed check will have a solution provided. If your app fails a check, try the provided solution and reach out on Discord if you get stuck.`, @@ -34,85 +41,59 @@ Discord if you get stuck.`, } func checkCmd(cmd *cobra.Command, args []string) error { - // Use the same logic as buildifier to find relevant Tidbyt apps. - apps := args - if rflag { - discovered, err := utils.ExpandDirectories(&args) + // check every path. + foundIssue := false + for _, path := range args { + // check if path exists, and whether it is a directory or a file + info, err := os.Stat(path) if err != nil { - return fmt.Errorf("could not discover apps using recursive flag: %w", err) - } - apps = discovered - } else { - for _, app := range apps { - if filepath.Ext(app) != ".star" { - return fmt.Errorf("only starlark source files or directories with the recursive flag are supported") - } + return fmt.Errorf("failed to stat %s: %w", path, err) } - } - // TODO: this needs to be parallelized. + var fsys fs.FS + var baseDir string + if info.IsDir() { + fsys = os.DirFS(path) + baseDir = path + } else { + if !strings.HasSuffix(path, ".star") { + return fmt.Errorf("script file must have suffix .star: %s", path) + } - // Check every app. - foundIssue := false - for _, app := range apps { - // Check app formatting. - dryRunFlag = true - err := formatCmd(cmd, []string{app}) - if err != nil { - foundIssue = true - failure(app, fmt.Errorf("app is not formatted correctly: %w", err), fmt.Sprintf("try `pixlet format %s`", app)) - continue + fsys = tools.NewSingleFileFS(path) + baseDir = filepath.Dir(path) } // Check if an app can load. - err = community.LoadApp(cmd, []string{app}) - if err != nil { - foundIssue = true - failure(app, fmt.Errorf("app failed to load: %w", err), "try `pixlet community load-app` and resolve any runtime issues") - continue - } - - // Check if app is linted. - outputFormat = "off" - err = lintCmd(cmd, []string{app}) + err = community.LoadApp(cmd, []string{path}) if err != nil { foundIssue = true - failure(app, fmt.Errorf("app has lint warnings: %w", err), fmt.Sprintf("try `pixlet lint --fix %s`", app)) + failure(path, fmt.Errorf("app failed to load: %w", err), "try `pixlet community load-app` and resolve any runtime issues") continue } // Ensure icons are valid. - err = community.ValidateIcons(cmd, []string{app}) + err = community.ValidateIcons(cmd, []string{path}) if err != nil { foundIssue = true - failure(app, fmt.Errorf("app has invalid icons: %w", err), "try `pixlet community list-icons` for the full list of valid icons") + failure(path, fmt.Errorf("app has invalid icons: %w", err), "try `pixlet community list-icons` for the full list of valid icons") continue } // Check app manifest exists - dir := filepath.Dir(app) - if !doesManifestExist(dir) { + if !doesManifestExist(baseDir) { foundIssue = true - failure(app, fmt.Errorf("couldn't find app manifest: %w", err), fmt.Sprintf("try `pixlet community create-manifest %s`", filepath.Join(dir, manifest.ManifestFileName))) + failure(path, fmt.Errorf("couldn't find app manifest"), fmt.Sprintf("try `pixlet community create-manifest %s`", filepath.Join(baseDir, manifest.ManifestFileName))) continue } // Validate manifest. - manifestFile := filepath.Join(dir, manifest.ManifestFileName) - community.ValidateManifestAppFileName = filepath.Base(app) + manifestFile := filepath.Join(baseDir, manifest.ManifestFileName) + community.ValidateManifestAppFileName = filepath.Base(path) err = community.ValidateManifest(cmd, []string{manifestFile}) if err != nil { foundIssue = true - failure(app, fmt.Errorf("manifest didn't validate: %w", err), "try correcting the validation issue by updating your manifest") - continue - } - - // Check spelling. - community.SilentSpelling = true - err = community.SpellCheck(cmd, []string{manifestFile}) - if err != nil { - foundIssue = true - failure(app, fmt.Errorf("manifest contains spelling errors: %w", err), fmt.Sprintf("try `pixlet community spell-check --fix %s`", manifestFile)) + failure(path, fmt.Errorf("manifest didn't validate: %w", err), "try correcting the validation issue by updating your manifest") continue } @@ -126,26 +107,58 @@ func checkCmd(cmd *cobra.Command, args []string) error { // Check if app renders. silenceOutput = true output = f.Name() - err = render(cmd, []string{app}) + err = render(cmd, []string{path}) if err != nil { foundIssue = true - failure(app, fmt.Errorf("app failed to render: %w", err), "try `pixlet render` and resolve any runtime issues") + failure(path, fmt.Errorf("app failed to render: %w", err), "try `pixlet render` and resolve any runtime issues") continue } // Check performance. - p, err := ProfileApp(app, map[string]string{}) + p, err := ProfileApp(path, map[string]string{}) if err != nil { return fmt.Errorf("could not profile app: %w", err) } if p.DurationNanos > maxRenderTime.Nanoseconds() { foundIssue = true - failure(app, fmt.Errorf("app takes too long to render %s", time.Duration(p.DurationNanos)), fmt.Sprintf("try optimizing your app using `pixlet profile %s` to get it under %s", app, maxRenderTime)) + failure( + path, + fmt.Errorf("app takes too long to render %s", time.Duration(p.DurationNanos)), + fmt.Sprintf("try optimizing your app using `pixlet profile %s` to get it under %s", path, time.Duration(maxRenderTime)), + ) continue } + // run format and lint on *.star files in the fs + fs.WalkDir(fsys, ".", func(p string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() || !strings.HasSuffix(p, ".star") { + return nil + } + + realPath := filepath.Join(baseDir, p) + + dryRunFlag = true + if err := formatCmd(cmd, []string{realPath}); err != nil { + foundIssue = true + failure(p, fmt.Errorf("app is not formatted correctly: %w", err), fmt.Sprintf("try `pixlet format %s`", realPath)) + } + + outputFormat = "off" + err = lintCmd(cmd, []string{realPath}) + if err != nil { + foundIssue = true + failure(p, fmt.Errorf("app has lint warnings: %w", err), fmt.Sprintf("try `pixlet lint --fix %s`", realPath)) + } + + return nil + }) + // If we're here, the app and manifest are good to go! - success(app) + success(path) } if foundIssue { diff --git a/cmd/community/community.go b/cmd/community/community.go index aa8e3064ef..81b4f01702 100644 --- a/cmd/community/community.go +++ b/cmd/community/community.go @@ -7,8 +7,6 @@ import ( func init() { CommunityCmd.AddCommand(ListIconsCmd) CommunityCmd.AddCommand(LoadAppCmd) - CommunityCmd.AddCommand(SpellCheckCmd) - CommunityCmd.AddCommand(TargetDeterminatorCmd) CommunityCmd.AddCommand(ValidateIconsCmd) CommunityCmd.AddCommand(ValidateManifestCmd) } diff --git a/cmd/community/loadapp.go b/cmd/community/loadapp.go index 8217976cb1..8fece80051 100644 --- a/cmd/community/loadapp.go +++ b/cmd/community/loadapp.go @@ -2,39 +2,50 @@ package community import ( "fmt" + "io/fs" "os" + "path/filepath" "strings" "github.com/spf13/cobra" "tidbyt.dev/pixlet/runtime" + "tidbyt.dev/pixlet/tools" ) var LoadAppCmd = &cobra.Command{ - Use: "load-app ", + Use: "load-app ", Short: "Validates an app can be successfully loaded in our runtime.", - Example: ` pixlet community load-app app.star`, + Example: `pixlet community load-app examples/clock`, Long: `This command ensures an app can be loaded into our runtime successfully.`, Args: cobra.ExactArgs(1), RunE: LoadApp, } func LoadApp(cmd *cobra.Command, args []string) error { - script := args[0] + path := args[0] - if !strings.HasSuffix(script, ".star") { - return fmt.Errorf("script file must have suffix .star: %s", script) + // 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) } - src, err := os.ReadFile(script) - if err != nil { - return fmt.Errorf("failed to read file %s: %w", script, err) + var fs fs.FS + if info.IsDir() { + fs = os.DirFS(path) + } else { + if !strings.HasSuffix(path, ".star") { + return fmt.Errorf("script file must have suffix .star: %s", path) + } + + fs = tools.NewSingleFileFS(path) } cache := runtime.NewInMemoryCache() runtime.InitHTTP(cache) runtime.InitCache(cache) - if _, err := runtime.NewApplet(script, src, runtime.WithPrintDisabled()); err != nil { + if _, err := runtime.NewAppletFromFS(filepath.Base(path), fs, runtime.WithPrintDisabled()); err != nil { return fmt.Errorf("failed to load applet: %w", err) } diff --git a/cmd/community/spellcheck.go b/cmd/community/spellcheck.go deleted file mode 100644 index 1aebcd0c5f..0000000000 --- a/cmd/community/spellcheck.go +++ /dev/null @@ -1,98 +0,0 @@ -package community - -import ( - "fmt" - "io" - "os" - - "github.com/client9/misspell" - "github.com/spf13/cobra" -) - -var ( - FixSpelling bool - SilentSpelling bool -) - -func init() { - SpellCheckCmd.Flags().BoolVarP(&FixSpelling, "fix", "f", false, "fixes spelling mistakes automatically") - SpellCheckCmd.Flags().BoolVarP(&SilentSpelling, "silent", "s", false, "silences spelling mistakes") -} - -var SpellCheckCmd = &cobra.Command{ - Use: "spell-check ", - Short: "Spell check for a file", - Example: ` pixlet community spell-check manifest.yaml - pixlet community spell-check app.star`, - Long: `This command checks the spelling of strings located in a file. This can be used -both for a manifest and Tidbyt app.`, - Args: cobra.ExactArgs(1), - RunE: SpellCheck, -} - -func SpellCheck(cmd *cobra.Command, args []string) error { - // Load file for checking. - f, err := os.OpenFile(args[0], os.O_RDWR, 0644) - if err != nil { - return fmt.Errorf("could not open file: %w", err) - } - defer f.Close() - - b, err := io.ReadAll(f) - if err != nil { - return fmt.Errorf("could not read file: %w", err) - } - - // Create replacer. - r := misspell.Replacer{ - Replacements: misspell.DictMain, - } - - // Tidbyt is primarily in US markets. We only ship a US power plug, and all - // materials are in the US locale. In the future, we will need to consider - // how we manage spell check as we look to support more markets. - r.AddRuleList(misspell.DictAmerican) - r.Compile() - - // Run replacer. - updated, diffs := r.Replace(string(b)) - - // If FixSpelling is true, we only want to fix spelling and return - if FixSpelling { - // Updating a file in line gets a bit tricky. The file would first have - // to be cleared of the file contents, which feels dangerous. So - // instead, create a temp file, write the contents, and then replace - // the original file with the new file. - temp := args[0] + ".temp" - t, err := os.Create(temp) - if err != nil { - return fmt.Errorf("could not create file: %w", err) - } - defer t.Close() - - _, err = t.WriteString(updated) - if err != nil { - return fmt.Errorf("could not update file: %w", err) - } - - err = os.Rename(temp, args[0]) - if err != nil { - return fmt.Errorf("could not replace file: %w", err) - } - - return nil - } - - if !SilentSpelling { - for _, diff := range diffs { - fmt.Printf("`%s` is a misspelling of `%s` at line: %d\n", diff.Original, diff.Corrected, diff.Line) - } - } - - // Return error if there are any diffs. - if len(diffs) > 0 { - return fmt.Errorf("%s contains spelling errors", args[0]) - } - - return nil -} diff --git a/cmd/community/targetdeterminator.go b/cmd/community/targetdeterminator.go deleted file mode 100644 index cd3a2d24f2..0000000000 --- a/cmd/community/targetdeterminator.go +++ /dev/null @@ -1,92 +0,0 @@ -package community - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/spf13/cobra" - "tidbyt.dev/pixlet/tools/repo" -) - -var ( - oldCommit string - newCommit string -) - -func init() { - TargetDeterminatorCmd.Flags().StringVarP(&oldCommit, "old", "o", "", "The old commit to compare against") - TargetDeterminatorCmd.MarkFlagRequired("old") - TargetDeterminatorCmd.Flags().StringVarP(&newCommit, "new", "n", "", "The new commit to compare against") - TargetDeterminatorCmd.MarkFlagRequired("new") -} - -var TargetDeterminatorCmd = &cobra.Command{ - Use: "target-determinator", - Short: "Determines what files have changed between old and new commit", - Example: ` pixlet community target-determinator \ - --old 4d69e9bbf181434229a98e87909a619634072930 \ - --new 2fc2a1fcfa48bbb0b836084c1b1e259322c4e133`, - Long: `This command determines what files have changed between two commits -so that we can limit the build in the community repo to only the files that -have changed.`, - RunE: determineTargets, -} - -func determineTargets(cmd *cobra.Command, args []string) error { - cwd, err := os.Getwd() - if err != nil { - return fmt.Errorf("could not determine targets, something went wrong with the local filesystem: %w", err) - } - - changedFiles, err := repo.DetermineChanges(cwd, oldCommit, newCommit) - if err != nil { - return fmt.Errorf("could not determine targets: %w", err) - } - - changedApps := map[string]bool{} - for _, f := range changedFiles { - dir := filepath.Dir(f) + string(os.PathSeparator) - - // We only care about things in apps/{{ app package }}/ changing and - // nothing else. Skip any file changes that are not in that structure. - parts := strings.Split(f, string(os.PathSeparator)) - if len(parts) < 3 { - continue - } - - // If the filepath does not start with apps, we also don't care about - // it. - if !strings.HasPrefix(dir, "apps") { - continue - } - - // If the directory no longer exists, we don't care about it. This would - // happen if someone deleted an app in a PR. - if !dirExists(dir) { - continue - } - - changedApps[dir] = true - } - - for app := range changedApps { - fmt.Println(app) - } - - return nil -} - -func dirExists(dir string) bool { - _, err := os.Stat(dir) - if os.IsNotExist(err) { - return false - } - - if err != nil { - return false - } - - return true -} diff --git a/cmd/community/validateicons.go b/cmd/community/validateicons.go index f6d055afb1..d693995bd2 100644 --- a/cmd/community/validateicons.go +++ b/cmd/community/validateicons.go @@ -3,20 +3,22 @@ package community import ( "encoding/json" "fmt" - "io" + "io/fs" "os" + "path/filepath" + "strings" "github.com/spf13/cobra" - "go.starlark.net/starlark" "tidbyt.dev/pixlet/icons" "tidbyt.dev/pixlet/runtime" "tidbyt.dev/pixlet/schema" + "tidbyt.dev/pixlet/tools" ) var ValidateIconsCmd = &cobra.Command{ - Use: "validate-icons ", + Use: "validate-icons ", Short: "Validates the schema icons used are available in our mobile app.", - Example: ` pixlet community validate-icons app.star`, + Example: `pixlet community validate-icons examples/schema_hello_world`, Long: `This command determines if the icons selected in your app schema are supported by our mobile app.`, Args: cobra.ExactArgs(1), @@ -24,29 +26,30 @@ by our mobile app.`, } func ValidateIcons(cmd *cobra.Command, args []string) error { - f, err := os.Open(args[0]) + path := args[0] + + // check if path exists, and whether it is a directory or a file + info, err := os.Stat(path) if err != nil { - return fmt.Errorf("couldn't open app: %w", err) + return fmt.Errorf("failed to stat %s: %w", path, err) } - defer f.Close() - src, err := io.ReadAll(f) - if err != nil { - return fmt.Errorf("failed to read app %s: %w", args[0], err) + var fs fs.FS + if info.IsDir() { + fs = os.DirFS(path) + } else { + if !strings.HasSuffix(path, ".star") { + return fmt.Errorf("script file must have suffix .star: %s", path) + } + + fs = tools.NewSingleFileFS(path) } cache := runtime.NewInMemoryCache() runtime.InitHTTP(cache) runtime.InitCache(cache) - // Remove the print function from the starlark thread. - initializers := []runtime.ThreadInitializer{} - initializers = append(initializers, func(thread *starlark.Thread) *starlark.Thread { - thread.Print = func(thread *starlark.Thread, msg string) {} - return thread - }) - - applet, err := runtime.NewApplet(args[0], src, runtime.WithPrintDisabled()) + applet, err := runtime.NewAppletFromFS(filepath.Base(path), fs, runtime.WithPrintDisabled()) if err != nil { return fmt.Errorf("failed to load applet: %w", err) } diff --git a/cmd/lint.go b/cmd/lint.go index eaec00b9ab..104507c48e 100644 --- a/cmd/lint.go +++ b/cmd/lint.go @@ -48,11 +48,6 @@ func lintCmd(cmd *cobra.Command, args []string) error { differ, _ := differ.Find() diff = differ - // TODO: We currently offer misspelling protection in the community repo - // for app manifests. We'll want to consider adding additional spelling - // support to pixlet lint to ensure typos in apps don't make it to - // production. - // Run buildifier and exit with the returned exit code. exitCode := runBuildifier(args, lint, mode, outputFormat, rflag, vflag) if exitCode != 0 { diff --git a/cmd/profile.go b/cmd/profile.go index 27d5a9b7e2..1a4d691d9b 100644 --- a/cmd/profile.go +++ b/cmd/profile.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "io/fs" "os" "strings" "time" @@ -14,6 +15,7 @@ import ( "go.starlark.net/starlark" "tidbyt.dev/pixlet/runtime" + "tidbyt.dev/pixlet/tools" ) var ( @@ -27,8 +29,8 @@ func init() { } var ProfileCmd = &cobra.Command{ - Use: "profile [script] [=value>]...", - Short: "Run a Pixlet script and print its execution-time profile", + Use: "profile [=value>]...", + Short: "Run a Pixlet app and print its execution-time profile", Args: cobra.MinimumNArgs(1), RunE: profile, } @@ -65,11 +67,7 @@ func (u printUI) WantBrowser() bool { return false } func (u printUI) SetAutoComplete(complete func(string) string) {} func profile(cmd *cobra.Command, args []string) error { - script := args[0] - - if !strings.HasSuffix(script, ".star") { - return fmt.Errorf("script file must have suffix .star: %s", script) - } + path := args[0] config := map[string]string{} for _, param := range args[1:] { @@ -80,7 +78,7 @@ func profile(cmd *cobra.Command, args []string) error { config[split[0]] = split[1] } - profile, err := ProfileApp(script, config) + profile, err := ProfileApp(path, config) if err != nil { return err } @@ -96,17 +94,28 @@ func profile(cmd *cobra.Command, args []string) error { return nil } -func ProfileApp(script string, config map[string]string) (*pprof_profile.Profile, error) { - src, err := os.ReadFile(script) +func ProfileApp(path string, config map[string]string) (*pprof_profile.Profile, error) { + info, err := os.Stat(path) if err != nil { - return nil, fmt.Errorf("failed to read file %s: %w", script, err) + return nil, fmt.Errorf("failed to stat %s: %w", path, err) + } + + var fsys fs.FS + if info.IsDir() { + fsys = os.DirFS(path) + } else { + if !strings.HasSuffix(path, ".star") { + return nil, fmt.Errorf("script file must have suffix .star: %s", path) + } + + fsys = tools.NewSingleFileFS(path) } cache := runtime.NewInMemoryCache() runtime.InitHTTP(cache) runtime.InitCache(cache) - applet, err := runtime.NewApplet(script, src, runtime.WithPrintDisabled()) + applet, err := runtime.NewAppletFromFS(path, fsys, runtime.WithPrintDisabled()) if err != nil { return nil, fmt.Errorf("failed to load applet: %w", err) } diff --git a/cmd/render.go b/cmd/render.go index d64327c78c..e7c532c4c0 100644 --- a/cmd/render.go +++ b/cmd/render.go @@ -8,7 +8,6 @@ import ( "os" "path/filepath" "strings" - "testing/fstest" "time" "github.com/spf13/cobra" @@ -16,6 +15,7 @@ import ( "tidbyt.dev/pixlet/encode" "tidbyt.dev/pixlet/globals" "tidbyt.dev/pixlet/runtime" + "tidbyt.dev/pixlet/tools" ) var ( @@ -102,15 +102,7 @@ func render(cmd *cobra.Command, args []string) error { 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}, - } - + fs = tools.NewSingleFileFS(path) outPath = strings.TrimSuffix(path, ".star") } diff --git a/go.mod b/go.mod index 6ccccc8e20..228bc50f82 100644 --- a/go.mod +++ b/go.mod @@ -8,14 +8,13 @@ require ( github.com/Code-Hex/Neo-cowsay/v2 v2.0.4 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 github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4 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.12.0 - github.com/go-playground/validator/v10 v10.15.1 + github.com/go-playground/validator/v10 v10.19.0 github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 github.com/google/tink/go v1.7.0 github.com/gorilla/mux v1.8.1 @@ -62,7 +61,7 @@ require ( 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/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -75,7 +74,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/leodido/go-urn v1.2.4 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect diff --git a/go.sum b/go.sum index 5db58c01df..86b6fb1b2f 100644 --- a/go.sum +++ b/go.sum @@ -51,7 +51,6 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 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/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= @@ -90,8 +89,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= 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= @@ -114,8 +113,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM= -github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= +github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= @@ -157,8 +156,6 @@ 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-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= @@ -198,8 +195,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -326,7 +323,6 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= diff --git a/icons/icons.go b/icons/icons.go index 1b4661eb8d..12f71f96b2 100644 --- a/icons/icons.go +++ b/icons/icons.go @@ -1,4 +1,3 @@ -//nolint:misspell package icons // IconsMap is the name of a FontAwesome icon to the name in our mobile app. In diff --git a/runtime/applet.go b/runtime/applet.go index 2bac67fe01..0c4ddddc5c 100644 --- a/runtime/applet.go +++ b/runtime/applet.go @@ -11,9 +11,9 @@ import ( "testing" "testing/fstest" + starlibbsoup "github.com/qri-io/starlib/bsoup" 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" @@ -311,24 +311,21 @@ func (a *Applet) PathsForBundle() []string { } 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 - } + // list files in the root directory of fsys + rootDir, err := fs.ReadDir(fsys, ".") + if err != nil { + return fmt.Errorf("reading root directory: %v", err) + } - if d.IsDir() || path.Dir(pathToLoad) != "." { - // only process files in the root directory - return nil + for _, d := range rootDir { + if d.IsDir() || !strings.HasSuffix(d.Name(), ".star") { + // only process Starlark files + continue } - if !strings.HasSuffix(pathToLoad, ".star") { - // not a starlark file - return nil + if err := a.ensureLoaded(fsys, d.Name()); err != nil { + return err } - - return a.ensureLoaded(fsys, pathToLoad) - }); err != nil { - return err } if a.mainFun == nil { diff --git a/schema/schema.go b/schema/schema.go index 63527edf30..dfe9caf7ee 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -39,7 +39,7 @@ type SchemaField struct { Name string `json:"name,omitempty" validate:"required_for=datetime dropdown location locationbased onoff radio text typeahead png"` Description string `json:"description,omitempty"` Icon string `json:"icon,omitempty" validate:"forbidden_for=generated"` - Visibility *SchemaVisibility `json:"visibility,omitempty" validate:"omitempty,dive"` + Visibility *SchemaVisibility `json:"visibility,omitempty" validate:"omitempty"` Default string `json:"default,omitempty" validate:"required_for=dropdown onoff radio"` Options []SchemaOption `json:"options,omitempty" validate:"required_for=dropdown radio,dive"` diff --git a/tools/repo/repo.go b/tools/repo/repo.go index abd48a4bbb..f79e49c1a6 100644 --- a/tools/repo/repo.go +++ b/tools/repo/repo.go @@ -5,8 +5,6 @@ import ( "github.com/gitsight/go-vcsurl" "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/object" ) // IsInRepo determines if the provided directory is in the provided git @@ -60,65 +58,3 @@ func RepoRoot(dir string) (string, error) { return worktree.Filesystem.Root(), nil } - -func DetermineChanges(dir string, oldCommit string, newCommit string) ([]string, error) { - // Load the repo. - repo, err := git.PlainOpenWithOptions(dir, &git.PlainOpenOptions{ - DetectDotGit: true, - }) - if err != nil { - return nil, fmt.Errorf("couldn't instantiate repo: %w", err) - } - - // Do a bunch of plumbing to get these commits usable for go-git - oldHash, err := repo.ResolveRevision(plumbing.Revision(oldCommit)) - if err != nil { - return nil, fmt.Errorf("couldn't parse old commit: %w", err) - } - newHash, err := repo.ResolveRevision(plumbing.Revision(newCommit)) - if err != nil { - return nil, fmt.Errorf("couldn't parse new commit: %w", err) - } - old, err := repo.CommitObject(*oldHash) - if err != nil { - return nil, fmt.Errorf("couldn't find old commit: %w", err) - } - new, err := repo.CommitObject(*newHash) - if err != nil { - return nil, fmt.Errorf("couldn't find new commit: %w", err) - } - oldTree, err := old.Tree() - if err != nil { - return nil, fmt.Errorf("couldn't generate tree for old commit: %w", err) - } - newTree, err := new.Tree() - if err != nil { - return nil, fmt.Errorf("couldn't generate tree for new commit: %w", err) - } - - // Diff the two trees to determine what changed. - changes, err := oldTree.Diff(newTree) - if err != nil { - return nil, fmt.Errorf("couldn't get changes between commits: %w", err) - } - - // Create a unique list of changed files. - changedFiles := []string{} - for _, change := range changes { - changedFiles = append(changedFiles, getChangeName(change)) - } - - return changedFiles, nil -} - -func getChangeName(change *object.Change) string { - var empty = object.ChangeEntry{} - - // Use the To field for Inserts and Modifications. - if change.To != empty { - return change.To.Name - } - - // The From field for Deletes. - return change.From.Name -}