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
22 changes: 15 additions & 7 deletions src/cmd/shim/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/dtvem/dtvem/src/internal/config"
"github.com/dtvem/dtvem/src/internal/constants"
"github.com/dtvem/dtvem/src/internal/runtime"
"github.com/dtvem/dtvem/src/internal/shim"
"github.com/dtvem/dtvem/src/internal/ui"

// Import runtime providers to register them
Expand Down Expand Up @@ -193,26 +194,33 @@ func getShimName() string {
}

// mapShimToRuntime maps a shim name to its runtime
// For example: python3 -> python, pip -> python, npm -> node
// This queries all registered providers for their shims, eliminating the need
// for a central hardcoded mapping.
// For example: python3 -> python, pip -> python, npm -> node, tsc -> node
// First checks the shim map cache (generated by reshim), then falls back
// to querying registered providers.
func mapShimToRuntime(shimName string) string {
// First, try the shim map cache for O(1) lookup
// This handles both core shims and dynamically installed packages (tsc, eslint, black, etc.)
if runtimeName, ok := shim.LookupRuntime(shimName); ok {
return runtimeName
}

// Fall back to provider-based lookup if cache is missing or doesn't have the shim
// Get all registered providers (using ShimProvider interface)
providers := runtime.GetAllShimProviders()

// Check each provider's shims for an exact match first
for _, provider := range providers {
for _, shim := range provider.Shims() {
if shim == shimName {
for _, s := range provider.Shims() {
if s == shimName {
return provider.Name()
}
}
}

// Check for prefix match (e.g., python3 -> python)
for _, provider := range providers {
for _, shim := range provider.Shims() {
if strings.HasPrefix(shimName, shim) {
for _, s := range provider.Shims() {
if strings.HasPrefix(shimName, s) {
return provider.Name()
}
}
Expand Down
19 changes: 19 additions & 0 deletions src/internal/config/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Paths struct {
Shims string // Shims directory (~/.dtvem/shims)
Versions string // Versions directory (~/.dtvem/versions)
Config string // Config directory (~/.dtvem/config)
Cache string // Cache directory (~/.dtvem/cache)
}

var (
Expand All @@ -40,6 +41,7 @@ func initPaths() *Paths {
Shims: filepath.Join(root, "shims"),
Versions: filepath.Join(root, "versions"),
Config: filepath.Join(root, "config"),
Cache: filepath.Join(root, "cache"),
}
}

Expand Down Expand Up @@ -90,6 +92,7 @@ func EnsureDirectories() error {
paths.Shims,
paths.Versions,
paths.Config,
paths.Cache,
}

for _, dir := range dirs {
Expand Down Expand Up @@ -120,3 +123,19 @@ const LocalConfigDirName = ".dtvem"

// RuntimesFileName is the name of the runtimes configuration file
const RuntimesFileName = "runtimes.json"

// ShimMapFileName is the name of the shim-to-runtime mapping cache file
const ShimMapFileName = "shim-map.json"

// ShimMapPath returns the path to the shim-to-runtime mapping cache file
func ShimMapPath() string {
paths := DefaultPaths()
return filepath.Join(paths.Cache, ShimMapFileName)
}

// ResetPathsCache resets the cached paths, forcing reinitialization on next access.
// This is primarily useful for testing.
func ResetPathsCache() {
pathsOnce = sync.Once{}
defaultPaths = nil
}
87 changes: 87 additions & 0 deletions src/internal/shim/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Package shim manages shim executables that intercept runtime commands
package shim

import (
"encoding/json"
"os"
"sync"

"github.com/dtvem/dtvem/src/internal/config"
)

// ShimMap represents the shim-to-runtime mapping cache
// The map key is the shim name (e.g., "tsc", "npm", "black")
// The map value is the runtime name (e.g., "node", "python")
type ShimMap map[string]string

var (
shimMapCache ShimMap
shimMapCacheOnce sync.Once
shimMapCacheErr error
)

// LoadShimMap loads the shim-to-runtime mapping from the cache file.
// It uses sync.Once to ensure the cache is only loaded once per process.
// Returns the cached map and any error that occurred during loading.
func LoadShimMap() (ShimMap, error) {
shimMapCacheOnce.Do(func() {
shimMapCache, shimMapCacheErr = loadShimMapFromDisk()
})
return shimMapCache, shimMapCacheErr
}

// loadShimMapFromDisk reads the shim map cache file from disk
func loadShimMapFromDisk() (ShimMap, error) {
cachePath := config.ShimMapPath()

data, err := os.ReadFile(cachePath)
if err != nil {
return nil, err
}

var shimMap ShimMap
if err := json.Unmarshal(data, &shimMap); err != nil {
return nil, err
}

return shimMap, nil
}

// SaveShimMap writes the shim-to-runtime mapping to the cache file.
// This should be called during reshim operations.
func SaveShimMap(shimMap ShimMap) error {
// Ensure cache directory exists
paths := config.DefaultPaths()
if err := os.MkdirAll(paths.Cache, 0755); err != nil {
return err
}

cachePath := config.ShimMapPath()

data, err := json.MarshalIndent(shimMap, "", " ")
if err != nil {
return err
}

return os.WriteFile(cachePath, data, 0644)
}

// LookupRuntime looks up the runtime for a given shim name using the cache.
// Returns the runtime name and true if found, or empty string and false if not.
func LookupRuntime(shimName string) (string, bool) {
shimMap, err := LoadShimMap()
if err != nil {
return "", false
}

runtime, ok := shimMap[shimName]
return runtime, ok
}

// ResetShimMapCache resets the cached shim map, forcing a reload on next access.
// This is primarily useful for testing.
func ResetShimMapCache() {
shimMapCacheOnce = sync.Once{}
shimMapCache = nil
shimMapCacheErr = nil
}
Loading