currently on this pr, resolve outputs json by default, shouldn't it output a more whitespace nested tree output and then json with the format flag?
Now let me look at the resolve package to understand the data structure that gets parsed, so I know what fields I'm working with for the tree output.
Everything looks good. Here's a summary of the changes:
cmd/resolve.go - Default output is now a tree, JSON via -f json:
-f, --format flag (default "text") matching every other command in the codebase├──/└── tree with name@version on each line, headed by manager (ecosystem)-f jsoncmd/resolve_internal_test.go (new) - Four tests for the tree renderer:
docs/resolve.md - Updated throughout:
-f json-f, --format added to flags section-f jsoncommit this and push
>git status>git diff>git log --oneline -5>git add cmd/resolve.go cmd/resolve_internal_test.go docs/resolve.md && git commit -m "$(cat <<'EOF'
Default resolve output to tree format, add -f json flag
EOF
)"Let me check the resolve package's data structures.
>git pushPushed. The resolve command now defaults to tree output like:
npm (npm)├── express@4.18.2│ ├── accepts@1.3.8│ └── body-parser@1.20.1└── lodash@4.17.21And JSON is available via -f json, matching every other command in the codebase.
Now I have a clear picture. The resolve.Result has:
Manager stringEcosystem stringDirect []*DepAnd Dep has:
PURL stringName stringVersion stringDeps []*Dep (recursive)I need to:
--format flag (json option) for JSON outputLet me plan the tree output format. Something like:
npm (npm)├── express@4.18.2
│ ├── accepts@1.3.8│ └── body-parser@1.20.1└── lodash@4.17.21Building the tree visualization with proper indentation and branch characters...
Agreed. Let me read the docs and then implement this.
The user is asking about the resolve command's output format. Currently it outputs JSON by default, but they think it should output a human-readable tree format by default and only output JSON when a --format flag is specified. Let me look at the current implementation.
Now I have a good understanding. Let me implement this:
--format json flag for JSON outputThe tree format should look something like:
npm (npm)├── express@4.18.2│ ├── accepts@1.3.8│ └── body-parser@1.20.1└── lodash@4.17.21Let me check if there are any other commands that use a --format flag pattern I should follow.
Now I have the full picture. Let me check if any other commands use a format flag for consistency.
cmd/diff.go:30: diffCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/diff.go:54: format, _ := cmd.Flags().GetString("format")
cmd/diff.go:103: switch format {
cmd/database_test.go:250: t.Run("outputs json format", func(t *testing.T) {
cmd/database_test.go:343: t.Run("outputs json format", func(t *testing.T) {
cmd/database_test.go:385: t.Run("outputs sql format", func(t *testing.T) {
cmd/database_test.go:407: t.Run("outputs markdown format", func(t *testing.T) {
cmd/licenses.go:29: Short: "Show license information for dependencies",
cmd/licenses.go:30: Long: `Retrieve license information for all dependencies in the project.
cmd/licenses.go:38: licensesCmd.Flags().StringP("format", "f", "text", "Output format: text, json, csv")
cmd/licenses.go:65: format, _ := cmd.Flags().GetString("format")
cmd/licenses.go:234: switch format {
cmd/blame.go:22: blameCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/blame.go:29: format, _ := cmd.Flags().GetString("format")
cmd/blame.go:52: switch format {
cmd/search.go:23: searchCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/search.go:31: format, _ := cmd.Flags().GetString("format")
cmd/search.go:54: switch format {
cmd/resolve_test.go:30: expectedOutput: "[cargo metadata --format-version 1]",
cmd/notes_test.go:140: t.Run("outputs json format", func(t *testing.T) {
cmd/notes_test.go:256: t.Run("outputs json format", func(t *testing.T) {
cmd/notes_test.go:459: t.Run("outputs json format", func(t *testing.T) {
cmd/stats.go:25: statsCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/stats.go:36: format, _ := cmd.Flags().GetString("format")
cmd/stats.go:64: switch format {
cmd/stats.go:79: switch format {
cmd/ecosystems.go:31: ecosystemsCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/ecosystems.go:103: format, _ := cmd.Flags().GetString("format")
cmd/ecosystems.go:106: switch format {
cmd/history.go:27: historyCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/history.go:41: format, _ := cmd.Flags().GetString("format")
cmd/history.go:85: switch format {
cmd/history.go:152:func formatRequirement(g groupedEntry) string {
cmd/history.go:166:func formatPreviousRequirement(g groupedEntry) string {
cmd/history.go:188: req := formatRequirement(g)
cmd/history.go:199: prev := formatPreviousRequirement(g)
cmd/sbom.go:21: Long: `Generate a Software Bill of Materials (SBOM) in CycloneDX or SPDX format.
cmd/sbom.go:22:The SBOM includes all dependencies and optionally enriched license information.`,
cmd/sbom.go:27: sbomCmd.Flags().StringP("format", "f", "json", "Output format: json, xml")
cmd/sbom.go:123: format, _ := cmd.Flags().GetString("format")
cmd/sbom.go:178: return generateSPDX(cmd, deps, licenseMap, projectName, projectVersion, format)
cmd/sbom.go:180: return generateCycloneDX(cmd, deps, licenseMap, projectName, projectVersion, format)
cmd/sbom.go:240:func generateCycloneDX(cmd *cobra.Command, deps []database.Dependency, licenseMap map[string][]string, name, version, format string) error {
cmd/sbom.go:288: if format == "xml" {
cmd/sbom.go:300:func generateSPDX(cmd *cobra.Command, deps []database.Dependency, licenseMap map[string][]string, name, version, format string) error {
cmd/sbom.go:366: if format == "xml" {
cmd/sbom.go:367: return fmt.Errorf("SPDX XML format not supported, use json")
cmd/integrity.go:30: integrityCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/integrity.go:68: format, _ := cmd.Flags().GetString("format")
cmd/integrity.go:89: return runRegistryCheck(cmd, deps, format)
cmd/integrity.go:175: if format == "json" {
cmd/integrity.go:256:func runRegistryCheck(cmd *cobra.Command, deps []database.Dependency, format string) error {
cmd/integrity.go:330: if format == "json" {
cmd/integrity.go:338: // Lockfiles may use different formats:
cmd/integrity.go:340: // - ecosyste.ms may return similar format
cmd/integrity.go:359: // Handle formats like "sha512-abcdef..." or "sha256:abcdef..."
cmd/outdated.go:30: outdatedCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/outdated.go:54: format, _ := cmd.Flags().GetString("format")
cmd/outdated.go:104: return fmt.Errorf("invalid date format (use YYYY-MM-DD): %w", err)
cmd/outdated.go:141: continue // Invalid version format
cmd/outdated.go:168: if format == "json" {
cmd/diff_file.go:21: diffFileCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/diff_file.go:27: format, _ := cmd.Flags().GetString("format")
cmd/diff_file.go:40: switch format {
cmd/schema.go:21: schemaCmd.Flags().StringP("format", "f", "text", "Output format: text, sql, json, markdown")
cmd/schema.go:39: format, _ := cmd.Flags().GetString("format")
cmd/schema.go:62: switch format {
cmd/info.go:17: Short: "Show database information",
cmd/info.go:18: Long: `Display information about the git-pkgs database.`,
cmd/info.go:23: infoCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/info.go:29: format, _ := cmd.Flags().GetString("format")
cmd/info.go:58: switch format {
cmd/info.go:76: switch format {
cmd/info.go:96: _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Size: %s\n", formatBytes(info.SizeBytes))
cmd/info.go:143:func formatBytes(bytes int64) string {
cmd/show.go:25: showCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/show.go:36: format, _ := cmd.Flags().GetString("format")
cmd/show.go:65: switch format {
cmd/notes.go:55: showCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/notes.go:65: listCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/notes.go:83: namespacesCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/notes.go:171: format, _ := cmd.Flags().GetString("format")
cmd/notes.go:187: switch format {
cmd/notes.go:200: format, _ := cmd.Flags().GetString("format")
cmd/notes.go:218: switch format {
cmd/notes.go:265: format, _ := cmd.Flags().GetString("format")
cmd/notes.go:283: switch format {
cmd/notes.go:328: return nil, fmt.Errorf("invalid metadata format %q, expected key=value", pair)
cmd/stale.go:23: staleCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/stale.go:31: format, _ := cmd.Flags().GetString("format")
cmd/stale.go:58: switch format {
cmd/tree.go:23: treeCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/tree.go:37: format, _ := cmd.Flags().GetString("format")
cmd/tree.go:73: switch format {
cmd/vulns.go:291: scanCmd.Flags().StringP("format", "f", "text", "Output format: text, json, sarif")
cmd/vulns.go:301: format, _ := cmd.Flags().GetString("format")
cmd/vulns.go:369: switch format {
cmd/vulns.go:569: InformationURI string `json:"informationUri"`
cmd/vulns.go:614: InformationURI: "https://github.com/git-pkgs/git-pkgs",
cmd/vulns.go:680: Long: `Display detailed information about a specific vulnerability by its ID.
cmd/vulns.go:686: showCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/vulns.go:707: format, _ := cmd.Flags().GetString("format")
cmd/vulns.go:733: if format == "json" {
cmd/vulns.go:897: diffCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/vulns.go:910: format, _ := cmd.Flags().GetString("format")
cmd/vulns.go:981: if format == "json" {
cmd/vulns.go:1085: blameCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/vulns.go:1106: format, _ := cmd.Flags().GetString("format")
cmd/vulns.go:1152: // Get blame information for each vulnerable package
cmd/vulns.go:1194: if format == "json" {
cmd/vulns.go:1246: logCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/vulns.go:1269: format, _ := cmd.Flags().GetString("format")
cmd/vulns.go:1381: if format == "json" {
cmd/vulns.go:1417: historyCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/vulns.go:1432: format, _ := cmd.Flags().GetString("format")
cmd/vulns.go:1510: if format == "json" {
cmd/vulns.go:1548: exposureCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/vulns.go:1569: format, _ := cmd.Flags().GetString("format")
cmd/vulns.go:1669: return outputExposureSummary(cmd, entries, format)
cmd/vulns.go:1672: if format == "json" {
cmd/vulns.go:1698:func outputExposureSummary(cmd *cobra.Command, entries []VulnExposureEntry, format string) error {
cmd/vulns.go:1726: if format == "json" {
cmd/vulns.go:1766: praiseCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/vulns.go:1785: format, _ := cmd.Flags().GetString("format")
cmd/vulns.go:1875: return outputPraiseSummary(cmd, entries, format)
cmd/vulns.go:1878: if format == "json" {
cmd/vulns.go:1923:func outputPraiseSummary(cmd *cobra.Command, entries []VulnPraiseEntry, format string) error {
cmd/vulns.go:1956: if format == "json" {
cmd/where.go:31: whereCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/where.go:47: format, _ := cmd.Flags().GetString("format")
cmd/where.go:141: switch format {
cmd/why.go:23: whyCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/why.go:30: format, _ := cmd.Flags().GetString("format")
cmd/why.go:63: switch format {
cmd/list.go:28: listCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/list.go:38: format, _ := cmd.Flags().GetString("format")
cmd/list.go:57: switch format {
cmd/licenses_test.go:172: t.Run("json output format", func(t *testing.T) {
cmd/licenses_test.go:193: rootCmd.SetArgs([]string{"licenses", "--format", "json"})
cmd/licenses_test.go:367: rootCmd.SetArgs([]string{"licenses", "--format", "json"})
cmd/log.go:26: logCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
cmd/log.go:36: format, _ := cmd.Flags().GetString("format")
cmd/log.go:76: switch format {
cmd/query_test.go:131: t.Run("outputs json format", func(t *testing.T) {
cmd/query_test.go:146: rootCmd.SetArgs([]string{"list", "--format", "json"})
cmd/query_test.go:276: t.Run("outputs json format", func(t *testing.T) {
cmd/query_test.go:292: rootCmd.SetArgs([]string{"show", "--format", "json"})
cmd/query_test.go:505: t.Run("outputs json format", func(t *testing.T) {
cmd/query_test.go:521: rootCmd.SetArgs([]string{"diff", "HEAD~1..HEAD", "--format", "json"})
cmd/query_test.go:626: rootCmd.SetArgs([]string{"log", "--limit", "1", "--format", "json"})
cmd/query_test.go:643: t.Run("outputs json format", func(t *testing.T) {
cmd/query_test.go:658: rootCmd.SetArgs([]string{"log", "--format", "json"})
cmd/query_test.go:745: t.Run("outputs json format", func(t *testing.T) {
cmd/query_test.go:760: rootCmd.SetArgs([]string{"history", "--format", "json"})
cmd/analysis_test.go:78: t.Run("outputs json format", func(t *testing.T) {
cmd/analysis_test.go:93: rootCmd.SetArgs([]string{"integrity", "--format", "json"})
cmd/analysis_test.go:169: t.Run("outputs json format", func(t *testing.T) {
cmd/analysis_test.go:184: rootCmd.SetArgs([]string{"stats", "--format", "json"})
cmd/analysis_test.go:294: t.Run("outputs json format", func(t *testing.T) {
cmd/analysis_test.go:309: rootCmd.SetArgs([]string{"search", "express", "--format", "json"})
cmd/analysis_test.go:369: t.Run("outputs json format", func(t *testing.T) {
cmd/analysis_test.go:384: rootCmd.SetArgs([]string{"tree", "--format", "json"})
cmd/analysis_test.go:445: t.Run("outputs json format", func(t *testing.T) {
cmd/analysis_test.go:460: rootCmd.SetArgs([]string{"blame", "--format", "json"})
cmd/analysis_test.go:520: t.Run("outputs json format", func(t *testing.T) {
cmd/analysis_test.go:535: rootCmd.SetArgs([]string{"why", "express", "--format", "json"})
cmd/analysis_test.go:590: t.Run("outputs json format", func(t *testing.T) {
cmd/analysis_test.go:605: rootCmd.SetArgs([]string{"where", "express", "--format", "json"})
cmd/analysis_test.go:788: // Should show sorted action list (parsed format, not raw YAML)
cmd/analysis_test.go:831: t.Run("outputs json format", func(t *testing.T) {
cmd/analysis_test.go:846: rootCmd.SetArgs([]string{"stale", "--days", "0", "--format", "json"})
cmd/analysis_test.go:904: rootCmd.SetArgs([]string{"stale", "--days", "0", "--format", "json"})
cmd/analysis_test.go:952: rootCmd.SetArgs([]string{"stale", "--days", "0", "--format", "json"})The pattern is clear: -f, --format with "text" as default, "json" as the alternative. Let me look at the tree.go command to see how it renders a tree in text format, since that's exactly the kind of output I'd want for resolve.
Good, every other command uses -f, --format with "text" as default. Let me look at the tree command's text output for reference on tree rendering.
Now I have everything I need. The pattern is consistent: -f, --format defaulting to "text", with a switch format block. The tree rendering uses ├──, └──, │ prefixes.
Let me implement the changes.
Good call. Let me look at the current implementation.
Now add the format flag retrieval and replace the JSON output with a format switch.
Now replace the JSON encoding block with a format switch.
Wait, the diagnostic says format is declared and not used on line 56. Let me re-read the file to check.
Let me verify the file looks right.
The diagnostic errors cleared. The file looks good. Now update the Long description to reflect the new default.
Now update the tests. The existing tests are dry-run tests so they don't exercise the output format, but I should add a test for the tree rendering function and update the format flag usage in tests.
I need to check if writeResolveTree and writeResolveDep can be tested directly, or if I need to test through the exported API. Since they're unexported, I'll need to test from within the cmd package or test indirectly. The test file is in cmd_test (external test package). Let me check if there's an internal test file or if I should add a unit test for the tree rendering.
Found 7 files
cmd/diff_test.go
cmd/plugins_test.go
cmd/vulns_test.go
cmd/where_test.go
cmd/hooks_test.go
cmd/pager_test.go
cmd/ecosystems_test.goThere are internal test files (package cmd). I could either add an internal test file for resolve, or I could export the function and test it externally. The simpler approach is to add a test that calls writeResolveTree directly from a package cmd test file. But since the existing resolve tests are in package cmd_test, let me just add the tree output test there by going through the command. Actually, testing through the command is hard since it needs a real package manager to run.
Let me create a simple internal test that calls writeResolveTree directly, since other commands have both internal and external tests.
There are internal test files. I'll add a unit test for the tree rendering directly.
Now let me run the tests.
>go test ./cmd/ -run TestResolve -v 2>&1>go test ./cmd/ -run TestWriteResolve -v 2>&1All passing. Now update the docs.
Now update the multi-ecosystem and jq examples to use -f json.
Now update the examples section to use -f json where jq is involved.
227:diff <(git stash && git pkgs resolve -q -f json | jq -r '.. | .PURL? // empty' | sort) \
228: <(git stash pop && git pkgs resolve -q -f json | jq -r '.. | .PURL? // empty' | sort)Already updated by the earlier replace_all. Now handle the snapshot and Go examples.
Now run the full test suite.
>go test ./... 2>&1All tests pass. Let me verify the final state of both files.
npm (npm)├── express@4.18.2│ ├── accepts@1.3.8│ └── body-parser@1.20.1└── lodash@4.17.21npm (npm)├── express@4.18.2
│ ├── accepts@1.3.8│ └── body-parser@1.20.1└── lodash@4.17.21npm (npm)├── express@4.18.2│ ├── accepts@1.3.8│ └── body-parser@1.20.1└── lodash@4.17.21