Skip to content

Commit a9f1059

Browse files
feat: package dependency <package-name> cmd to list dependencies (#23)
* registry list cmd * added verbose for detailed view * package search cmd with flags * fixed gql query issues * refactoring * display refactoring * refactoring * display registries the package belongs to * package dependency cmd to display direct and indirect dependencies * added openapi endpoints for package search, with fallback logic * refactorings * conflicts resolved * undoing unrelated changes * fixed conflicts * fixed conflicts * fixed conflicts * some refactorings * updated readme * revert: move GQL query split to separate PR Remove package_search_count.gql and restore the combined package_search_with_count.gql approach. The split of search and count into separate GraphQL queries will be handled in a dedicated PR. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 83b175c commit a9f1059

4 files changed

Lines changed: 260 additions & 11 deletions

File tree

CLAUDE.md

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
44

55
## Project Overview
6-
This is a Go-based CLI tool for interacting with JuliaHub, a platform for Julia computing. The CLI provides commands for authentication, dataset management, registry management, project management, user information, token management, Git integration, and Julia integration.
6+
7+
This is a Go-based CLI tool for interacting with JuliaHub, a platform for Julia computing. The CLI provides commands for authentication, dataset management, registry management, package management, project management, user information, token management, Git integration, and Julia integration.
78

89
## Architecture
910

@@ -13,7 +14,7 @@ The application follows a command-line interface pattern using the Cobra library
1314
- **auth.go**: OAuth2 device flow authentication with JWT token handling
1415
- **datasets.go**: Dataset operations (list, download, upload, status) with REST API integration
1516
- **registries.go**: Registry operations (list, config, add, update) with REST API integration
16-
- **packages.go**: Package search with REST API primary path (`/packages/info`) and GraphQL fallback
17+
- **packages.go**: Package operations (search, dependency) with REST API primary path (`/packages/info`), GraphQL fallback, and documentation API (`/docs/{registry}/{package}/stable/pkg.json`)
1718
- **projects.go**: Project management using GraphQL API with user filtering
1819
- **user.go**: User information retrieval using GraphQL API and REST API for listing users
1920
- **tokens.go**: Token management operations (list) with REST API integration
@@ -42,9 +43,8 @@ The application follows a command-line interface pattern using the Cobra library
4243
- `jh dataset`: Dataset operations (list, download, upload, status)
4344
- `jh registry`: Registry operations (list, config — all via REST API)
4445
- `jh registry config`: Show registry JSON config by name; subcommands add/update accept JSON via stdin or `--file`
45-
- `jh package`: Package search and info (REST primary via `/packages/info`, GraphQL fallback; supports filtering by registry)
46+
- `jh package`: Package search and dependency (REST primary via `/packages/info`, GraphQL fallback; dependency data from `/docs/{registry}/{package}/stable/pkg.json`)
4647
- `jh project`: Project management (list with GraphQL, supports user filtering)
47-
- `jh package`: Package search (REST primary via `/packages/info`, GraphQL fallback)
4848
- `jh user`: User information (info with GraphQL)
4949
- `jh admin`: Administrative commands (user management, token management, landing page)
5050
- `jh admin user`: User management (list all users with REST API, supports verbose mode)
@@ -103,6 +103,11 @@ go run . package search --limit 20 ml
103103
go run . package search --registries General optimization
104104
go run . package info DataFrames
105105
go run . package info Plots --registries General
106+
107+
# Get package dependencies
108+
go run . package dependency DataFrames
109+
go run . package dependency DataFrames --indirect
110+
go run . package dependency CSV --registry General
106111
```
107112

108113
### Test registry operations
@@ -230,9 +235,11 @@ The application uses OAuth2 device flow:
230235

231236
### REST API Integration
232237
- **Dataset operations**: Use presigned URLs for upload/download
238+
- **Registry operations**: `/api/v1/registry/registries/descriptions` for listing registries
233239
- **User management**: `/app/config/features/manage` endpoint for listing all users
234240
- **Token management**: `/app/token/activelist` endpoint for listing all API tokens
235-
- **Package search primary**: `/packages/info` endpoint with `name`, `registries`, `tags`, `licenses`, `limit`, `offset` query params; returns `{packages: [...], meta: {total: N}}`
241+
- **Package search/info primary**: `/packages/info` endpoint with `name`, `registries`, `tags`, `licenses`, `limit`, `offset` query params; returns `{packages: [...], meta: {total: N}}`
242+
- **Package dependencies**: `/docs/{registry}/{package}/stable/pkg.json` for dependency information
236243
- **Authentication**: Bearer token with ID token
237244
- **Upload workflow**: 3-step process (request presigned URL, upload to URL, close upload)
238245

@@ -286,6 +293,40 @@ git clone https://github.com/user/repo.git # Ignored by
286293
- **Token management**: Stores and refreshes tokens per server automatically
287294
- **Error handling**: Graceful fallback to other credential helpers for non-JuliaHub URLs
288295

296+
## Package Management
297+
298+
The CLI provides comprehensive package discovery and dependency analysis:
299+
300+
### Package Search and Info
301+
- **Search**: `jh package search` uses GraphQL API to search packages across registries
302+
- **Info**: `jh package info` retrieves detailed package metadata
303+
- **Filtering**: Supports filtering by registry, installation status, and failures
304+
305+
### Package Dependency (`jh package dependency`)
306+
- **Endpoint**: Uses package documentation API at `/docs/{registry}/{package}/stable/pkg.json`
307+
- **Registry resolution**: Automatically uses first registry package belongs to, or specific registry via `--registry` flag
308+
- **Dependency types**: Distinguishes between direct and indirect dependencies via `direct` field in API response
309+
- **Display limits**:
310+
- Default: Shows up to 10 direct dependencies
311+
- With `--indirect`: Shows up to 10 direct and 50 indirect dependencies
312+
- **Output format**:
313+
- Direct-only mode: Single table with columns: NAME, REGISTRY, UUID, VERSIONS
314+
- Indirect mode: Separate sections for direct and indirect dependencies with columns: NAME, REGISTRY, UUID, VERSIONS
315+
- Registry column shows which registry each dependency belongs to (empty for stdlib packages)
316+
317+
#### Implementation Details (`packages.go`)
318+
- `getPackageDependencies()`: Main function for dependency retrieval
319+
1. Fetches all registries to get registry IDs for GraphQL query
320+
2. Searches for package using GraphQL to get registry information
321+
3. Determines target registry (first registry or user-specified)
322+
4. Fetches package documentation JSON from docs endpoint
323+
5. Filters and limits dependencies based on flags
324+
6. Displays results in formatted tables with separate sections
325+
326+
#### Data Structures
327+
- `PackageDependency`: Represents a single dependency with fields for direct/indirect status, name, UUID, versions, registry, and slug
328+
- `PackageDocsResponse`: Response from documentation API containing package metadata and dependencies array
329+
289330
## Julia Integration
290331

291332
The CLI provides Julia installation and execution with JuliaHub configuration:
@@ -362,14 +403,15 @@ jh run setup
362403
- Landing page commands (`jh admin landing-page`) use REST API: GET `/app/homepage` (show), POST `/app/config/homepage` (update), DELETE `/app/config/homepage` (remove); require appropriate permissions
363404
- Landing page `update` command accepts content inline as an argument, from a file via `--file`, or piped via stdin (priority: `--file` > arg > stdin)
364405
- Landing page response uses custom JSON unmarshaling (`homepageResponse`) to handle `message` being either an object or a string
365-
- Package search (`jh package search`) and info (`jh package info`) both try REST API (`/packages/info`) first, then fall back to GraphQL (`FilteredPackagesWithCount` via `/v1/graphql`) on failure; a warning is printed to stderr when the fallback is used
406+
- Package search (`jh package search`) and info (`jh package info`) both try REST API (`/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
366407
- REST API passes `--registries` as comma-separated registry names to the `registries` query param; GraphQL fallback passes registry IDs to the `registries` variable
367-
- `fetchRegistries` in `registries.go` is used by `listRegistries`, `packageSearchCmd`, and `packageInfoCmd` to resolve registry names to IDs (for GraphQL) and names (for REST)
408+
- `fetchRegistries` in `registries.go` is used by `listRegistries`, `packageSearchCmd`, `packageInfoCmd`, and `packageDependencyCmd` to resolve registry names to IDs (for GraphQL) and names (for REST)
368409
- 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
369410
- A package in multiple registries appears as multiple rows (one per registry) in both REST and GraphQL paths, since the GraphQL view (`package_rank_vw`) is already flattened per package-registry combination
370-
- GraphQL fallback uses `package_search_with_count.gql` which fetches both the package list and aggregate count in a single request (`package_search` + `package_search_aggregate` root fields)
411+
- GraphQL fallback uses `package_search.gql` (`FilteredPackages`) for the package list and `package_search_count.gql` (`FilteredPackagesCount`) for the aggregate count as separate requests
371412
- `executeGraphQL(server, token, req)` in `packages.go` is a shared helper for GraphQL POST requests (sets Authorization, Content-Type, Accept, X-Hasura-Role headers)
372413
- `getPackageInfo` in `packages.go` implements exact name-match lookup using REST-first (`getPackageInfoREST`), GraphQL fallback (`getPackageInfoGraphQL`); `packageInfoCmd` in `main.go` resolves registries via `fetchRegistries`
414+
- `getPackageDependencies` uses GraphQL (`fetchGraphQLPackages`) to locate the package, then fetches `/docs/{registry}/{package}/stable/pkg.json` for dependency data; no REST fallback (docs endpoint is authoritative)
373415

374416
## Implementation Details
375417

README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ A command-line interface for interacting with JuliaHub, a platform for Julia com
66

77
- **Authentication**: OAuth2 device flow authentication with JWT token handling
88
- **Dataset Management**: List, download, upload, and check status of datasets
9-
- **Package Management**: Search and explore Julia packages across registries via REST API (GraphQL fallback)
9+
- **Package Management**: Search and explore Julia packages across registries via REST API (GraphQL fallback), with dependency analysis
1010
- **Registry Management**: List, add, and update Julia package registries
1111
- **Project Management**: List and filter projects using GraphQL API
1212
- **Git Integration**: Clone, push, fetch, and pull with automatic JuliaHub authentication
@@ -161,6 +161,10 @@ go build -o jh .
161161
- `--offset <n>` - Number of results to skip
162162
- `jh package info <package-name>` - Get detailed information about a specific package (exact name match, case-insensitive)
163163
- `jh package info --registries General` - Search in specific registries only
164+
- `jh package dependency <package-name>` - List package dependencies
165+
- Default: Shows up to 10 direct dependencies (NAME, REGISTRY, UUID, VERSIONS)
166+
- `jh package dependency --indirect` - Include both direct and indirect dependencies
167+
- `jh package dependency --registry General` - Specify registry to use
164168

165169
### Registry Management (`jh registry`)
166170

@@ -171,6 +175,7 @@ go build -o jh .
171175
- `jh registry config add` - Add a new registry (JSON payload via stdin or `--file`)
172176
- `jh registry config update` - Update an existing registry (same JSON schema as add, same flags)
173177

178+
174179
### Project Management (`jh project`)
175180

176181
- `jh project list` - List all accessible projects
@@ -274,6 +279,11 @@ jh package search --limit 20 --offset 0 ml
274279
# Get detailed info about a specific package
275280
jh package info DataFrames
276281
jh package info Plots --registries General
282+
283+
# List package dependencies
284+
jh package dependency DataFrames # Shows up to 10 direct dependencies
285+
jh package dependency DataFrames --indirect # Includes indirect dependencies
286+
jh package dependency CSV --registry General # Use specific registry
277287
```
278288

279289
### Registry Operations
@@ -419,7 +429,7 @@ Note: Arguments after `--` are passed directly to Julia. The `jh run` command:
419429

420430
- **Built with Go** using the Cobra CLI framework
421431
- **Authentication**: OAuth2 device flow with JWT token management
422-
- **APIs**: REST API for datasets and package search/info (primary); GraphQL API for projects, user info, and package search/info fallback (single request returns results + total count)
432+
- **APIs**: REST API for datasets and package search/info (primary); GraphQL API for projects, user info, package search/info fallback, and package dependency lookup
423433
- **Git Integration**: Seamless authentication via HTTP headers or credential helper
424434
- **Cross-platform**: Supports Windows, macOS, and Linux
425435

main.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,38 @@ The package name must match exactly (case-insensitive).`,
818818
},
819819
}
820820

821+
var packageDependencyCmd = &cobra.Command{
822+
Use: "dependency <package-name>",
823+
Short: "List package dependencies",
824+
Long: `List dependencies for a specific Julia package.
825+
826+
By default, shows all direct dependencies. Use --indirect flag to include
827+
both direct and indirect dependencies.
828+
829+
The command fetches dependency information from the package documentation
830+
JSON endpoint. If a package exists in multiple registries, it uses the
831+
first registry by default. You can specify a different registry using
832+
the --registry flag.`,
833+
Example: " jh package dependency DataFrames\n jh package dependency --indirect Plots\n jh package dependency --registry General CSV",
834+
Args: cobra.ExactArgs(1),
835+
Run: func(cmd *cobra.Command, args []string) {
836+
server, err := getServerFromFlagOrConfig(cmd)
837+
if err != nil {
838+
fmt.Printf("Failed to get server config: %v\n", err)
839+
os.Exit(1)
840+
}
841+
842+
packageName := args[0]
843+
registryName, _ := cmd.Flags().GetString("registry")
844+
showIndirect, _ := cmd.Flags().GetBool("indirect")
845+
846+
if err := getPackageDependencies(server, packageName, registryName, showIndirect); err != nil {
847+
fmt.Printf("Failed to get package dependencies: %v\n", err)
848+
os.Exit(1)
849+
}
850+
},
851+
}
852+
821853
var registryCmd = &cobra.Command{
822854
Use: "registry",
823855
Short: "Registry management commands",
@@ -1618,6 +1650,9 @@ func init() {
16181650
packageSearchCmd.Flags().Bool("verbose", false, "Show detailed package information")
16191651
packageInfoCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
16201652
packageInfoCmd.Flags().String("registries", "", "Filter by registry names (comma-separated, e.g., 'General,CustomRegistry')")
1653+
packageDependencyCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
1654+
packageDependencyCmd.Flags().String("registry", "", "Specify registry name (uses first registry if not specified)")
1655+
packageDependencyCmd.Flags().Bool("indirect", false, "Include indirect dependencies")
16211656
registryListCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
16221657
registryListCmd.Flags().Bool("verbose", false, "Show detailed registry information")
16231658
projectListCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
@@ -1640,7 +1675,7 @@ func init() {
16401675
authCmd.AddCommand(authLoginCmd, authRefreshCmd, authStatusCmd, authEnvCmd)
16411676
jobCmd.AddCommand(jobListCmd, jobStartCmd)
16421677
datasetCmd.AddCommand(datasetListCmd, datasetDownloadCmd, datasetUploadCmd, datasetStatusCmd)
1643-
packageCmd.AddCommand(packageSearchCmd, packageInfoCmd)
1678+
packageCmd.AddCommand(packageSearchCmd, packageInfoCmd, packageDependencyCmd)
16441679
registryConfigCmd.Flags().StringP("server", "s", "", "JuliaHub server")
16451680
registryConfigAddCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
16461681
registryConfigAddCmd.Flags().StringP("file", "f", "", "Path to JSON config file (reads from stdin if omitted)")

0 commit comments

Comments
 (0)