diff --git a/completers/common/pnpm_completer/cmd/catFile.go b/completers/common/pnpm_completer/cmd/catFile.go new file mode 100644 index 0000000000..ee618e5369 --- /dev/null +++ b/completers/common/pnpm_completer/cmd/catFile.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "github.com/carapace-sh/carapace" + "github.com/spf13/cobra" +) + +var catFileCmd = &cobra.Command{ + Use: "cat-file", + Short: "Prints the contents of a file based on the hash value stored in the index file", + Run: func(cmd *cobra.Command, args []string) {}, +} + +func init() { + carapace.Gen(catFileCmd).Standalone() + + catFileCmd.Flags().BoolP("help", "h", false, "Output usage information") + + rootCmd.AddCommand(catFileCmd) +} diff --git a/completers/common/pnpm_completer/cmd/catIndex.go b/completers/common/pnpm_completer/cmd/catIndex.go new file mode 100644 index 0000000000..d933da37cf --- /dev/null +++ b/completers/common/pnpm_completer/cmd/catIndex.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "github.com/carapace-sh/carapace" + "github.com/spf13/cobra" +) + +var catIndexCmd = &cobra.Command{ + Use: "cat-index", + Short: "Prints the index file of a specific package from the store", + Run: func(cmd *cobra.Command, args []string) {}, +} + +func init() { + carapace.Gen(catIndexCmd).Standalone() + + catIndexCmd.Flags().BoolP("help", "h", false, "Output usage information") + + rootCmd.AddCommand(catIndexCmd) +} diff --git a/completers/common/pnpm_completer/cmd/exec.go b/completers/common/pnpm_completer/cmd/exec.go index 523e08ab23..4ce92e77a9 100644 --- a/completers/common/pnpm_completer/cmd/exec.go +++ b/completers/common/pnpm_completer/cmd/exec.go @@ -2,24 +2,46 @@ package cmd import ( "github.com/carapace-sh/carapace" - "github.com/carapace-sh/carapace-bridge/pkg/actions/bridge" + "github.com/carapace-sh/carapace-bin/pkg/actions/tools/pnpm" "github.com/spf13/cobra" ) var execCmd = &cobra.Command{ - Use: "exec", - Short: "Executes a shell command in scope of a project", - GroupID: "run", - Run: func(cmd *cobra.Command, args []string) {}, - DisableFlagParsing: true, + Use: "exec", + Short: "Executes a shell command in scope of a project", + GroupID: "run", + Run: func(cmd *cobra.Command, args []string) {}, } func init() { carapace.Gen(execCmd).Standalone() + execCmd.Flags().Bool("aggregate-output", false, "Aggregate output from child processes that are run in parallel") + execCmd.Flags().String("changed-files-ignore-pattern", "", "Defines files to ignore when filtering for changed projects") + execCmd.Flags().Bool("color", false, "Controls colors in the output") + execCmd.Flags().StringP("dir", "C", "", "Change to directory ") + execCmd.Flags().String("filter", "", "set filter") + execCmd.Flags().String("filter-prod", "", "Restricts the scope to package names matching the given pattern") + execCmd.Flags().BoolP("help", "h", false, "Output usage information") + execCmd.Flags().String("loglevel", "", "What level of logs to report") + execCmd.Flags().Bool("no-bail", false, "The command will exit with a 0 exit code even if the script fails") + execCmd.Flags().Bool("no-color", false, "Controls colors in the output") + execCmd.Flags().Bool("parallel", false, "Completely disregard concurrency and topological sorting") + execCmd.Flags().BoolP("recursive", "r", false, "Run the defined package script in every package found in subdirectories") + execCmd.Flags().Bool("report-summary", false, "Save the execution results of every package to \"pnpm-exec-summary.json\"") + execCmd.Flags().Bool("resume-from", false, "Command executed from given package") + execCmd.Flags().Bool("sequential", false, "Run the specified scripts one by one") + execCmd.Flags().Bool("stream", false, "Stream output from child processes immediately") + execCmd.Flags().String("test-pattern", "", "Defines files related to tests.") + execCmd.Flags().Bool("use-stderr", false, "Divert all output to stderr") + execCmd.Flags().Bool("workspace-root", false, "Run the command on the root workspace project") + addWorkspaceFlags(execCmd) rootCmd.AddCommand(execCmd) - carapace.Gen(execCmd).PositionalAnyCompletion( - bridge.ActionCarapaceBin(), - ) + carapace.Gen(execCmd).FlagCompletion(carapace.ActionMap{ + "dir": carapace.ActionDirectories(), + "filter": pnpm.ActionFilter(), + "filter-prod": pnpm.ActionFilter(), + "loglevel": pnpm.ActionLoglevel(), + }) } diff --git a/completers/common/pnpm_completer/cmd/findHash.go b/completers/common/pnpm_completer/cmd/findHash.go new file mode 100644 index 0000000000..6063325a4a --- /dev/null +++ b/completers/common/pnpm_completer/cmd/findHash.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "github.com/carapace-sh/carapace" + "github.com/spf13/cobra" +) + +var findHashCmd = &cobra.Command{ + Use: "find-hash", + Short: "Experimental! Lists the packages that include the file with the specified hash", + Run: func(cmd *cobra.Command, args []string) {}, +} + +func init() { + carapace.Gen(findHashCmd).Standalone() + + findHashCmd.Flags().BoolP("help", "h", false, "Output usage information") + + rootCmd.AddCommand(findHashCmd) +} diff --git a/completers/common/pnpm_completer/cmd/help.go b/completers/common/pnpm_completer/cmd/help.go new file mode 100644 index 0000000000..8889f92a4d --- /dev/null +++ b/completers/common/pnpm_completer/cmd/help.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "github.com/carapace-sh/carapace" + "github.com/spf13/cobra" +) + +var helpCmd = &cobra.Command{ + Use: "help", + Short: "Show help for pnpm", + Run: func(cmd *cobra.Command, args []string) {}, +} + +func init() { + carapace.Gen(helpCmd).Standalone() + + helpCmd.Flags().BoolP("help", "h", false, "Output usage information") + + rootCmd.AddCommand(helpCmd) +} diff --git a/completers/common/pnpm_completer/cmd/init.go b/completers/common/pnpm_completer/cmd/init.go new file mode 100644 index 0000000000..d180e82d3a --- /dev/null +++ b/completers/common/pnpm_completer/cmd/init.go @@ -0,0 +1,28 @@ +package cmd + +import ( + "github.com/carapace-sh/carapace" + "github.com/spf13/cobra" +) + +var initCmd = &cobra.Command{ + Use: "init", + Short: "Create a package.json file", + Run: func(cmd *cobra.Command, args []string) {}, +} + +func init() { + carapace.Gen(initCmd).Standalone() + + initCmd.Flags().BoolP("help", "h", false, "Output usage information") + initCmd.Flags().String("name", "", "Set the name field in package.json") + initCmd.Flags().String("version", "", "Set the version field in package.json") + initCmd.Flags().String("description", "", "Set the description field in package.json") + initCmd.Flags().String("author", "", "Set the author field in package.json") + initCmd.Flags().String("license", "", "Set the license field in package.json") + initCmd.Flags().String("homepage", "", "Set the homepage field in package.json") + initCmd.Flags().String("repository", "", "Set the repository field in package.json") + initCmd.Flags().String("keywords", "", "Set the keywords field in package.json") + + rootCmd.AddCommand(initCmd) +} diff --git a/completers/common/pnpm_completer/cmd/rebuild.go b/completers/common/pnpm_completer/cmd/rebuild.go index 3241209c56..5d79a4b582 100644 --- a/completers/common/pnpm_completer/cmd/rebuild.go +++ b/completers/common/pnpm_completer/cmd/rebuild.go @@ -42,6 +42,9 @@ func init() { }) carapace.Gen(rebuildCmd).PositionalAnyCompletion( - pnpm.ActionDependencies(), + carapace.Batch( + pnpm.ActionDependencies(), + pnpm.ActionWorkspaceDependencies(), + ).ToA(), ) } diff --git a/completers/common/pnpm_completer/cmd/remove.go b/completers/common/pnpm_completer/cmd/remove.go index 2686644165..24e37f2b62 100644 --- a/completers/common/pnpm_completer/cmd/remove.go +++ b/completers/common/pnpm_completer/cmd/remove.go @@ -45,6 +45,9 @@ func init() { }) carapace.Gen(removeCmd).PositionalAnyCompletion( - pnpm.ActionDependencies(), + carapace.Batch( + pnpm.ActionDependencies(), + pnpm.ActionWorkspaceDependencies(), + ).ToA(), ) } diff --git a/completers/common/pnpm_completer/cmd/root.go b/completers/common/pnpm_completer/cmd/root.go index 060d856107..6488ae9474 100644 --- a/completers/common/pnpm_completer/cmd/root.go +++ b/completers/common/pnpm_completer/cmd/root.go @@ -2,6 +2,7 @@ package cmd import ( "github.com/carapace-sh/carapace" + "github.com/carapace-sh/carapace-bin/pkg/actions/tools/pnpm" "github.com/spf13/cobra" ) @@ -15,6 +16,7 @@ var rootCmd = &cobra.Command{ func Execute() error { return rootCmd.Execute() } + func init() { carapace.Gen(rootCmd).Standalone() @@ -23,9 +25,33 @@ func init() { &cobra.Group{ID: "review", Title: "Review Commands"}, &cobra.Group{ID: "run", Title: "Run Commands"}, &cobra.Group{ID: "store", Title: "Store Commands"}, + &cobra.Group{ID: "other", Title: "Other Commands"}, ) rootCmd.Flags().BoolP("help", "h", false, "show help") rootCmd.Flags().BoolP("recursive", "r", false, "Run the command for each project in the workspace") rootCmd.Flags().BoolP("version", "v", false, "show version") + rootCmd.Flags().String("filter", "", "set filter") + rootCmd.Flags().String("filter-prod", "", "Restricts the scope to package names matching the given pattern") + rootCmd.Flags().String("loglevel", "", "What level of logs to report") + rootCmd.Flags().Bool("color", false, "Controls colors in the output") + rootCmd.Flags().Bool("no-color", false, "Controls colors in the output") + + carapace.Gen(rootCmd).FlagCompletion(carapace.ActionMap{ + "filter": pnpm.ActionFilter(), + "filter-prod": pnpm.ActionFilter(), + "loglevel": pnpm.ActionLoglevel(), + }) +} + +func addWorkspaceFlags(cmd *cobra.Command) { + cmd.Flags().StringArrayP("workspace", "w", []string{""}, "Enable running a command in the context of the given workspace") + cmd.Flags().Bool("workspaces", false, "Enable running a command in the context of all workspaces") + + carapace.Gen(cmd).FlagCompletion(carapace.ActionMap{ + "workspace": carapace.Batch( + pnpm.ActionWorkspaces(), + pnpm.ActionWorkspaceDependencies(), + ).ToA(), + }) } diff --git a/completers/common/pnpm_completer/cmd/root_root.go b/completers/common/pnpm_completer/cmd/root_root.go index d0e645f626..9d977a1e80 100644 --- a/completers/common/pnpm_completer/cmd/root_root.go +++ b/completers/common/pnpm_completer/cmd/root_root.go @@ -5,15 +5,16 @@ import ( "github.com/spf13/cobra" ) -var root_rootCmd = &cobra.Command{ +var rootRootCmd = &cobra.Command{ Use: "root", - Short: "Print the effective `node_modules` directory", + Short: "Print the effective modules directory", Run: func(cmd *cobra.Command, args []string) {}, } func init() { - carapace.Gen(root_rootCmd).Standalone() + carapace.Gen(rootRootCmd).Standalone() - root_rootCmd.Flags().BoolP("global", "g", false, "Print the global `node_modules` directory") - rootCmd.AddCommand(root_rootCmd) + rootRootCmd.Flags().BoolP("help", "h", false, "Output usage information") + + rootCmd.AddCommand(rootRootCmd) } diff --git a/completers/common/pnpm_completer/cmd/run.go b/completers/common/pnpm_completer/cmd/run.go index abf375386b..be92e1c6f0 100644 --- a/completers/common/pnpm_completer/cmd/run.go +++ b/completers/common/pnpm_completer/cmd/run.go @@ -46,5 +46,10 @@ func init() { "loglevel": pnpm.ActionLoglevel(), }) - // TODO complete scripts + carapace.Gen(runCmd).PositionalCompletion( + carapace.Batch( + pnpm.ActionScripts(), + pnpm.ActionWorkspaceScripts(), + ).ToA(), + ) } diff --git a/completers/common/pnpm_completer/cmd/start.go b/completers/common/pnpm_completer/cmd/start.go index 5c6106eec8..4f399c3376 100644 --- a/completers/common/pnpm_completer/cmd/start.go +++ b/completers/common/pnpm_completer/cmd/start.go @@ -2,20 +2,46 @@ package cmd import ( "github.com/carapace-sh/carapace" + "github.com/carapace-sh/carapace-bin/pkg/actions/tools/pnpm" "github.com/spf13/cobra" ) var startCmd = &cobra.Command{ - Use: "start", - Short: "Runs an arbitrary command specified in the package's \"start\" property", - GroupID: "run", - Run: func(cmd *cobra.Command, args []string) {}, + Use: "start", + Short: "Runs an arbitrary command specified in the package's start property of its scripts object", + Run: func(cmd *cobra.Command, args []string) {}, } func init() { carapace.Gen(startCmd).Standalone() + startCmd.Flags().Bool("aggregate-output", false, "Aggregate output from child processes that are run in parallel") + startCmd.Flags().String("changed-files-ignore-pattern", "", "Defines files to ignore when filtering for changed projects") + startCmd.Flags().Bool("color", false, "Controls colors in the output") + startCmd.Flags().StringP("dir", "C", "", "Change to directory ") + startCmd.Flags().String("filter", "", "set filter") + startCmd.Flags().String("filter-prod", "", "Restricts the scope to package names matching the given pattern") + startCmd.Flags().BoolP("help", "h", false, "Output usage information") + startCmd.Flags().Bool("if-present", false, "Avoid exiting with a non-zero exit code when the script is undefined") + startCmd.Flags().String("loglevel", "", "What level of logs to report") + startCmd.Flags().Bool("no-bail", false, "The command will exit with a 0 exit code even if the script fails") + startCmd.Flags().Bool("no-color", false, "Controls colors in the output") + startCmd.Flags().Bool("parallel", false, "Completely disregard concurrency and topological sorting") + startCmd.Flags().BoolP("recursive", "r", false, "Run the defined package script in every package found in subdirectories") + startCmd.Flags().Bool("report-summary", false, "Save the execution results of every package to \"pnpm-exec-summary.json\"") + startCmd.Flags().Bool("resume-from", false, "Command executed from given package") + startCmd.Flags().Bool("sequential", false, "Run the specified scripts one by one") + startCmd.Flags().Bool("stream", false, "Stream output from child processes immediately") + startCmd.Flags().String("test-pattern", "", "Defines files related to tests.") + startCmd.Flags().Bool("use-stderr", false, "Divert all output to stderr") + startCmd.Flags().Bool("workspace-root", false, "Run the command on the root workspace project") + addWorkspaceFlags(startCmd) rootCmd.AddCommand(startCmd) - // TODO positional completion + carapace.Gen(startCmd).FlagCompletion(carapace.ActionMap{ + "dir": carapace.ActionDirectories(), + "filter": pnpm.ActionFilter(), + "filter-prod": pnpm.ActionFilter(), + "loglevel": pnpm.ActionLoglevel(), + }) } diff --git a/completers/common/pnpm_completer/cmd/update.go b/completers/common/pnpm_completer/cmd/update.go index 353549c3c7..a260d796ab 100644 --- a/completers/common/pnpm_completer/cmd/update.go +++ b/completers/common/pnpm_completer/cmd/update.go @@ -50,6 +50,9 @@ func init() { }) carapace.Gen(updateCmd).PositionalAnyCompletion( - pnpm.ActionDependencyNames(), + carapace.Batch( + pnpm.ActionDependencyNames(), + pnpm.ActionWorkspaceDependencies(), + ).ToA(), ) } diff --git a/pkg/actions/tools/pnpm/config.go b/pkg/actions/tools/pnpm/config.go new file mode 100644 index 0000000000..b876766b3b --- /dev/null +++ b/pkg/actions/tools/pnpm/config.go @@ -0,0 +1,39 @@ +package pnpm + +import ( + "encoding/json" + + "github.com/carapace-sh/carapace" +) + +// ActionLocalConfigKeys completes local config keys +func ActionLocalConfigKeys() carapace.Action { + return actionConfigKeys(false) +} + +// ActionGlobalConfigKeys completes global config keys +func ActionGlobalConfigKeys() carapace.Action { + return actionConfigKeys(true) +} + +func actionConfigKeys(global bool) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + args := []string{"config", "list", "--json"} + if global { + args = append(args, "--global") + } + + return carapace.ActionExecCommand("pnpm", args...)(func(output []byte) carapace.Action { + var config map[string]interface{} + if err := json.Unmarshal(output, &config); err != nil { + return carapace.ActionMessage(err.Error()) + } + + vals := make([]string, 0, len(config)) + for key := range config { + vals = append(vals, key) + } + return carapace.ActionValues(vals...) + }) + }).Tag("config keys") +} diff --git a/pkg/actions/tools/pnpm/module.go b/pkg/actions/tools/pnpm/module.go new file mode 100644 index 0000000000..1c022eb218 --- /dev/null +++ b/pkg/actions/tools/pnpm/module.go @@ -0,0 +1,46 @@ +package pnpm + +import ( + "fmt" + "os" + "strings" + + "github.com/carapace-sh/carapace" + "github.com/carapace-sh/carapace/pkg/util" +) + +func nodeModulesPath(c carapace.Context) (string, error) { + return util.FindReverse(c.Dir, "node_modules") +} + +// ActionModules completes modules +func ActionModules() carapace.Action { + return carapace.ActionMultiParts("/", func(c carapace.Context) carapace.Action { + path, err := nodeModulesPath(c) + if err != nil { + return carapace.ActionMessage(err.Error()) + } + + fullSegments := make([]string, 0) + for _, part := range c.Parts { + if strings.HasPrefix(part, "@") { + fullSegments = append(fullSegments, part) + } else { + fullSegments = append(fullSegments, part, "node_modules") + } + } + + contents, err := os.ReadDir(fmt.Sprintf("%v/%v", path, strings.Join(fullSegments, "/"))) + if err != nil { + return carapace.ActionValues() + } + + vals := make([]string, 0) + for _, content := range contents { + if content.IsDir() && !strings.HasPrefix(content.Name(), ".") { + vals = append(vals, content.Name()) + } + } + return carapace.ActionValues(vals...) + }) +} diff --git a/pkg/actions/tools/pnpm/owner.go b/pkg/actions/tools/pnpm/owner.go new file mode 100644 index 0000000000..69cc005af1 --- /dev/null +++ b/pkg/actions/tools/pnpm/owner.go @@ -0,0 +1,20 @@ +package pnpm + +import ( + "strings" + + "github.com/carapace-sh/carapace" +) + +// ActionOwners completes owners +func ActionOwners(pkg string) carapace.Action { + return carapace.ActionExecCommand("pnpm", "owner", "ls", pkg)(func(output []byte) carapace.Action { + lines := strings.Split(string(output), "\n") + + vals := make([]string, 0) + for _, line := range lines[:len(lines)-1] { + vals = append(vals, strings.SplitN(line, " ", 2)...) + } + return carapace.ActionValuesDescribed(vals...) + }) +} diff --git a/pkg/actions/tools/pnpm/package.go b/pkg/actions/tools/pnpm/package.go new file mode 100644 index 0000000000..fb6bc7902b --- /dev/null +++ b/pkg/actions/tools/pnpm/package.go @@ -0,0 +1,117 @@ +package pnpm + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/carapace-sh/carapace" + "github.com/carapace-sh/carapace/pkg/util" +) + +// ActionPackageSearch completes packages@version for given registry +func ActionPackageSearch(registry string) carapace.Action { + return carapace.ActionMultiParts("@", func(c carapace.Context) carapace.Action { + switch len(c.Parts) { + case 0: + return ActionPackageNames(registry).NoSpace() + case 1: + return ActionPackageVersions(PackageOpts{Registry: registry, Package: c.Parts[0]}) + default: + return carapace.ActionValues() + } + }) +} + +// ActionPackageNames completes package names for given registry +func ActionPackageNames(registry string) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + args := []string{"search", "--parseable", "--searchlimit", "250", fmt.Sprintf(`/^%v`, c.Value)} + if registry != "" { + args = append(args, "--registry", registry) + } + + // pnpm uses npm search under the hood, but we can also try pnpm-specific search + return carapace.ActionExecCommand("pnpm", args...)(func(output []byte) carapace.Action { + lines := strings.Split(string(output), "\n") + + vals := make([]string, 0) + for _, line := range lines[:len(lines)-1] { + fields := strings.Split(line, "\t") + if len(fields) >= 2 { + vals = append(vals, fields[0], fields[1]) + } + } + return carapace.ActionValuesDescribed(vals...) + }) + }) +} + +type PackageOpts struct { + Registry string + Package string +} + +// ActionPackageVersions completes versions for given package +func ActionPackageVersions(opts PackageOpts) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + args := []string{"view", opts.Package, "versions", "--json"} + if opts.Registry != "" { + args = append(args, "--registry", opts.Registry) + } + + // pnpm uses npm view under the hood + return carapace.ActionExecCommand("pnpm", args...)(func(output []byte) carapace.Action { + var versions []string + if err := json.Unmarshal(output, &versions); err != nil { + return carapace.ActionMessage(err.Error()) + } + return carapace.ActionValues(versions...) + }) + }) +} + +// ActionPackageTags completes tags for given package +func ActionPackageTags(opts PackageOpts) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + args := []string{"view", opts.Package, "dist-tags", "--json"} + if opts.Registry != "" { + args = append(args, "--registry", opts.Registry) + } + + // pnpm uses npm view under the hood + return carapace.ActionExecCommand("pnpm", args...)(func(output []byte) carapace.Action { + var tags map[string]string + if err := json.Unmarshal(output, &tags); err != nil { + return carapace.ActionMessage(err.Error()) + } + + vals := make([]string, 0, len(tags)*2) + for tag, version := range tags { + vals = append(vals, tag, version) + } + return carapace.ActionValuesDescribed(vals...) + }) + }) +} + +type packageJson struct { + Scripts map[string]string + Workspaces []string + Dependencies map[string]string + DevDependencies map[string]string `json:"devDependencies"` + OptionalDependencies map[string]string `json:"optionalDependencies"` + PeerDependencies map[string]string `json:"peerDependencies"` +} + +func loadPackageJson(c carapace.Context) (pj packageJson, err error) { + var packageFile string + if packageFile, err = util.FindReverse(c.Dir, "package.json"); err == nil { + var content []byte + if content, err = os.ReadFile(packageFile); err == nil { + err = json.Unmarshal(content, &pj) + } + } + return +} diff --git a/pkg/actions/tools/pnpm/pnpm.go b/pkg/actions/tools/pnpm/pnpm.go index 12c2b30240..6936f31f72 100644 --- a/pkg/actions/tools/pnpm/pnpm.go +++ b/pkg/actions/tools/pnpm/pnpm.go @@ -1,13 +1,10 @@ package pnpm import ( - "encoding/json" - "fmt" "strings" "github.com/carapace-sh/carapace" "github.com/carapace-sh/carapace-bin/pkg/actions/tools/git" - "github.com/carapace-sh/carapace-bin/pkg/actions/tools/npm" ) // ActionFilter completes filter @@ -21,52 +18,93 @@ func ActionFilter() carapace.Action { c.Value = c.Value[index+1:] return carapace.ActionDirectories().Invoke(c).Prefix(prefix).ToA().NoSpace() } - return npm.ActionDependencies().NoSpace() + return carapace.Batch( + ActionWorkspaceFilter(), + ActionWorkspacePackages().NoSpace(), + ).ToA() }) } -type location struct { - Dependencies map[string]struct { - Version string - } -} - -// ActionDependencyNames completes dependency names +// ActionDependencyNames completes dependency names from package.json func ActionDependencyNames() carapace.Action { return carapace.ActionCallback(func(c carapace.Context) carapace.Action { - return carapace.ActionExecCommand("pnpm", "list", "--json")(func(output []byte) carapace.Action { - var locations []location - if err := json.Unmarshal(output, &locations); err != nil { - return carapace.ActionMessage(err.Error()) - } + pj, err := loadPackageJson(c) + if err != nil { + return carapace.ActionMessage(err.Error()) + } - vals := make([]string, 0) - for _, l := range locations { - for name := range l.Dependencies { - vals = append(vals, name) - } + vals := make([]string, 0) + seen := make(map[string]bool) + + for name := range pj.Dependencies { + if !seen[name] { + seen[name] = true + vals = append(vals, name) } - return carapace.ActionValues(vals...) - }) + } + for name := range pj.DevDependencies { + if !seen[name] { + seen[name] = true + vals = append(vals, name) + } + } + for name := range pj.OptionalDependencies { + if !seen[name] { + seen[name] = true + vals = append(vals, name) + } + } + return carapace.ActionValues(vals...).Tag("dependencies") }) } -// ActionDependencies ocmpletes dependencies with their version +// ActionDependencies completes dependencies with their version from package.json func ActionDependencies() carapace.Action { return carapace.ActionCallback(func(c carapace.Context) carapace.Action { - return carapace.ActionExecCommand("pnpm", "list", "--json")(func(output []byte) carapace.Action { - var locations []location - if err := json.Unmarshal(output, &locations); err != nil { - return carapace.ActionMessage(err.Error()) - } + pj, err := loadPackageJson(c) + if err != nil { + return carapace.ActionMessage(err.Error()) + } - vals := make([]string, 0) - for _, l := range locations { - for name, details := range l.Dependencies { - vals = append(vals, fmt.Sprintf("%v@%v", name, details.Version)) + vals := make([]string, 0) + seen := make(map[string]bool) + + // Helper to add dependencies with description (version) + addDeps := func(deps map[string]string) { + for name, version := range deps { + if !seen[name] { + seen[name] = true + vals = append(vals, name, version) } } - return carapace.ActionValues(vals...) - }).Invoke(c).ToMultiPartsA("@") + } + + addDeps(pj.Dependencies) + addDeps(pj.DevDependencies) + addDeps(pj.OptionalDependencies) + + return carapace.ActionValuesDescribed(vals...).Tag("dependencies") + }) +} + +// ActionStorePath completes store paths +func ActionStorePath() carapace.Action { + return carapace.ActionExecCommand("pnpm", "store", "path")(func(output []byte) carapace.Action { + path := strings.TrimSpace(string(output)) + return carapace.ActionValues(path) + }) +} + +// ActionStoreStatus completes store status information +func ActionStoreStatus() carapace.Action { + return carapace.ActionExecCommand("pnpm", "store", "status")(func(output []byte) carapace.Action { + lines := strings.Split(strings.TrimSpace(string(output)), "\n") + vals := make([]string, 0) + for _, line := range lines { + if line != "" { + vals = append(vals, line) + } + } + return carapace.ActionValues(vals...) }) } diff --git a/pkg/actions/tools/pnpm/script.go b/pkg/actions/tools/pnpm/script.go new file mode 100644 index 0000000000..c83887d8aa --- /dev/null +++ b/pkg/actions/tools/pnpm/script.go @@ -0,0 +1,18 @@ +package pnpm + +import "github.com/carapace-sh/carapace" + +// ActionScripts completes scripts +func ActionScripts() carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + if pj, err := loadPackageJson(c); err != nil { + return carapace.ActionMessage(err.Error()) + } else { + vals := make([]string, 0) + for name := range pj.Scripts { + vals = append(vals, name) + } + return carapace.ActionValues(vals...) + } + }) +} diff --git a/pkg/actions/tools/pnpm/store.go b/pkg/actions/tools/pnpm/store.go new file mode 100644 index 0000000000..614097d96c --- /dev/null +++ b/pkg/actions/tools/pnpm/store.go @@ -0,0 +1,40 @@ +package pnpm + +import ( + "strings" + + "github.com/carapace-sh/carapace" +) + +// ActionStorePackages completes packages in the store +func ActionStorePackages() carapace.Action { + return carapace.ActionExecCommand("pnpm", "store", "status")(func(output []byte) carapace.Action { + lines := strings.Split(strings.TrimSpace(string(output)), "\n") + vals := make([]string, 0) + for _, line := range lines { + if line != "" && !strings.Contains(line, "Packages:") && !strings.Contains(line, "Size:") { + vals = append(vals, line) + } + } + return carapace.ActionValues(vals...) + }) +} + +// ActionStorePrune completes store prune options +func ActionStorePrune() carapace.Action { + return carapace.ActionValues("--verify-store-integrity") +} + +// ActionStoreAdd completes store add options +func ActionStoreAdd() carapace.Action { + return carapace.ActionMultiParts("@", func(c carapace.Context) carapace.Action { + switch len(c.Parts) { + case 0: + return ActionPackageNames("").NoSpace() + case 1: + return ActionPackageVersions(PackageOpts{Package: c.Parts[0]}) + default: + return carapace.ActionValues() + } + }) +} diff --git a/pkg/actions/tools/pnpm/workspace.go b/pkg/actions/tools/pnpm/workspace.go new file mode 100644 index 0000000000..275e0fe1fc --- /dev/null +++ b/pkg/actions/tools/pnpm/workspace.go @@ -0,0 +1,89 @@ +package pnpm + +import ( + "encoding/json" + "os" + "strings" + + "github.com/carapace-sh/carapace" + "github.com/carapace-sh/carapace/pkg/util" +) + +type pnpmWorkspace struct { + Packages []string `json:"packages"` +} + +// ActionWorkspaces completes workspaces +func ActionWorkspaces() carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + // First try to find pnpm-workspace.yaml + if workspaceFile, err := util.FindReverse(c.Dir, "pnpm-workspace.yaml"); err == nil { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + content, err := os.ReadFile(workspaceFile) + if err != nil { + return carapace.ActionMessage(err.Error()) + } + + // Parse YAML-like structure for packages + lines := strings.Split(string(content), "\n") + vals := make([]string, 0) + inPackages := false + + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "packages:" { + inPackages = true + continue + } + if inPackages && strings.HasPrefix(line, "-") { + packagePath := strings.TrimSpace(strings.TrimPrefix(line, "-")) + vals = append(vals, packagePath) + } + } + + return carapace.ActionValues(vals...) + }) + } + + // Fallback to package.json workspaces + if pj, err := loadPackageJson(c); err != nil { + return carapace.ActionMessage(err.Error()) + } else { + return carapace.ActionValues(pj.Workspaces...) + } + }) +} + +// ActionWorkspacePackages completes packages within workspaces +func ActionWorkspacePackages() carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + return carapace.ActionExecCommandE("pnpm", "ls", "-r", "--json", "--depth", "-1")(func(output []byte, err error) carapace.Action { + if err != nil { + return carapace.ActionValues() + } + + var packages []struct { + Name string `json:"name"` + Version string `json:"version"` + Path string `json:"path"` + Private bool `json:"private"` + } + + if err := json.Unmarshal(output, &packages); err != nil { + return carapace.ActionMessage(err.Error()) + } + + vals := make([]string, 0, len(packages)*2) + for _, pkg := range packages { + if pkg.Name != "" && !pkg.Private { + desc := pkg.Version + if desc == "" { + desc = pkg.Path + } + vals = append(vals, pkg.Name, desc) + } + } + return carapace.ActionValuesDescribed(vals...).Tag("workspace packages") + }) + }) +} diff --git a/pkg/actions/tools/pnpm/workspace_specific.go b/pkg/actions/tools/pnpm/workspace_specific.go new file mode 100644 index 0000000000..ff808148bd --- /dev/null +++ b/pkg/actions/tools/pnpm/workspace_specific.go @@ -0,0 +1,105 @@ +package pnpm + +import ( + "encoding/json" + "os" + "path/filepath" + "strings" + + "github.com/carapace-sh/carapace" + "github.com/carapace-sh/carapace/pkg/util" +) + +// ActionWorkspaceScripts completes scripts available across all workspace packages +func ActionWorkspaceScripts() carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + workspaceFile, err := util.FindReverse(c.Dir, "pnpm-workspace.yaml") + if err != nil { + return ActionScripts() + } + + content, err := os.ReadFile(workspaceFile) + if err != nil { + return carapace.ActionMessage(err.Error()) + } + + workspaceDir := filepath.Dir(workspaceFile) + lines := strings.Split(string(content), "\n") + + vals := make([]string, 0) + seen := make(map[string]bool) + inPackages := false + + for _, line := range lines { + trimmed := strings.TrimSpace(line) + if trimmed == "packages:" { + inPackages = true + continue + } + if inPackages && strings.HasPrefix(trimmed, "-") { + pattern := strings.Trim(strings.TrimPrefix(trimmed, "-"), "\"' \t") + matches, err := filepath.Glob(filepath.Join(workspaceDir, pattern)) + if err != nil { + continue + } + for _, match := range matches { + data, err := os.ReadFile(filepath.Join(match, "package.json")) + if err != nil { + continue + } + var pj packageJson + if json.Unmarshal(data, &pj) == nil { + for name := range pj.Scripts { + if !seen[name] { + seen[name] = true + vals = append(vals, name) + } + } + } + } + } + } + return carapace.ActionValues(vals...).Tag("workspace scripts") + }) +} + +// ActionWorkspaceFilter completes common workspace filter patterns +func ActionWorkspaceFilter() carapace.Action { + return carapace.ActionValuesDescribed( + "...", "Include all dependents (recursive)", + "...^", "Include direct dependents only", + "^...", "Include all dependencies (recursive)", + "^...^", "Include direct dependencies only", + "*", "All packages", + "packages/*", "All packages in packages/", + "apps/*", "All packages in apps/", + "libs/*", "All packages in libs/", + "tools/*", "All packages in tools/", + ).Tag("workspace filter patterns") +} + +type location struct { + Dependencies map[string]struct { + Version string + } +} + +// ActionWorkspaceDependencies completes workspace package names with their versions +func ActionWorkspaceDependencies() carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + return carapace.ActionExecCommand("pnpm", "list", "--json", "--depth", "0")(func(output []byte) carapace.Action { + var locations []location + if err := json.Unmarshal(output, &locations); err != nil { + return carapace.ActionMessage(err.Error()) + } + + vals := make([]string, 0) + for _, l := range locations { + for name, details := range l.Dependencies { + vals = append(vals, name, details.Version) + } + } + return carapace.ActionValuesDescribed(vals...).Tag("workspace dependencies") + }) + }) +}