Skip to content

Commit beae136

Browse files
committed
feat(xfpm): add local package installation via path command and improve logger error output
1 parent 654f907 commit beae136

File tree

7 files changed

+162
-13
lines changed

7 files changed

+162
-13
lines changed

cmd/xfpm/main.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ func init() {
7474
installCmd.Flags().BoolP("force", "f", false, "Force re-install even if already extracted")
7575
installCmd.Flags().Bool("update", false, "Update mode: always fetch fresh metadata from registry")
7676
installCmd.Flags().BoolP("global", "g", false, "Install packages globally")
77+
installCmd.Flags().StringP("path", "P", "", "Install from a local path")
7778
}
7879

7980

@@ -84,6 +85,7 @@ var installCmd = &cobra.Command{
8485
RunE: func(cmd *cobra.Command, args []string) error {
8586
startTime := time.Now()
8687
global, _ := cmd.Flags().GetBool("global")
88+
localPath, _ := cmd.Flags().GetString("path")
8789

8890
var projectRoot string
8991
var err error
@@ -118,6 +120,10 @@ var installCmd = &cobra.Command{
118120

119121
rootDeps := make(map[string]string)
120122
directPkgs := make(map[string]string)
123+
124+
// Map for local packages: name -> absolute path
125+
localPackages := make(map[string]string)
126+
121127
pkgJsonPath := filepath.Join(projectRoot, "package.json")
122128
var pkg *core.PackageJson
123129

@@ -130,9 +136,45 @@ var installCmd = &cobra.Command{
130136
}
131137
}
132138

139+
// Handle --path flag
140+
if localPath != "" {
141+
absPath, err := filepath.Abs(localPath)
142+
if err == nil {
143+
localPkg, err := core.LoadPackageJson(filepath.Join(absPath, "package.json"))
144+
if err == nil {
145+
localPackages[localPkg.Name] = absPath
146+
rootDeps[localPkg.Name] = localPkg.Version
147+
directPkgs[localPkg.Name] = localPkg.Version
148+
if resolver.ForcePackages == nil {
149+
resolver.ForcePackages = make(map[string]bool)
150+
}
151+
resolver.ForcePackages[localPkg.Name] = true
152+
}
153+
}
154+
}
155+
133156
if len(args) > 0 {
134-
resolver.ForcePackages = make(map[string]bool)
157+
if resolver.ForcePackages == nil {
158+
resolver.ForcePackages = make(map[string]bool)
159+
}
135160
for _, p := range args {
161+
// Check if p is a local path
162+
if strings.HasPrefix(p, "./") || strings.HasPrefix(p, "../") || filepath.IsAbs(p) {
163+
absPath, err := filepath.Abs(p)
164+
if err == nil {
165+
if fi, err := os.Stat(absPath); err == nil && fi.IsDir() {
166+
localPkg, err := core.LoadPackageJson(filepath.Join(absPath, "package.json"))
167+
if err == nil {
168+
localPackages[localPkg.Name] = absPath
169+
rootDeps[localPkg.Name] = localPkg.Version
170+
directPkgs[localPkg.Name] = localPkg.Version
171+
resolver.ForcePackages[localPkg.Name] = true
172+
continue
173+
}
174+
}
175+
}
176+
}
177+
136178
name := p
137179
req := ""
138180

@@ -176,6 +218,8 @@ var installCmd = &cobra.Command{
176218
return fmt.Errorf("no package.json found and no packages specified")
177219
}
178220

221+
resolver.SetLocalPackages(localPackages)
222+
179223
s, _ := pterm.DefaultSpinner.
180224
WithRemoveWhenDone(true).
181225
WithText("Analysing project tree...").
@@ -216,6 +260,12 @@ var installCmd = &cobra.Command{
216260
return err
217261
}
218262

263+
for _, p := range resolved {
264+
if path, ok := localPackages[p.Name]; ok {
265+
p.LocalPath = path
266+
}
267+
}
268+
219269
utils.Success("Dependency tree resolved successfully (%d total).", len(resolved))
220270

221271
installer := core.NewInstaller(cas, registry, projectRoot)
@@ -298,7 +348,7 @@ var installCmd = &cobra.Command{
298348

299349
func main() {
300350
if err := rootCmd.Execute(); err != nil {
301-
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
351+
utils.Error("%v", err)
302352
os.Exit(1)
303353
}
304354
}

internal/core/installer.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,73 @@ func (i *Installer) batchEnsureExtracted(ctx context.Context, packages []*Resolv
207207
return nil
208208
}
209209

210+
func (i *Installer) extractLocal(pkg *ResolvedPackage) error {
211+
pkgVStoreName := strings.ReplaceAll(pkg.Name, "/", "+") + "@" + pkg.Version
212+
pkgDir := filepath.Join(i.vstoreRoot, pkgVStoreName, "node_modules", pkg.Name)
213+
214+
// Surgically remove only the package content directory
215+
os.RemoveAll(pkgDir)
216+
217+
pterm.Printf(" %s %-30s %s\n", utils.GreenColor.Sprint("├─"), pkg.Name+"@"+pkg.Version, utils.DimColor.Sprint("indexing local path..."))
218+
219+
fileMap := make(map[string]string)
220+
221+
err := filepath.Walk(pkg.LocalPath, func(path string, info os.FileInfo, err error) error {
222+
if err != nil {
223+
return err
224+
}
225+
if info.IsDir() {
226+
if info.Name() == "node_modules" || info.Name() == ".git" || info.Name() == ".xpm" {
227+
return filepath.SkipDir
228+
}
229+
return nil
230+
}
231+
232+
relPath, err := filepath.Rel(pkg.LocalPath, path)
233+
if err != nil {
234+
return err
235+
}
236+
237+
file, err := os.Open(path)
238+
if err != nil {
239+
return err
240+
}
241+
defer file.Close()
242+
243+
isExecutable := (info.Mode() & 0111) != 0
244+
hash, err := i.cas.StoreStream(file, isExecutable)
245+
if err != nil {
246+
return err
247+
}
248+
249+
fileMap["package/"+relPath] = hash
250+
return nil
251+
})
252+
253+
if err != nil {
254+
return err
255+
}
256+
257+
// Store index in CAS
258+
if err := i.cas.StoreIndex(pkg.Name, pkg.Version, fileMap); err != nil {
259+
return err
260+
}
261+
262+
if err := i.LinkFilesToDir(pkgDir, fileMap); err != nil {
263+
return err
264+
}
265+
266+
// Mark as changed
267+
cacheKey := pkg.Name + "@" + pkg.Version
268+
i.changedPackages.Store(cacheKey, true)
269+
return nil
270+
}
271+
210272
func (i *Installer) ensureExtracted(ctx context.Context, pkg *ResolvedPackage) error {
273+
if pkg.LocalPath != "" {
274+
return i.extractLocal(pkg)
275+
}
276+
211277
pkgVStoreName := strings.ReplaceAll(pkg.Name, "/", "+") + "@" + pkg.Version
212278
pkgDir := filepath.Join(i.vstoreRoot, pkgVStoreName, "node_modules", pkg.Name)
213279

internal/core/resolver.go

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package core
33
import (
44
"context"
55
"fmt"
6+
"path/filepath"
67
"runtime"
78
"strings"
89
"sync"
@@ -17,6 +18,7 @@ type ResolvedPackage struct {
1718
SemverVersion *semver.Version
1819
Metadata *VersionMetadata
1920
ResolvedDependencies map[string]string
21+
LocalPath string // If set, this package is installed from a local path
2022
}
2123

2224
type Platform struct {
@@ -166,6 +168,12 @@ type Resolver struct {
166168
onProgress func(string)
167169
Update bool // Global update mode
168170
ForcePackages map[string]bool // Packages to force resolve (ignore cache)
171+
LocalPackages map[string]string // Package name -> local absolute path
172+
}
173+
174+
175+
func (r *Resolver) SetLocalPackages(local map[string]string) {
176+
r.LocalPackages = local
169177
}
170178

171179
func (r *Resolver) SetOnProgress(fn func(string)) {
@@ -326,12 +334,37 @@ func (r *Resolver) resolvePackage(ctx context.Context, name, req string, isOptio
326334
utils.Log("FORCED", fmt.Sprintf("Fresh resolution for %s@%s", realName, realReq))
327335
}
328336

329-
pkgInfo, err := r.registry.FetchPackage(ctx, realName, shouldForce)
330-
if err != nil {
331-
if isOptional {
332-
return nil
337+
var pkgInfo *RegistryPackage
338+
var err error
339+
340+
if path, ok := r.LocalPackages[realName]; ok {
341+
// Local package: load from disk
342+
pkgJson, lerr := LoadPackageJson(filepath.Join(path, "package.json"))
343+
if lerr != nil {
344+
return fmt.Errorf("loading local package.json for %s: %w", realName, lerr)
345+
}
346+
pkgInfo = &RegistryPackage{
347+
Name: pkgJson.Name,
348+
DistTags: map[string]string{"latest": pkgJson.Version},
349+
Versions: map[string]VersionMetadata{
350+
pkgJson.Version: {
351+
Name: pkgJson.Name,
352+
Version: pkgJson.Version,
353+
Dependencies: pkgJson.Dependencies,
354+
OptionalDependencies: pkgJson.OptionalDependencies,
355+
PeerDependencies: pkgJson.PeerDependencies,
356+
// Bin and other fields will be handled by installer if needed
357+
},
358+
},
359+
}
360+
} else {
361+
pkgInfo, err = r.registry.FetchPackage(ctx, realName, shouldForce)
362+
if err != nil {
363+
if isOptional {
364+
return nil
365+
}
366+
return err
333367
}
334-
return err
335368
}
336369

337370
version, err := r.bestMatch(pkgInfo, realReq)

internal/utils/lib_version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package utils
22

33
var (
4-
BinVersion = "G0.1.48"
4+
BinVersion = "G0.1.49"
55
)

internal/utils/logger.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func Info(format string, a ...any) {
3131
},
3232
MessageStyle: InfoColor,
3333
}
34-
printer.Printf(format, a...)
34+
printer.Println(fmt.Sprintf(format, a...))
3535
}
3636

3737
func Success(format string, a ...any) {
@@ -42,7 +42,7 @@ func Success(format string, a ...any) {
4242
},
4343
MessageStyle: SuccessColor,
4444
}
45-
printer.Printf(format, a...)
45+
printer.Println(fmt.Sprintf(format, a...))
4646
}
4747

4848
func Warn(format string, a ...any) {
@@ -53,7 +53,7 @@ func Warn(format string, a ...any) {
5353
},
5454
MessageStyle: WarnColor,
5555
}
56-
printer.Printf(format, a...)
56+
printer.Println(fmt.Sprintf(format, a...))
5757
}
5858

5959
func Error(format string, a ...any) {
@@ -64,7 +64,7 @@ func Error(format string, a ...any) {
6464
},
6565
MessageStyle: ErrorColor,
6666
}
67-
printer.Printf(format, a...)
67+
printer.Println(fmt.Sprintf(format, a...))
6868
}
6969

7070
func Log(prefix, msg string) {

version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "XFPM (XyPriss Package Manager)",
33
"description": "The ultra-fast package manager for the XyPriss ecosystem.",
44
"warning": "CRITICAL: Public modifications to this file are strictly forbidden. Internal Nehonix metadata ONLY.",
5-
"latest": "G0.1.48",
5+
"latest": "G0.1.49",
66
"metadata": {
77
"releaseDate": "2026-03-03",
88
"channel": "stable"

xfpm

11.2 KB
Binary file not shown.

0 commit comments

Comments
 (0)