Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The application follows a command-line interface pattern using the Cobra library
- **landing.go**: Landing page management (show, update, remove) with REST API integration
- **vuln.go**: Vulnerability scanning for Julia packages via REST API
- **scan.go**: Trivy manifest scanning — discover and upload a Julia manifest (+ optional project file) and fetch results via the StaticAnalysis service. Manifest discovery recognizes every name Julia/Pkg accepts (`Manifest.toml`, `JuliaManifest.toml`, and version-specific `Manifest-v1.11.toml` / `JuliaManifest-v1.11.toml`); when a directory holds more than one, the user is prompted to select (defaults to the highest-precedence candidate). Project file is `Project.toml` or `JuliaProject.toml`
- **style.go**: terminal output helpers — `bold`/`dim` (ANSI only when stdout is a terminal, `NO_COLOR` is unset and `TERM` is not `dumb`; `colorEnabled`), `formatCount` (thousands separators), `sparkline`, rune-aware `truncate`/`pad`/`padLeft`, and `printField` (aligned `label value` lines). Text output is for people; scripts should use `--json`
- **git.go**: Git integration (clone, push, fetch, pull) with JuliaHub authentication
- **julia.go**: Julia installation and management
- **run.go**: Julia execution with JuliaHub configuration
Expand Down Expand Up @@ -126,6 +127,9 @@ go run . package dependency CSV --registry General
# Get package dependents (reverse dependencies)
go run . package dependents JuMP
go run . package dependents JuMP --indirect
go run . package dependents JuMP --json | jq .total
go run . package dependency JuMP --json
go run . package info JuMP --json
```

### Test registry operations
Expand Down Expand Up @@ -512,6 +516,9 @@ jh run setup
- Landing page `update` command accepts content inline as an argument, from a file via `--file`, or piped via stdin (priority: `--file` > arg > stdin)
- Landing page response uses custom JSON unmarshaling (`homepageResponse`) to handle `message` being either an object or a string
- Package search (`jh package search`) and info (`jh package info`) both try REST API (`/api/v1/ui/packages/info`) first, then fall back to GraphQL (`FilteredPackages` / `FilteredPackagesCount` via `/v1/graphql`) on failure; a warning is printed to stderr when the fallback is used
- `jh package info` adds `details` (`packageDetails`) to each match: the listing's `downloads_timeseries` plus the stable `pkg.json` (`fetchPackageDocs`, one request per match): homepage, release date, contributors, `doctype`/`documenter_errored`, and dependency/dependent counts (unique UUIDs, direct wins). A failed `pkg.json` fetch prints a warning on stderr and leaves those fields empty. `restToInfo` also carries `owner`, `is_app`, `is_dyad` and `downloads_30d` (sum of the 30 daily counts), so `jh package search --verbose` shows them too; the GraphQL fallback has no download data and omits `downloads_30d`
- Package text output: `jh package info` and `search --verbose` print a card per package (`printPackageCard`: header `name vX · registry · owner`, description, aligned fields, links including the JuliaHub page `https://<server>/ui/Packages/<registry>/<name>` from `packagePageURL`, install hint); `search` prints a table with STARS and DL/30 DAYS columns; `dependency` and `dependents` print `printPackageHeading`, the direct/indirect/total counts and tables, with standard libraries (no registry in pkg.json) shown as `stdlib`. `splitDependencies` collapses `deps` to one entry per UUID like `splitDependents`, for both text and `--json`. `--short` on `dependency`/`dependents` prints only `printPackageSummary` (`name vX · registry: <countsSummary>`); it is mutually exclusive with `--json`
- `jh package info --json` prints the exact matches in the same `{"results": [...], "total": N}` document (`writePackagesJSON`; no match → empty `results`). `jh package dependency --json` prints `dependenciesDoc` and `jh package dependents --json` prints `dependentsDoc` (counts always cover every dependent; `dependents` lists indirect ones only with `--indirect`). All go through `writeJSON`; errors from these three commands go to stderr
- REST API passes `--registries` as comma-separated registry names to the `registries` query param; GraphQL fallback passes registry IDs to the `registries` variable
- `fetchRegistries` in `registries.go` is used by `listRegistries`, `packageSearchCmd`, `packageInfoCmd`, and `packageDependencyCmd` to resolve registry names to IDs (for GraphQL) and names (for REST)
- Both REST and GraphQL package search/info paths produce identical output columns (Registry and Owner); GraphQL resolves registry names from the `registryIDs`/`registryNames` already in `PackageSearchParams` — no extra API call needed
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,16 +162,21 @@ go build -o jh .
- `--registries <names>` - Filter by registry names (comma-separated, e.g. `General,MyRegistry`)
- `--limit <n>` - Maximum results to return (default: 10)
- `--offset <n>` - Number of results to skip
- `jh package info <package-name>` - Get detailed information about a specific package (exact name match, case-insensitive)
- `jh package info <package-name>` - Get detailed information about a specific package (exact name match, case-insensitive): the listing fields (owner, stars, license, latest version, downloads over the last 30 days, app/Dyad type) plus the stable version's homepage, release date, dependency and dependent counts, contributors and docs build type
- `jh package info --registries General` - Search in specific registries only
- `jh package info --json` - Print only `{"results": [...], "total": N}` on stdout
- `jh package dependency <package-name>` - List package dependencies
- Default: Shows up to 10 direct dependencies (NAME, REGISTRY, UUID, VERSIONS)
- `jh package dependency --indirect` - Include both direct and indirect dependencies
- `jh package dependency --registry General` - Specify registry to use
- `jh package dependency --short` - Print one line: `JuMP v1.32.1 · General: 9 direct · 40 indirect · 49 total dependencies`
- `jh package dependency --json` - Print only `{"name", "version", "registry", "dependencies": [...]}` on stdout (`--indirect` adds the indirect ones)
- `jh package dependents <package-name>` - List packages that depend on a package (reverse dependencies)
- Default: Prints direct/indirect/total counts and lists the direct dependents (NAME, REGISTRY, UUID)
- `jh package dependents --indirect` - Also list indirect dependents
- `jh package dependents --registry General` - Specify registry to use
- `jh package dependents --short` - Print one line: `JuMP v1.32.1 · General: 335 direct · 94 indirect · 429 total dependents`
- `jh package dependents --json` - Print only `{"name", "version", "registry", "direct", "indirect", "total", "dependents": [...]}` on stdout; the counts always cover every dependent, `dependents` lists the indirect ones only with `--indirect`

### Registry Management (`jh registry`)

Expand Down
36 changes: 24 additions & 12 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -1006,16 +1006,17 @@ Shows comprehensive package information including:
- Documentation links

The package name must match exactly (case-insensitive).`,
Example: " jh package info DataFrames\n jh package info Plots\n jh package info CSV",
Example: " jh package info DataFrames\n jh package info Plots\n jh package info --json CSV",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
server, err := getServerFromFlagOrConfig(cmd)
if err != nil {
fmt.Printf("Failed to get server config: %v\n", err)
fmt.Fprintf(os.Stderr, "Failed to get server config: %v\n", err)
os.Exit(1)
}

packageName := args[0]
asJSON, _ := cmd.Flags().GetBool("json")
registryNamesStr, _ := cmd.Flags().GetString("registries")

// Fetch all registries from the API
Expand Down Expand Up @@ -1057,8 +1058,8 @@ The package name must match exactly (case-insensitive).`,
}
}

if err := getPackageInfo(server, packageName, registryIDs, registryNames); err != nil {
fmt.Printf("Failed to get package info: %v\n", err)
if err := getPackageInfo(server, packageName, registryIDs, registryNames, asJSON); err != nil {
fmt.Fprintf(os.Stderr, "Failed to get package info: %v\n", err)
os.Exit(1)
}
},
Expand All @@ -1076,21 +1077,23 @@ The command fetches dependency information from the package documentation
JSON endpoint. If a package exists in multiple registries, it uses the
first registry by default. You can specify a different registry using
the --registry flag.`,
Example: " jh package dependency DataFrames\n jh package dependency --indirect Plots\n jh package dependency --registry General CSV",
Example: " jh package dependency DataFrames\n jh package dependency --indirect Plots\n jh package dependency --registry General CSV\n jh package dependency --short JuMP\n jh package dependency --json JuMP",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
server, err := getServerFromFlagOrConfig(cmd)
if err != nil {
fmt.Printf("Failed to get server config: %v\n", err)
fmt.Fprintf(os.Stderr, "Failed to get server config: %v\n", err)
os.Exit(1)
}

packageName := args[0]
asJSON, _ := cmd.Flags().GetBool("json")
short, _ := cmd.Flags().GetBool("short")
registryName, _ := cmd.Flags().GetString("registry")
showIndirect, _ := cmd.Flags().GetBool("indirect")

if err := getPackageDependencies(server, packageName, registryName, showIndirect); err != nil {
fmt.Printf("Failed to get package dependencies: %v\n", err)
if err := getPackageDependencies(server, packageName, registryName, showIndirect, asJSON, short); err != nil {
fmt.Fprintf(os.Stderr, "Failed to get package dependencies: %v\n", err)
os.Exit(1)
}
},
Expand All @@ -1109,21 +1112,23 @@ The data comes from the same package documentation JSON endpoint as
jh package dependency, for the package's stable version. If a package
exists in multiple registries, it uses the first registry by default.
You can specify a different registry using the --registry flag.`,
Example: " jh package dependents JuMP\n jh package dependents --indirect DataFrames\n jh package dependents --registry General CSV",
Example: " jh package dependents JuMP\n jh package dependents --indirect DataFrames\n jh package dependents --registry General CSV\n jh package dependents --short JuMP\n jh package dependents --json JuMP",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
server, err := getServerFromFlagOrConfig(cmd)
if err != nil {
fmt.Printf("Failed to get server config: %v\n", err)
fmt.Fprintf(os.Stderr, "Failed to get server config: %v\n", err)
os.Exit(1)
}

packageName := args[0]
asJSON, _ := cmd.Flags().GetBool("json")
short, _ := cmd.Flags().GetBool("short")
registryName, _ := cmd.Flags().GetString("registry")
showIndirect, _ := cmd.Flags().GetBool("indirect")

if err := getPackageDependents(server, packageName, registryName, showIndirect); err != nil {
fmt.Printf("Failed to get package dependents: %v\n", err)
if err := getPackageDependents(server, packageName, registryName, showIndirect, asJSON, short); err != nil {
fmt.Fprintf(os.Stderr, "Failed to get package dependents: %v\n", err)
os.Exit(1)
}
},
Expand Down Expand Up @@ -2488,12 +2493,19 @@ func init() {
packageSearchCmd.Flags().Bool("verbose", false, "Show detailed package information")
packageInfoCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
packageInfoCmd.Flags().String("registries", "", "Filter by registry names (comma-separated, e.g., 'General,CustomRegistry')")
packageInfoCmd.Flags().Bool("json", false, "Print only a JSON document ({\"results\": [...], \"total\": N}) on stdout")
packageDependencyCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
packageDependencyCmd.Flags().String("registry", "", "Specify registry name (uses first registry if not specified)")
packageDependencyCmd.Flags().Bool("indirect", false, "Include indirect dependencies")
packageDependencyCmd.Flags().Bool("json", false, "Print only a JSON document ({\"name\", \"version\", \"registry\", \"dependencies\": [...]}) on stdout")
packageDependencyCmd.Flags().Bool("short", false, "Print only one line: the direct, indirect and total dependencies counts")
packageDependencyCmd.MarkFlagsMutuallyExclusive("short", "json")
packageDependentsCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
packageDependentsCmd.Flags().String("registry", "", "Specify registry name (uses first registry if not specified)")
packageDependentsCmd.Flags().Bool("indirect", false, "Also list indirect dependents")
packageDependentsCmd.Flags().Bool("json", false, "Print only a JSON document ({\"name\", \"version\", \"registry\", \"direct\", \"indirect\", \"total\", \"dependents\": [...]}) on stdout")
packageDependentsCmd.Flags().Bool("short", false, "Print only one line: the direct, indirect and total dependents counts")
packageDependentsCmd.MarkFlagsMutuallyExclusive("short", "json")
registryListCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
registryListCmd.Flags().Bool("verbose", false, "Show detailed registry information")
projectListCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
Expand Down
Loading