Skip to content

Commit 0b1f13f

Browse files
authored
feat(init): add --user flag for non-admin Windows installation (#205)
1 parent 94606cf commit 0b1f13f

10 files changed

Lines changed: 1199 additions & 15 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Integration Tests - Init Command
2+
3+
on:
4+
workflow_dispatch:
5+
# Manual trigger for testing init command flags
6+
push:
7+
branches: [main]
8+
paths:
9+
- 'src/cmd/init.go'
10+
- 'src/internal/config/settings.go'
11+
- 'src/internal/path/path_windows.go'
12+
- 'src/internal/path/path_unix.go'
13+
- 'install.ps1'
14+
- 'install.sh'
15+
- '.github/workflows/integration-test-init.yml'
16+
pull_request:
17+
branches: [main]
18+
paths:
19+
- 'src/cmd/init.go'
20+
- 'src/internal/config/settings.go'
21+
- 'src/internal/path/path_windows.go'
22+
- 'src/internal/path/path_unix.go'
23+
- 'install.ps1'
24+
- 'install.sh'
25+
- '.github/workflows/integration-test-init.yml'
26+
27+
permissions:
28+
contents: read
29+
30+
jobs:
31+
init:
32+
name: Init Command
33+
uses: CodingWithCalvin/.github/.github/workflows/dtvem-integration-test-init.yml@main

install.ps1

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# dtvem installer for Windows
2-
# Usage: irm https://raw.githubusercontent.com/CodingWithCalvin/dtvem.cli/main/install.ps1 | iex
2+
# Usage:
3+
# Standard (admin required): irm https://raw.githubusercontent.com/CodingWithCalvin/dtvem.cli/main/install.ps1 | iex
4+
# User install (no admin): iex "& { $(irm https://raw.githubusercontent.com/CodingWithCalvin/dtvem.cli/main/install.ps1) } -UserInstall"
5+
6+
param(
7+
[switch]$UserInstall
8+
)
39

410
$ErrorActionPreference = "Stop"
511

@@ -117,6 +123,7 @@ function Test-Checksum {
117123
}
118124

119125
function Main {
126+
param([switch]$UserInstall)
120127
Write-Host ""
121128
Write-Host "========================================" -ForegroundColor Blue
122129
Write-Host " dtvem installer" -ForegroundColor Blue
@@ -269,7 +276,13 @@ function Main {
269276
# Temporarily add to PATH for this session
270277
$env:Path = "$INSTALL_DIR;$env:Path"
271278

272-
& $dtvemPath init
279+
if ($UserInstall) {
280+
Write-Info "Using user-level PATH (no admin required)"
281+
& $dtvemPath init --user -y
282+
}
283+
else {
284+
& $dtvemPath init
285+
}
273286
Write-Success "dtvem is ready to use!"
274287
Write-Info "Both $INSTALL_DIR and $SHIMS_DIR have been added to PATH"
275288
}
@@ -298,4 +311,4 @@ function Main {
298311
}
299312
}
300313

301-
Main
314+
Main -UserInstall:$UserInstall

install.sh

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,25 @@
22
set -e
33

44
# dtvem installer for macOS and Linux
5-
# Usage: curl -fsSL https://raw.githubusercontent.com/CodingWithCalvin/dtvem.cli/main/install.sh | bash
5+
# Usage:
6+
# Standard: curl -fsSL https://raw.githubusercontent.com/CodingWithCalvin/dtvem.cli/main/install.sh | bash
7+
# User install: curl -fsSL https://raw.githubusercontent.com/CodingWithCalvin/dtvem.cli/main/install.sh | bash -s -- --user-install
68

79
REPO="CodingWithCalvin/dtvem.cli"
10+
USER_INSTALL=false
11+
12+
# Parse arguments
13+
while [[ $# -gt 0 ]]; do
14+
case $1 in
15+
--user-install)
16+
USER_INSTALL=true
17+
shift
18+
;;
19+
*)
20+
shift
21+
;;
22+
esac
23+
done
824

925
# This will be replaced with the actual version during release
1026
# Format: DTVEM_RELEASE_VERSION="1.0.0"
@@ -366,11 +382,21 @@ main() {
366382
# Run init to add shims directory to PATH
367383
echo ""
368384
info "Running dtvem init to add shims directory to PATH..."
369-
if "$INSTALL_DIR/dtvem" init; then
370-
success "dtvem is ready to use!"
371-
info "Both $INSTALL_DIR and $SHIMS_DIR have been added to PATH"
385+
if [ "$USER_INSTALL" = true ]; then
386+
info "Using user-level PATH"
387+
if "$INSTALL_DIR/dtvem" init --user -y; then
388+
success "dtvem is ready to use!"
389+
info "Both $INSTALL_DIR and $SHIMS_DIR have been added to PATH"
390+
else
391+
warning "dtvem init failed - you may need to run it manually"
392+
fi
372393
else
373-
warning "dtvem init failed - you may need to run it manually"
394+
if "$INSTALL_DIR/dtvem" init; then
395+
success "dtvem is ready to use!"
396+
info "Both $INSTALL_DIR and $SHIMS_DIR have been added to PATH"
397+
else
398+
warning "dtvem init failed - you may need to run it manually"
399+
fi
374400
fi
375401

376402
echo ""

schemas/settings.schema.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"$id": "https://raw.githubusercontent.com/CodingWithCalvin/dtvem.cli/main/schemas/settings.schema.json",
4+
"title": "dtvem Settings Configuration",
5+
"description": "Settings file for dtvem (Development Tool Virtual Environment Manager) installation preferences",
6+
"type": "object",
7+
"properties": {
8+
"installType": {
9+
"type": "string",
10+
"description": "The type of dtvem installation. 'system' uses System PATH (requires admin on Windows), 'user' uses User PATH (no admin required).",
11+
"enum": ["system", "user"],
12+
"default": "system"
13+
}
14+
},
15+
"required": ["installType"],
16+
"additionalProperties": false,
17+
"examples": [
18+
{
19+
"installType": "system"
20+
},
21+
{
22+
"installType": "user"
23+
}
24+
]
25+
}

src/cmd/init.go

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
package cmd
22

33
import (
4+
"runtime"
5+
46
"github.com/CodingWithCalvin/dtvem.cli/src/internal/config"
7+
"github.com/CodingWithCalvin/dtvem.cli/src/internal/constants"
58
"github.com/CodingWithCalvin/dtvem.cli/src/internal/path"
69
"github.com/CodingWithCalvin/dtvem.cli/src/internal/ui"
710
"github.com/spf13/cobra"
811
)
912

10-
var initYes bool
13+
var (
14+
initYes bool
15+
initUser bool
16+
)
1117

1218
var initCmd = &cobra.Command{
1319
Use: "init",
@@ -18,10 +24,15 @@ This command:
1824
- Creates the ~/.dtvem directory structure
1925
- Adds ~/.dtvem/shims to your PATH (with your permission)
2026
27+
Options:
28+
--user Use User PATH instead of System PATH on Windows (no admin required)
29+
Note: System-installed runtimes will take priority over dtvem shims
30+
2131
Run this command after installing dtvem for the first time.
2232
2333
Example:
24-
dtvem init`,
34+
dtvem init
35+
dtvem init --user # Windows: use User PATH (no admin)`,
2536
Run: func(cmd *cobra.Command, args []string) {
2637
ui.Header("Initializing dtvem...")
2738

@@ -37,24 +48,109 @@ Example:
3748

3849
spinner.Success("Directories created")
3950

51+
// Determine install type and check for switching
52+
userInstall := determineInstallType(cmd)
53+
previousSettings, _ := config.LoadSettings()
54+
isSwitching := cmd.Flags().Changed("user") && previousSettings != nil &&
55+
((userInstall && previousSettings.InstallType == config.InstallTypeSystem) ||
56+
(!userInstall && previousSettings.InstallType == config.InstallTypeUser))
57+
58+
// Warn about switching install types on Windows
59+
if isSwitching && runtime.GOOS == constants.OSWindows {
60+
warnAboutInstallTypeSwitch(userInstall, previousSettings.InstallType)
61+
}
62+
4063
// Setup PATH - AddToPath handles checking position and moving if needed
4164
shimsDir := path.ShimsDir()
4265

43-
if err := path.AddToPath(shimsDir, initYes); err != nil {
66+
if err := path.AddToPath(shimsDir, initYes, userInstall); err != nil {
4467
ui.Error("Failed to configure PATH: %v", err)
4568
ui.Info("You can manually add %s to your PATH", shimsDir)
4669
return
4770
}
4871

72+
// Save settings for future reference
73+
installType := config.InstallTypeSystem
74+
if userInstall {
75+
installType = config.InstallTypeUser
76+
}
77+
settings := &config.Settings{InstallType: installType}
78+
if err := config.SaveSettings(settings); err != nil {
79+
ui.Warning("Failed to save settings: %v", err)
80+
}
81+
4982
ui.Success("dtvem initialized successfully!")
83+
84+
// Show reminder for user-level installations on Windows
85+
if userInstall && runtime.GOOS == constants.OSWindows {
86+
ui.Info("")
87+
ui.Warning("Note: Using User PATH. System-installed runtimes may take priority.")
88+
ui.Info("Run 'dtvem init' as administrator for system-level PATH if needed.")
89+
}
90+
5091
ui.Info("\nNext steps:")
5192
ui.Info(" 1. Restart your terminal (required for PATH changes)")
5293
ui.Info(" 2. Run: dtvem install <runtime> <version>")
5394
ui.Info(" 3. Run: dtvem global <runtime> <version>")
5495
},
5596
}
5697

98+
// determineInstallType determines whether to use user-level or system-level installation.
99+
// Priority: flag > saved settings > default (system)
100+
func determineInstallType(cmd *cobra.Command) bool {
101+
// If --user flag was explicitly set, use it
102+
if cmd.Flags().Changed("user") {
103+
return initUser
104+
}
105+
106+
// Check saved settings
107+
settings, err := config.LoadSettings()
108+
if err == nil && settings.InstallType == config.InstallTypeUser {
109+
return true
110+
}
111+
112+
// Default to system install
113+
return false
114+
}
115+
116+
// warnAboutInstallTypeSwitch warns the user about switching install types
117+
// and provides instructions for cleaning up the old PATH entry.
118+
func warnAboutInstallTypeSwitch(toUser bool, previousType config.InstallType) {
119+
shimsDir := path.ShimsDir()
120+
121+
ui.Warning("Switching install type from %s to %s", previousType, map[bool]string{true: "user", false: "system"}[toUser])
122+
ui.Info("")
123+
124+
if toUser {
125+
// Switching from system to user
126+
ui.Info("Your previous system-level PATH entry may still exist.")
127+
ui.Info("To avoid conflicts, you may want to remove the old System PATH entry:")
128+
ui.Info("")
129+
ui.Info(" Manual removal steps:")
130+
ui.Info(" 1. Open System Properties > Environment Variables")
131+
ui.Info(" 2. Under 'System variables', select 'Path' and click 'Edit'")
132+
ui.Info(" 3. Remove the entry: %s", ui.Highlight(shimsDir))
133+
ui.Info(" 4. Click OK to save")
134+
ui.Info("")
135+
ui.Info(" Or run as administrator:")
136+
ui.Info(" dtvem init (without --user)")
137+
ui.Info(" This will move the entry to System PATH properly.")
138+
} else {
139+
// Switching from user to system
140+
ui.Info("Your previous user-level PATH entry may still exist.")
141+
ui.Info("To avoid conflicts, you may want to remove the old User PATH entry:")
142+
ui.Info("")
143+
ui.Info(" Manual removal steps:")
144+
ui.Info(" 1. Open System Properties > Environment Variables")
145+
ui.Info(" 2. Under 'User variables', select 'Path' and click 'Edit'")
146+
ui.Info(" 3. Remove the entry: %s", ui.Highlight(shimsDir))
147+
ui.Info(" 4. Click OK to save")
148+
}
149+
ui.Info("")
150+
}
151+
57152
func init() {
58153
initCmd.Flags().BoolVarP(&initYes, "yes", "y", false, "Skip confirmation prompts")
154+
initCmd.Flags().BoolVar(&initUser, "user", false, "Use User PATH instead of System PATH (Windows: no admin required)")
59155
rootCmd.AddCommand(initCmd)
60156
}

0 commit comments

Comments
 (0)