Skip to content

Commit 2a07770

Browse files
authored
feat: eagerly move credetials to depot, allow args to julia on jh run (#11)
1 parent 4aac9c4 commit 2a07770

File tree

5 files changed

+266
-27
lines changed

5 files changed

+266
-27
lines changed

CLAUDE.md

Lines changed: 107 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ The application follows a command-line interface pattern using the Cobra library
4444
- `jh git-credential`: Git credential helper for seamless authentication
4545
- `jh julia`: Julia installation management
4646
- `jh run`: Julia execution with JuliaHub configuration
47+
- `jh run setup`: Setup Julia credentials without starting Julia
4748

4849
4. **Data Models**:
4950
- UUID strings for most entity IDs (projects, datasets, resources)
@@ -111,6 +112,27 @@ echo -e "protocol=https\nhost=juliahub.com\npath=git/projects/test/test\n" | go
111112
git clone https://juliahub.com/git/projects/username/project.git
112113
```
113114

115+
### Test Julia integration
116+
```bash
117+
# Install Julia (if not already installed)
118+
go run . julia install
119+
120+
# Setup Julia credentials only
121+
go run . run setup
122+
123+
# Run Julia REPL with credentials setup
124+
go run . run
125+
126+
# Run Julia with credentials setup
127+
go run . run -- -e "println(\"Hello from JuliaHub!\")"
128+
129+
# Run Julia script with project
130+
go run . run -- --project=. script.jl
131+
132+
# Run Julia with multiple flags
133+
go run . run -- --project=. --threads=4 -e "println(Threads.nthreads())"
134+
```
135+
114136
## Dependencies
115137

116138
- `github.com/spf13/cobra`: CLI framework
@@ -196,10 +218,43 @@ git clone https://github.com/user/repo.git # Ignored by
196218
## Julia Integration
197219

198220
The CLI provides Julia installation and execution with JuliaHub configuration:
221+
222+
### Julia Installation (`jh julia install`)
199223
- Cross-platform installation (Windows via winget, Unix via official installer)
200-
- Authentication file creation (`~/.julia/servers/<server>/auth.toml`)
201-
- Package server configuration (`JULIA_PKG_SERVER`)
202-
- Project activation (`--project=.`)
224+
- Installs latest stable Julia version
225+
226+
### Julia Credentials
227+
- **Authentication file**: Automatically creates `~/.julia/servers/<server>/auth.toml`
228+
- **Atomic writes**: Uses temporary file + rename for safe credential updates
229+
- **Automatic updates**: Credentials are automatically refreshed when:
230+
- User runs `jh auth login`
231+
- User runs `jh auth refresh`
232+
- Token is refreshed via `ensureValidToken()`
233+
- User runs `jh run` or `jh run setup`
234+
235+
### Julia Commands
236+
237+
#### `jh run [-- julia-args...]` - Run Julia with JuliaHub configuration
238+
```bash
239+
jh run # Start Julia REPL
240+
jh run -- script.jl # Run a script
241+
jh run -- -e "println(\"Hello\")" # Execute code
242+
jh run -- --project=. --threads=4 script.jl # Run with flags
243+
```
244+
- Sets up credentials, then starts Julia
245+
- Arguments after `--` are passed directly to Julia without modification
246+
- User controls all Julia flags (including `--project`, `--threads`, etc.)
247+
- Environment variables set:
248+
- `JULIA_PKG_SERVER`: Points to your JuliaHub server
249+
- `JULIA_PKG_USE_CLI_GIT`: Set to `true` for Git integration
250+
251+
#### `jh run setup` - Setup credentials only (no Julia execution)
252+
```bash
253+
jh run setup
254+
```
255+
- Creates/updates `~/.julia/servers/<server>/auth.toml` with current credentials
256+
- Does not start Julia
257+
- Useful for explicitly updating credentials
203258

204259
## Development Notes
205260

@@ -209,10 +264,58 @@ The CLI provides Julia installation and execution with JuliaHub configuration:
209264
- Token refresh is automatic via `ensureValidToken()`
210265
- File uploads use multipart form data with proper content types
211266
- Julia auth files use TOML format with `preferred_username` from JWT claims
267+
- Julia auth files use atomic writes (temp file + rename) to prevent corruption
268+
- Julia credentials are automatically updated after login and token refresh
212269
- Git commands use `http.extraHeader` for authentication and pass through all arguments
213270
- Git credential helper provides seamless authentication for standard Git commands
214271
- Multi-server authentication handled automatically via credential helper
215272
- Project filtering supports `--user` parameter for showing specific user's projects or own projects
216273
- Clone command automatically resolves `username/project` format to project UUIDs
217274
- Folder naming conflicts are resolved with automatic numbering (project-1, project-2, etc.)
218-
- Credential helper follows Git protocol: responds only to JuliaHub URLs, ignores others
275+
- Credential helper follows Git protocol: responds only to JuliaHub URLs, ignores others
276+
277+
## Implementation Details
278+
279+
### Julia Credentials Management (`run.go`)
280+
281+
The Julia credentials system consists of three main functions:
282+
283+
1. **`createJuliaAuthFile(server, token)`**:
284+
- Creates `~/.julia/servers/<server>/auth.toml` with TOML-formatted credentials
285+
- Uses atomic writes: writes to temporary file, syncs, then renames
286+
- Includes all necessary fields: tokens, expiration, refresh URL, user info
287+
- Called by `setupJuliaCredentials()` and `updateJuliaCredentialsIfNeeded()`
288+
289+
2. **`setupJuliaCredentials()`**:
290+
- Public function called by:
291+
- `jh run` command (before starting Julia)
292+
- `jh run setup` command
293+
- `jh auth login` command (after successful login)
294+
- `jh auth refresh` command (after successful refresh)
295+
- Ensures valid token via `ensureValidToken()`
296+
- Creates/updates Julia auth file
297+
- Returns error if authentication fails
298+
299+
3. **`runJulia(args)`**:
300+
- Sets up credentials via `setupJuliaCredentials()`
301+
- Configures environment variables (`JULIA_PKG_SERVER`, `JULIA_PKG_USE_CLI_GIT`)
302+
- Executes Julia with user-provided arguments (no automatic flags)
303+
- Streams stdin/stdout/stderr to maintain interactive experience
304+
305+
### Automatic Credential Updates (`auth.go`)
306+
307+
The `updateJuliaCredentialsIfNeeded(server, token)` function:
308+
- Called automatically by `ensureValidToken()` after token refresh
309+
- Checks if `~/.julia/servers/<server>/auth.toml` exists
310+
- If exists, updates it with refreshed token
311+
- If not exists, does nothing (user hasn't used Julia integration yet)
312+
- Errors are silently ignored to avoid breaking token operations
313+
314+
This ensures Julia credentials stay in sync with the main auth tokens without requiring manual intervention.
315+
316+
### Command Structure
317+
318+
- **`jh run`**: Primary command - always starts Julia after setting up credentials
319+
- **`jh run setup`**: Subcommand - only sets up credentials without starting Julia
320+
- **`jh auth login`**: Automatically sets up Julia credentials after successful login
321+
- **`jh auth refresh`**: Automatically sets up Julia credentials after successful refresh

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,8 @@ go build -o jh .
169169
### Julia Integration
170170

171171
- `jh julia install` - Install Julia programming language
172-
- `jh run` - Start Julia with JuliaHub configuration
172+
- `jh run [-- julia-args...]` - Run Julia with JuliaHub configuration
173+
- `jh run setup` - Setup JuliaHub credentials for Julia without starting Julia
173174

174175
### User Information (`jh user`)
175176

@@ -246,6 +247,33 @@ git push
246247
Note: It's recommended to use the git-credential helper, but you can still
247248
clone using `jh clone username/project-name`; otherwise you need the project's uuid
248249

250+
### Julia Workflow
251+
252+
```bash
253+
# Install Julia (if not already installed)
254+
jh julia install
255+
256+
# Setup JuliaHub credentials only
257+
jh run setup
258+
259+
# Start Julia REPL with JuliaHub configuration
260+
jh run
261+
262+
# Run a Julia script
263+
jh run -- script.jl
264+
265+
# Execute Julia code directly
266+
jh run -- -e "println(\"Hello from JuliaHub!\")"
267+
268+
# Run Julia with project and multiple threads
269+
jh run -- --project=. --threads=4 script.jl
270+
```
271+
272+
Note: Arguments after `--` are passed directly to Julia. The `jh run` command:
273+
1. Sets up JuliaHub credentials in `~/.julia/servers/<server>/auth.toml`
274+
2. Configures `JULIA_PKG_SERVER` environment variable
275+
3. Starts Julia with your specified arguments
276+
249277
## Architecture
250278

251279
- **Built with Go** using the Cobra CLI framework

auth.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net/http"
1010
"net/url"
1111
"os"
12+
"path/filepath"
1213
"strings"
1314
"time"
1415
)
@@ -328,9 +329,32 @@ func ensureValidToken() (*StoredToken, error) {
328329
}
329330
}
330331

332+
// Update Julia credentials after token refresh
333+
// We ignore errors here to avoid breaking token operations if Julia setup fails
334+
_ = updateJuliaCredentialsIfNeeded(storedToken.Server, updatedToken)
335+
331336
return updatedToken, nil
332337
}
333338

339+
// updateJuliaCredentialsIfNeeded updates Julia credentials if the auth file exists
340+
// This is called after token refresh to keep credentials in sync
341+
func updateJuliaCredentialsIfNeeded(server string, token *StoredToken) error {
342+
homeDir, err := os.UserHomeDir()
343+
if err != nil {
344+
return err
345+
}
346+
347+
// Check if the auth.toml file exists
348+
authFilePath := filepath.Join(homeDir, ".julia", "servers", server, "auth.toml")
349+
if _, err := os.Stat(authFilePath); os.IsNotExist(err) {
350+
// File doesn't exist, so user hasn't used Julia integration yet
351+
return nil
352+
}
353+
354+
// File exists, update it
355+
return createJuliaAuthFile(server, token)
356+
}
357+
334358
func formatTokenInfo(token *StoredToken) string {
335359
claims, err := decodeJWT(token.AccessToken)
336360
if err != nil {

main.go

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,11 @@ This command will:
222222
}
223223

224224
fmt.Println("Successfully authenticated!")
225+
226+
// Setup Julia credentials after successful authentication
227+
if err := setupJuliaCredentials(); err != nil {
228+
fmt.Printf("Warning: Failed to setup Julia credentials: %v\n", err)
229+
}
225230
},
226231
}
227232

@@ -263,6 +268,11 @@ refresh them proactively.`,
263268
}
264269

265270
fmt.Println("Token refreshed successfully!")
271+
272+
// Setup Julia credentials after successful refresh
273+
if err := setupJuliaCredentials(); err != nil {
274+
fmt.Printf("Warning: Failed to setup Julia credentials: %v\n", err)
275+
}
266276
},
267277
}
268278

@@ -795,30 +805,61 @@ This command must be run from within a cloned JuliaHub project directory.`,
795805
}
796806

797807
var runCmd = &cobra.Command{
798-
Use: "run",
808+
Use: "run [-- julia-args...]",
799809
Short: "Run Julia with JuliaHub configuration",
800-
Long: `Start Julia with JuliaHub package server configuration.
810+
Long: `Run Julia with JuliaHub configuration and credentials.
801811
802812
This command:
803-
1. Ensures you have valid JuliaHub authentication
804-
2. Creates Julia authentication files (~/.julia/servers/<server>/auth.toml)
805-
3. Configures Julia to use JuliaHub as the package server
806-
4. Starts Julia with --project=. flag for local project activation
813+
1. Sets up JuliaHub credentials (~/.julia/servers/<server>/auth.toml)
814+
2. Starts Julia with the specified arguments
807815
808-
Environment variables set:
816+
Arguments after -- are passed directly to Julia without modification.
817+
Use 'jh run setup' to only setup credentials without starting Julia.
818+
819+
Environment variables set when running Julia:
809820
- JULIA_PKG_SERVER: Points to your JuliaHub server
810821
- JULIA_PKG_USE_CLI_GIT: Enables CLI git usage
811822
812823
Requires Julia to be installed (use 'jh julia install' if needed).`,
813-
Example: " jh run",
824+
Example: ` jh run # Start Julia REPL
825+
jh run -- script.jl # Run a script
826+
jh run -- -e "println(\"Hi\")" # Execute code
827+
jh run -- --project=. --threads=4 script.jl # Run with options`,
814828
Run: func(cmd *cobra.Command, args []string) {
815-
if err := runJulia(); err != nil {
829+
// Setup credentials and run Julia
830+
if err := runJulia(args); err != nil {
816831
fmt.Printf("Failed to run Julia: %v\n", err)
817832
os.Exit(1)
818833
}
819834
},
820835
}
821836

837+
var runSetupCmd = &cobra.Command{
838+
Use: "setup",
839+
Short: "Setup JuliaHub credentials for Julia",
840+
Long: `Setup JuliaHub credentials in ~/.julia/servers/<server>/auth.toml without starting Julia.
841+
842+
This command:
843+
1. Ensures you have valid JuliaHub authentication
844+
2. Creates/updates Julia authentication files (~/.julia/servers/<server>/auth.toml)
845+
846+
Credentials are automatically setup when:
847+
- Running 'jh auth login'
848+
- Running 'jh auth refresh'
849+
- Running 'jh run' (before starting Julia)
850+
851+
This command is useful for explicitly updating credentials without starting Julia.`,
852+
Example: ` jh run setup # Setup credentials only`,
853+
Run: func(cmd *cobra.Command, args []string) {
854+
// Only setup Julia credentials
855+
if err := setupJuliaCredentials(); err != nil {
856+
fmt.Printf("Failed to setup Julia credentials: %v\n", err)
857+
os.Exit(1)
858+
}
859+
fmt.Println("Julia credentials setup complete")
860+
},
861+
}
862+
822863
var gitCredentialCmd = &cobra.Command{
823864
Use: "git-credential",
824865
Short: "Git credential helper commands",
@@ -956,6 +997,7 @@ func init() {
956997
projectCmd.AddCommand(projectListCmd)
957998
userCmd.AddCommand(userInfoCmd)
958999
juliaCmd.AddCommand(juliaInstallCmd)
1000+
runCmd.AddCommand(runSetupCmd)
9591001
gitCredentialCmd.AddCommand(gitCredentialHelperCmd, gitCredentialGetCmd, gitCredentialStoreCmd, gitCredentialEraseCmd, gitCredentialSetupCmd)
9601002

9611003
rootCmd.AddCommand(authCmd, jobCmd, datasetCmd, projectCmd, userCmd, juliaCmd, cloneCmd, pushCmd, fetchCmd, pullCmd, runCmd, gitCredentialCmd, updateCmd)

0 commit comments

Comments
 (0)