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
2 changes: 2 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) via the homepage layout API. The layout is a JSON widget array; the landing page is the `greeter` widget's `metadata.content` markdown. `update` replaces that content (adding a greeter as the first, full-width row and shifting the others down when none exists), `remove` drops the greeter widget; both POST the complete layout back, preserving every field the CLI does not model. There is no separate DELETE endpoint any more
- **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`
- **search.go**: `jh search code|symbols|docs` against the search service (`POST /search/v1/{code,sym,docs}`, legacy `POST /search/{code,sym,docs}` fallback), with request builders, response decoders, table renderers and the `--json` writer as pure functions; the cobra commands live in `main.go`
- **git.go**: Git integration (clone, push, fetch, pull) with JuliaHub authentication
- **julia.go**: Julia installation and management
Expand Down Expand Up @@ -534,6 +535,7 @@ jh run setup
- 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; `searchPackages` prints `warning: REST package search failed (...); falling back to GraphQL` to stderr when it falls back (`getPackageInfo` falls back silently)
- `--registries` resolution for `jh package search` / `jh package info` / `jh search packages` goes through `resolveRegistries(server, csv)` in `registries.go` (empty = all registries; unknown name → `Registry not found: '<name>'`)
- `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 --json` gains 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
- `jh package search` / `jh search packages --json` sets `PackageSearchParams.JSON`; both the REST and GraphQL paths then call `writePackagesJSON`, which prints `{"results": [...], "total": N}` (snake_case keys from `packageInfo`'s json tags, `tags` always an array) 2-space indented with a trailing newline — nothing else on stdout
- **JSON output convention** (`jh search *`): `--json` prints only the document on stdout, 2-space indented, trailing newline — `{"results": <server array, untouched>, "truncated": bool}` for code/symbols/docs; warnings, the human-mode `note: results truncated (search stopped early)` line, and errors always go to stderr; exit code is 0/1 only
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,13 @@ go build -o jh .
- 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
14 changes: 10 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -1039,7 +1039,7 @@ 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\n jh package dependency --json JuMP",
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)
Expand All @@ -1050,10 +1050,11 @@ the --registry flag.`,

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, asJSON); err != nil {
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 @@ -1073,7 +1074,7 @@ 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\n jh package dependents --json JuMP",
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)
Expand All @@ -1084,10 +1085,11 @@ You can specify a different registry using the --registry flag.`,

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, asJSON); err != nil {
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 @@ -2657,10 +2659,14 @@ func init() {
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