Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
61200e2
Fix path traversal due to package name
thelovekesh May 5, 2026
797524e
Add func to validate project relative path
thelovekesh May 5, 2026
02e03c6
Add validation for content and bin dir
thelovekesh May 5, 2026
331b197
Add max compressed size safety
thelovekesh May 5, 2026
5aaf960
Fix bufferedReader pool leak
thelovekesh May 5, 2026
f645c71
Add closer on tarballer
thelovekesh May 5, 2026
7fac8c0
Fix orphaned backup files in content dir
thelovekesh May 5, 2026
6c44b75
Add error in installer to fail early if unable to create directories
thelovekesh May 5, 2026
d3e73d4
Fix mutation of included files in archive instance
thelovekesh May 5, 2026
efba5c5
Remove redundant comment
thelovekesh May 5, 2026
a34ff12
Add tarball size exceed error at packing time
thelovekesh May 5, 2026
65c3b9a
Fix buffer pool size
thelovekesh May 5, 2026
700c216
Add func to validate dep names
thelovekesh May 5, 2026
77645e8
Add dep names validation
thelovekesh May 5, 2026
685dc9f
Add hardcoded perms while creating files for tar
thelovekesh May 5, 2026
0f33487
Remove overriding walk dir errors
thelovekesh May 5, 2026
694ce59
Add safeguards for sparsed tar files causing size bloat
thelovekesh May 5, 2026
b01fc21
Limit reader to only assign size as per tar entry
thelovekesh May 5, 2026
ca70df4
Remove tmp dir and clean stale staging files
thelovekesh May 5, 2026
c2d7879
Add `github.com/gofrs/flock`
thelovekesh May 5, 2026
4c492c7
Add ctx and close on installer
thelovekesh May 5, 2026
d8d3511
Add wpm workspace lock
thelovekesh May 5, 2026
2da0c6a
Add better fs operations for installer and remove gc pauses
thelovekesh May 5, 2026
a0ee89e
Fix removal of temp dir after installation
thelovekesh May 5, 2026
5906c47
Update base dir param name
thelovekesh May 5, 2026
d772a61
Add workspace locking in possible wpm write operations
thelovekesh May 5, 2026
bd048c4
Add lock acquire waiting message
thelovekesh May 5, 2026
d3253cd
Remove locks from http cache api
thelovekesh May 5, 2026
01f69de
Remove singleflight from http caching, rather callers should limit du…
thelovekesh May 5, 2026
db877ad
Fix the rt order
thelovekesh May 5, 2026
90ac3a7
Add function to validate dist tags
thelovekesh May 5, 2026
6f87b39
Add validation for package names passed as args
thelovekesh May 5, 2026
6c50910
Fix dist tag validator
thelovekesh May 5, 2026
390ff47
Remove cache override for fetching manifest
thelovekesh May 5, 2026
ad745e2
Check error values
thelovekesh May 5, 2026
e472f6f
Fix max compressed size limit in error message
thelovekesh May 5, 2026
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
69 changes: 58 additions & 11 deletions cli/command/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"sync"

"wpm/cli/command"
"wpm/cli/version"
"wpm/pkg/output"
"wpm/pkg/pm/workspace"
"wpm/pkg/pm/wpmjson"
"wpm/pkg/pm/wpmjson/types"
"wpm/pkg/pm/wpmjson/validator"

"github.com/morikuni/aec"
"github.com/pkg/errors"
Expand Down Expand Up @@ -81,6 +84,29 @@ func runInstall(ctx context.Context, wpmCli command.Cli, opts installOptions, pa
return errors.Wrap(err, "failed to get current working directory")
}

wpmCli.Output().Prettyln(output.Text{
Plain: "wpm install v" + version.Version,
Fancy: aec.Bold.Apply("wpm install") + " " + aec.LightBlackF.Apply("v"+version.Version),
})

contentDir := wpmjson.New().ContentDir()
if probe, _ := wpmjson.Read(cwd); probe != nil {
contentDir = probe.ContentDir()
}

lock, err := workspace.AcquireLock(ctx, filepath.Join(cwd, contentDir), func() {
wpmCli.Output().PrettyErrorln(output.Text{
Plain: "waiting for another wpm process to finish in this workspace...",
Fancy: aec.Faint.Apply("waiting for another wpm process to finish in this workspace..."),
})
})
if err != nil {
return errors.Wrap(err, "failed to acquire workspace lock")
}
defer func() {
_ = lock.Release()
}()

cfg, err := wpmjson.Read(cwd)
if err != nil {
return err
Expand All @@ -94,11 +120,6 @@ func runInstall(ctx context.Context, wpmCli command.Cli, opts installOptions, pa
cfg = wpmjson.New()
}

wpmCli.Output().Prettyln(output.Text{
Plain: "wpm install v" + version.Version,
Fancy: aec.Bold.Apply("wpm install") + " " + aec.LightBlackF.Apply("v"+version.Version),
})

configModified := false

if len(packages) > 0 {
Expand Down Expand Up @@ -162,7 +183,10 @@ func addPackages(ctx context.Context, config *wpmjson.Config, wpmCli command.Cli
var mu sync.Mutex

for i, pkgArg := range packages {
name, versionOrTag := parsePackageArg(pkgArg)
name, versionOrTag, err := parsePackageArg(pkgArg)
if err != nil {
return err
}

progress.Stream(wpmCli.Err(), fmt.Sprintf(" Resolving %s@%s [%d/%d]", name, versionOrTag, i+1, len(packages)))

Expand Down Expand Up @@ -196,10 +220,33 @@ func addPackages(ctx context.Context, config *wpmjson.Config, wpmCli command.Cli
return g.Wait()
}

func parsePackageArg(arg string) (string, string) {
lastAt := strings.LastIndex(arg, "@")
if lastAt > 0 {
return arg[:lastAt], arg[lastAt+1:]
func parsePackageArg(arg string) (string, string, error) {
if arg == "" {
return "", "", errors.New("package argument cannot be empty")
}

name := arg
versionOrTag := "latest"

if lastAt := strings.LastIndex(arg, "@"); lastAt > 0 {
name = arg[:lastAt]
versionOrTag = arg[lastAt+1:]

if versionOrTag == "" {
versionOrTag = "latest"
}
}

if err := validator.IsValidPackageName(name); err != nil {
return "", "", fmt.Errorf("invalid package name %q: %w", name, err)
}
return arg, "latest"

verErr := validator.IsValidVersion(versionOrTag)
tagErr := validator.IsValidDistTag(versionOrTag)

if verErr != nil && tagErr != nil {
return "", "", fmt.Errorf("invalid version or tag %q: must be a valid semver or dist tag", versionOrTag)
}

return name, versionOrTag, nil
}
10 changes: 9 additions & 1 deletion cli/command/install/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ func Run(ctx context.Context, cwd string, wpmCli command.Cli, opts RunOptions) e
if wpmCfg == nil {
return errors.New("wpm.json config is required")
}
if err := wpmCfg.ValidateDependencyNames(); err != nil {
return errors.Wrap(err, "invalid dependency name in wpm.json")
}

lock, err := wpmlock.Read(cwd)
if err != nil {
Expand Down Expand Up @@ -120,9 +123,14 @@ func Run(ctx context.Context, cwd string, wpmCli command.Cli, opts RunOptions) e
}

// -- Actual Install --
inst := installer.New(absContentDir, opts.NetworkConcurrency, client, func(format string, args ...any) {
inst, err := installer.New(ctx, absContentDir, opts.NetworkConcurrency, client, func(format string, args ...any) {
wpmCli.Output().ErrorWrite(fmt.Sprintf(format+"\n", args...))
})
if err != nil {
return errors.Wrap(err, "failed to initialize installer")
}
defer inst.Close()

if err := inst.InstallAll(ctx, plan, installerProgress(wpmCli.Output())); err != nil {
return errors.Wrap(err, "installation failed")
}
Expand Down
16 changes: 10 additions & 6 deletions cli/command/publish/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ import (
"github.com/spf13/cobra"
)

const maxReadmeSize = 50 * 1024 // 50KB
const (
maxReadmeSize = 50 * 1024 // 50KB
maxPackedSize int64 = 128 * 1024 * 1024 // 128MB
)

type publishOptions struct {
dryRun bool
Expand Down Expand Up @@ -121,9 +124,13 @@ func getReadme(dirPath string) (string, error) {

type tarballSizeCounter struct {
total int64
limit int64
}

func (c *tarballSizeCounter) Write(p []byte) (n int, err error) {
if c.limit > 0 && c.total+int64(len(p)) > c.limit {
return 0, fmt.Errorf("tarball size exceeds %d bytes, refusing to continue", c.limit)
}
c.total += int64(len(p))
return len(p), nil
}
Expand Down Expand Up @@ -169,9 +176,10 @@ func runPublish(ctx context.Context, wpmCli command.Cli, opts publishOptions) er
if err != nil {
return errors.Wrap(err, "failed to pack the package into a tarball")
}
defer tarballer.Close()

hasher := sha256.New()
counter := &tarballSizeCounter{}
counter := &tarballSizeCounter{limit: maxPackedSize}
multiWriter := io.MultiWriter(tempFile, hasher, counter)

packTarball := func() error {
Expand Down Expand Up @@ -202,10 +210,6 @@ func runPublish(ctx context.Context, wpmCli command.Cli, opts publishOptions) er
return errors.New("tarball size is zero, cannot publish empty package")
}

if counter.total > 128*1024*1024 {
return errors.New("tarball size exceeds 128mb, cannot publish package")
}

dim := aec.Faint.Apply
blue := aec.LightBlueF.Apply
w := tabwriter.NewWriter(wpmCli.Err(), 0, 0, 2, ' ', 0)
Expand Down
31 changes: 26 additions & 5 deletions cli/command/uninstall/uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ import (
"context"
"fmt"
"os"
"path/filepath"

"wpm/cli"
"wpm/cli/command"
"wpm/cli/command/install"
"wpm/cli/version"
"wpm/pkg/output"
"wpm/pkg/pm/workspace"
"wpm/pkg/pm/wpmjson"

"github.com/morikuni/aec"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)

Expand All @@ -37,6 +40,29 @@ func runUninstall(ctx context.Context, wpmCli command.Cli, packages []string) er
return err
}

wpmCli.Output().Prettyln(output.Text{
Plain: "wpm uninstall v" + version.Version,
Fancy: aec.Bold.Apply("wpm uninstall") + " " + aec.LightBlackF.Apply("v"+version.Version),
})

contentDir := wpmjson.New().ContentDir()
if probe, _ := wpmjson.Read(cwd); probe != nil {
contentDir = probe.ContentDir()
}

lock, err := workspace.AcquireLock(ctx, filepath.Join(cwd, contentDir), func() {
wpmCli.Output().PrettyErrorln(output.Text{
Plain: "waiting for another wpm process to finish in this workspace...",
Fancy: aec.Faint.Apply("waiting for another wpm process to finish in this workspace..."),
})
})
if err != nil {
return errors.Wrap(err, "failed to acquire workspace lock")
}
defer func() {
_ = lock.Release()
}()

cfg, err := wpmjson.Read(cwd)
if err != nil {
return err
Expand All @@ -46,11 +72,6 @@ func runUninstall(ctx context.Context, wpmCli command.Cli, packages []string) er
return fmt.Errorf("no wpm.json found, so nothing to uninstall")
}

wpmCli.Output().Prettyln(output.Text{
Plain: "wpm uninstall v" + version.Version,
Fancy: aec.Bold.Apply("wpm uninstall") + " " + aec.LightBlackF.Apply("v"+version.Version),
})

changed := false
for _, name := range packages {
if cfg.Dependencies != nil {
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/containerd/errdefs v1.0.0
github.com/docker/go-units v0.5.0
github.com/fvbommel/sortorder v1.1.0
github.com/gofrs/flock v0.13.0
github.com/henvic/httpretty v0.1.4
github.com/klauspost/compress v1.18.6
github.com/moby/patternmatcher v0.6.1
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw=
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
github.com/henvic/httpretty v0.1.4 h1:Jo7uwIRWVFxkqOnErcoYfH90o3ddQyVrSANeS4cxYmU=
github.com/henvic/httpretty v0.1.4/go.mod h1:Dn60sQTZfbt2dYsdUSNsCljyF4AfdqnuJFDLJA1I4AM=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
Expand Down Expand Up @@ -47,8 +49,8 @@ github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiT
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/thlib/go-timezone-local v0.0.7 h1:fX8zd3aJydqLlTs/TrROrIIdztzsdFV23OzOQx31jII=
github.com/thlib/go-timezone-local v0.0.7/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
Expand Down
Loading
Loading