Compare commits
5 Commits
Author | SHA1 | Date |
---|---|---|
OLUWADAMILOLA OKUSANYA | fe8c87de18 | |
OLUWADAMILOLA OKUSANYA | f3dede1e33 | |
OLUWADAMILOLA OKUSANYA | cb52ddab05 | |
OLUWADAMILOLA OKUSANYA | e3e81130ba | |
OLUWADAMILOLA OKUSANYA | 234b593c26 |
|
@ -0,0 +1,36 @@
|
|||
|
||||
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
bin
|
||||
bin/*
|
||||
rpm
|
||||
rpm/*
|
||||
dist
|
||||
|
||||
tmp/
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
coverage
|
||||
.coverage
|
||||
|
||||
|
||||
# ignore the following files/directories
|
||||
**/userdata/*
|
||||
examples
|
||||
tests
|
||||
e2e
|
||||
site/
|
||||
.vscode/
|
||||
.idea/
|
||||
.DS_Store
|
||||
__rd*
|
||||
tests/out
|
43
Makefile
43
Makefile
|
@ -1,11 +1,52 @@
|
|||
# Inspired from https://dustinspecker.com/posts/go-combined-unit-integration-code-coverage/ and https://netdevops.me/2023/test-coverage-for-go-integration-tests/
|
||||
BIN_DIR = $(CURDIR)/bin
|
||||
COVERAGE_DIR = $(CURDIR)/coverage
|
||||
BINARY = $(BIN_DIR)/gosimplenpm
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
go clean
|
||||
|
||||
.PHONY: dep
|
||||
dep:
|
||||
go mod tidy
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
go fmt ./...
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
golangci-lint run
|
||||
|
||||
coverage-unit:
|
||||
go test ./... -short -covermode=count -coverprofile=./coverage/unit.out
|
||||
go tool cover -func=./coverage/unit.out
|
||||
coverage-integration:
|
||||
go test ./... -run Integration -covermode=count -coverprofile=./coverage/integration.out
|
||||
go tool cover -func=./coverage/integration.out
|
||||
|
||||
.PHONY: build-debug
|
||||
build-debug:
|
||||
mkdir -p $(BIN_DIR)
|
||||
go build -o $(BINARY) -cover main.go
|
||||
|
||||
.PHONY: test
|
||||
test: build-debug
|
||||
rm -rf $(COVERAGE_DIR)
|
||||
mkdir -p $(COVERAGE_DIR)
|
||||
go test -cover ./... -args -test.gocoverdir="$(COVERAGE_DIR)"
|
||||
|
||||
.PHONY: coverage-full
|
||||
coverage-full: test
|
||||
go tool covdata textfmt -i=$(COVERAGE_DIR) -o $(COVERAGE_DIR)/coverage.out
|
||||
go tool cover -func=$(COVERAGE_DIR)/coverage.out
|
||||
|
||||
.PHONY: coverage-html
|
||||
coverage-html:
|
||||
go tool cover -html=./coverage/coverage.out -o ./coverage/coverage.html
|
||||
open ./coverage/coverage.html
|
||||
|
||||
.PHONY: lint-all
|
||||
lint-all:
|
||||
golangci-lint run --enable-all
|
||||
.PHONY: dep lint clean
|
||||
|
|
20
README.md
20
README.md
|
@ -10,6 +10,22 @@ TODO...
|
|||
|
||||
TODO...
|
||||
|
||||
# Work List
|
||||
# TODO List
|
||||
|
||||
TODO...
|
||||
- [x] Adding unit tests
|
||||
- [x] Adding integration tests
|
||||
- [ ] Adding e2e tests
|
||||
- [ ] Adding support for log files, that is, writing logs to a log file
|
||||
- [ ] Support [abbreviated_package_format](https://github.com/verdaccio/verdaccio/issues/2792)
|
||||
- [ ] Copy artifactory setup where there is a package.json (current package.json) and there is also an index json for the npm cli. Basically, the structure is like this
|
||||
- [ ] Add a max bytes for publishing as shown [here](https://stackoverflow.com/questions/28282370/is-it-advisable-to-further-limit-the-size-of-forms-when-using-golang)
|
||||
|
||||
```sh
|
||||
registry
|
||||
|-.npm
|
||||
|-|-@scope1/package1
|
||||
|-|-|-index.json
|
||||
|-@scope1/package1
|
||||
|-|-package1-x.x.x.tgz
|
||||
|-|-package.json
|
||||
```
|
||||
|
|
28
app.go
28
app.go
|
@ -1,28 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"gosimplenpm/handler"
|
||||
)
|
||||
|
||||
type application struct {
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func (app *application) Routes() *mux.Router {
|
||||
m := mux.NewRouter()
|
||||
|
||||
// main handler
|
||||
m.HandleFunc("/{name}", handler.Get).Methods("GET")
|
||||
m.HandleFunc("/{name}", handler.Publish).Methods("PUT")
|
||||
// tar handlers
|
||||
m.HandleFunc("/{name}/-/{tar}", handler.Tar).Methods("GET")
|
||||
// tag handlers
|
||||
m.HandleFunc("/-/package/{name}/dist-tags/{tag}", handler.DistTagDelete).Methods("DELETE")
|
||||
m.HandleFunc("/-/package/{name}/dist-tags/{tag}", handler.DistTagPut).Methods("PUT")
|
||||
m.HandleFunc("/-/package/{name}/dist-tags", handler.DistTagGet).Methods("GET")
|
||||
return m
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package gosimplenpm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gosimplenpm/internal/config"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Aliases: []string{"conf"},
|
||||
Short: "Display the config file",
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
err := config.PrintConfigFile()
|
||||
if err != nil {
|
||||
fmt.Printf("Error printing config: %+v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(configCmd)
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package gosimplenpm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gosimplenpm/internal/config"
|
||||
"gosimplenpm/internal/handler"
|
||||
"gosimplenpm/internal/storage"
|
||||
"os"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "gosimplenpm",
|
||||
Short: "Gosimplenpm is an implementation of npm registry server",
|
||||
Long: `Gosimplenpm is an implemenation of npm registry server.
|
||||
|
||||
It creates the repository to host all your private npm repos. It can
|
||||
serve the npm packages offline.
|
||||
|
||||
Documentation about the npm private registry:
|
||||
https://docs.npmjs.com/packages-and-modules
|
||||
`,
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
err := config.VerifyConfig()
|
||||
if err != nil {
|
||||
fmt.Printf("Error verifying config: %+v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
var cfg config.Config
|
||||
err = config.LoadOrCreateConfig(&cfg)
|
||||
if err != nil {
|
||||
fmt.Printf("Error loading config: %+v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
lvl, err := logrus.ParseLevel(cfg.LogLevel)
|
||||
if err != nil {
|
||||
fmt.Printf("%+v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
Level: lvl,
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
app := &handler.Application{
|
||||
Conf: cfg,
|
||||
Logger: log,
|
||||
FSStorage: &storage.FSStorage{},
|
||||
}
|
||||
|
||||
fmt.Println("\n Server is starting....")
|
||||
err = app.Start()
|
||||
if err != nil {
|
||||
fmt.Printf("Server start up error: %+v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.CompletionOptions.HiddenDefaultCmd = true
|
||||
// hide help command
|
||||
rootCmd.SetHelpCommand(&cobra.Command{Hidden: true})
|
||||
// hide help flag
|
||||
rootCmd.PersistentFlags().BoolP("help", "h", false, "This help")
|
||||
rootCmd.PersistentFlags().Lookup("help").Hidden = true
|
||||
// TODO: get some variables from flags
|
||||
rootCmd.Flags().StringVarP(&config.HTTPListen, "listen", "l", config.HTTPListen, "HTTP bind interface and port for server")
|
||||
_ = rootCmd.MarkFlagRequired("listen")
|
||||
rootCmd.Flags().BoolVarP(&config.CanLog, "verbose", "v", config.CanLog, "Logging level for server")
|
||||
rootCmd.Flags().StringVar(&config.NpmRepoDir, "repodir", config.NpmRepoDir, "Repo dir to house published packages")
|
||||
}
|
18
go.mod
18
go.mod
|
@ -2,6 +2,20 @@ module gosimplenpm
|
|||
|
||||
go 1.20
|
||||
|
||||
require github.com/gorilla/mux v1.8.0
|
||||
require (
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
golang.org/x/crypto v0.10.0
|
||||
golang.org/x/mod v0.11.0
|
||||
)
|
||||
|
||||
// replace gosimplenpm/handler => ./handler
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
golang.org/x/sys v0.9.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
30
go.sum
30
go.sum
|
@ -1,2 +1,32 @@
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"gosimplenpm/storage"
|
||||
)
|
||||
|
||||
func Get(w http.ResponseWriter, r *http.Request) {
|
||||
packageName := mux.Vars(r)["name"]
|
||||
|
||||
fileToServe, err := storage.GetPackageFromStore(packageName)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if fileToServe == "" {
|
||||
ret := fmt.Sprintf("Package not found: %s", packageName)
|
||||
http.Error(w, ret, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// serve file
|
||||
http.ServeFile(w, r, fileToServe)
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Publish(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func DistTagDelete(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func DistTagGet(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func DistTagPut(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Tar(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
|
@ -0,0 +1,318 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var (
|
||||
// File to store the app configuration, like username, password, token, repo dir, logging level
|
||||
ConfigFilePath string
|
||||
|
||||
// Repo dir
|
||||
NpmRepoDir string
|
||||
|
||||
// HTTPListen
|
||||
HTTPListen string
|
||||
|
||||
// Set Verbose Logging
|
||||
CanLog bool
|
||||
|
||||
// Logging Level
|
||||
LoggingLvl string
|
||||
|
||||
// Username
|
||||
RegUser string
|
||||
|
||||
// Password
|
||||
RegPwd string
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Token string `json:"token"`
|
||||
RepoDir string `json:"repoDir"`
|
||||
IpAddress string `json:"ipAddress"`
|
||||
LogLevel string `json:"logLevel"`
|
||||
}
|
||||
|
||||
func checkIfCorrectIPPort(s string) bool {
|
||||
host, port, _ := net.SplitHostPort(s)
|
||||
if host == "" || port == "" {
|
||||
return false
|
||||
}
|
||||
if net.ParseIP(host) != nil {
|
||||
return true
|
||||
}
|
||||
_, err := net.ResolveIPAddr("ip", host)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func VerifyConfig() error {
|
||||
if !checkIfCorrectIPPort(HTTPListen) {
|
||||
return errors.New("ip address should be in the format of <ip>:<port>")
|
||||
}
|
||||
|
||||
// Check if config file exists
|
||||
dirname, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configDirPath, err := checkOrCreateConfigDir(dirname, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Create a logging file (it should be located at .gosimplenpm/applogs)
|
||||
|
||||
ConfigFilePath = path.Join(configDirPath, "config.json")
|
||||
|
||||
if NpmRepoDir == "" {
|
||||
NpmRepoDir = path.Join(dirname, ".gosimplenpm", "registry")
|
||||
err := checkOrCreateRepoDir(NpmRepoDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if NpmRepoDir != "" {
|
||||
err := checkOrCreateRepoDir(NpmRepoDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if CanLog {
|
||||
LoggingLvl = "DEBUG"
|
||||
fmt.Println("\n Enabled debug logging")
|
||||
} else {
|
||||
LoggingLvl = "INFO"
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkOrCreateConfigDir(fp string, canCreate bool) (string, error) {
|
||||
configDirPath := path.Join(fp, ".gosimplenpm", "config")
|
||||
ok := isDir(configDirPath)
|
||||
if !ok && canCreate {
|
||||
err := os.MkdirAll(configDirPath, os.ModePerm)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if !ok && !canCreate {
|
||||
return "", nil
|
||||
}
|
||||
return configDirPath, nil
|
||||
}
|
||||
|
||||
func checkOrCreateRepoDir(repoDirPath string) error {
|
||||
ok := isDir(repoDirPath)
|
||||
if !ok {
|
||||
err := os.MkdirAll(repoDirPath, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createConfig(cfg *Config, recreate bool) error {
|
||||
var scanner *bufio.Scanner
|
||||
if recreate {
|
||||
fmt.Println("\nNew config variables. Saving...")
|
||||
} else {
|
||||
fmt.Println("\nConfig file is not found. Creating...")
|
||||
}
|
||||
|
||||
configFile, err := os.Create(ConfigFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer configFile.Close()
|
||||
|
||||
cfg.IpAddress = HTTPListen
|
||||
cfg.LogLevel = LoggingLvl
|
||||
|
||||
// Get username
|
||||
if cfg.Token == "" {
|
||||
fmt.Println("Enter your username: ")
|
||||
scanner = bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan()
|
||||
err = scanner.Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
RegUser = scanner.Text()
|
||||
|
||||
fmt.Println("Enter your password: ")
|
||||
scanner = bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan()
|
||||
err = scanner.Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
RegPwd = scanner.Text()
|
||||
|
||||
token, err := generateAuthToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg.Token = token
|
||||
}
|
||||
|
||||
fmt.Printf("The npm authToken is %s.\n", cfg.Token)
|
||||
|
||||
err = json.NewEncoder(configFile).Encode(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadConfig(cfg *Config) error {
|
||||
configFile, err := os.Open(ConfigFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer configFile.Close()
|
||||
|
||||
err = json.NewDecoder(configFile).Decode(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadOrCreateConfig(cfg *Config) error {
|
||||
var err error
|
||||
ok := isFile(ConfigFilePath)
|
||||
|
||||
// If file is not found
|
||||
if !ok {
|
||||
err = createConfig(cfg, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if ok {
|
||||
// File is found
|
||||
err = loadConfig(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.Token == "" || cfg.IpAddress != HTTPListen || cfg.LogLevel != LoggingLvl || cfg.RepoDir != NpmRepoDir {
|
||||
// recreate the config file
|
||||
err = createConfig(cfg, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func isFile(fp string) bool {
|
||||
info, err := os.Stat(fp)
|
||||
if os.IsNotExist(err) || !info.Mode().IsRegular() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isDir(fp string) bool {
|
||||
info, err := os.Stat(fp)
|
||||
if os.IsNotExist(err) || !info.IsDir() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Hash password
|
||||
func hashPassword(password string) (string, error) {
|
||||
|
||||
// Convert password string to byte slice
|
||||
var passwordBytes = []byte(password)
|
||||
// Hash password with Bcrypt's min cost
|
||||
hashedPasswordBytes, err := bcrypt.GenerateFromPassword(passwordBytes, bcrypt.MinCost)
|
||||
return string(hashedPasswordBytes), err
|
||||
}
|
||||
|
||||
// Check if two passwords match using Bcrypt's CompareHashAndPassword
|
||||
// which return nil on success and an error on failure.
|
||||
// func doPasswordsMatch(hashedPassword, currPassword string) bool {
|
||||
// err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(currPassword))
|
||||
// return err == nil
|
||||
// }
|
||||
|
||||
func generateAuthToken() (string, error) {
|
||||
hashed, err := hashPassword(RegPwd)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
token := fmt.Sprintf("%s::%s", RegUser, hashed)
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func PrintConfigFile() error {
|
||||
// Check if config file exists
|
||||
dirname, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configDirPath, err := checkOrCreateConfigDir(dirname, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if configDirPath == "" {
|
||||
return errors.New("config dir is not found")
|
||||
}
|
||||
|
||||
ConfigFilePath = path.Join(configDirPath, "config.json")
|
||||
ok := isFile(ConfigFilePath)
|
||||
if !ok {
|
||||
return errors.New("config file is not found")
|
||||
}
|
||||
|
||||
configFile, err := os.Open(ConfigFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer configFile.Close()
|
||||
|
||||
b, err := io.ReadAll(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var result map[string]interface{}
|
||||
err = json.Unmarshal([]byte(b), &result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Pretty-print the result
|
||||
marshaled, err := json.MarshalIndent(result, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Printing config located at %s: \n %s\n", ConfigFilePath, (marshaled))
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"gosimplenpm/internal/config"
|
||||
"gosimplenpm/internal/middlewares"
|
||||
"gosimplenpm/internal/storage"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Application struct {
|
||||
Logger *logrus.Logger
|
||||
Conf config.Config
|
||||
Mux *mux.Router
|
||||
FSStorage storage.Storage
|
||||
}
|
||||
|
||||
func (app *Application) Routes() {
|
||||
|
||||
// Need to use UseEncodedPath as shown here https://github.com/gorilla/mux/blob/master/mux.go#L269
|
||||
m := mux.NewRouter().StrictSlash(true).UseEncodedPath()
|
||||
|
||||
m.Use(middlewares.LogMiddleware(app.Logger))
|
||||
|
||||
// main handler
|
||||
m.HandleFunc("/{name}", GetPackage(app.Logger, app.Conf, app.FSStorage)).Methods("GET")
|
||||
m.HandleFunc("/{name}", middlewares.AuthMiddleware(app.Conf)(Publish(app.Logger, app.Conf, app.FSStorage))).Methods("PUT")
|
||||
// tar handlers
|
||||
m.HandleFunc("/{name}/-/{tar}", PackageTarGet(app.Logger, app.Conf, app.FSStorage)).Methods("GET")
|
||||
// tag handlers
|
||||
m.HandleFunc("/-/package/{name}/dist-tags/{tag}", middlewares.AuthMiddleware(app.Conf)(DistTagDelete(app.Logger, app.Conf, app.FSStorage))).Methods("DELETE")
|
||||
m.HandleFunc("/-/package/{name}/dist-tags/{tag}", middlewares.AuthMiddleware(app.Conf)(DistTagPut(app.Logger, app.Conf, app.FSStorage))).Methods("PUT")
|
||||
m.HandleFunc("/-/package/{name}/dist-tags", DistTagGet(app.Logger, app.Conf, app.FSStorage)).Methods("GET")
|
||||
m.NotFoundHandler = http.HandlerFunc(NotFound)
|
||||
app.Mux = m
|
||||
}
|
||||
|
||||
func (app *Application) Start() error {
|
||||
app.Routes()
|
||||
server := &http.Server{
|
||||
Addr: app.Conf.IpAddress,
|
||||
Handler: app.Mux,
|
||||
ReadTimeout: 4 * time.Second,
|
||||
WriteTimeout: 4 * time.Second,
|
||||
}
|
||||
return server.ListenAndServe()
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"gosimplenpm/internal/config"
|
||||
"gosimplenpm/internal/storage"
|
||||
)
|
||||
|
||||
func GetPackage(lg *logrus.Logger, cfg config.Config, stg storage.Storage) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
escapedName := mux.Vars(r)["name"]
|
||||
packageName, _ := url.PathUnescape(escapedName)
|
||||
lg.WithFields(logrus.Fields{
|
||||
"function": "get-package",
|
||||
}).Debugf("Package name => %s\n", packageName)
|
||||
|
||||
fileToServe, found, err := stg.GetIndexJsonFromStore(packageName, cfg.RepoDir, lg)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if !found {
|
||||
ret := fmt.Sprintf("Package not found: %s", packageName)
|
||||
http.Error(w, ret, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// serve file
|
||||
http.ServeFile(w, r, fileToServe)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,218 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gosimplenpm/internal/config"
|
||||
"gosimplenpm/internal/storage"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUnitGet(t *testing.T) {
|
||||
t.Run("return `Not Found` error if package is not found", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/{name}", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
log := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, log *logrus.Logger) (string, bool, error) {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
GetPackage(log, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusNotFound)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Package not found: test-package\n")
|
||||
})
|
||||
|
||||
t.Run("return `Internal Server` error if package cannot be retrieved", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/test-package", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
log := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, log *logrus.Logger) (string, bool, error) {
|
||||
return "", true, fmt.Errorf("filesystem error")
|
||||
}
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
GetPackage(log, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "filesystem error\n")
|
||||
})
|
||||
|
||||
t.Run("return a file if package is found", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
f, err := os.CreateTemp(tmpDir, "foo.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString("{data: \"test data\"}")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/test-oackage", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
log := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, log *logrus.Logger) (string, bool, error) {
|
||||
return f.Name(), true, nil
|
||||
}
|
||||
// mfs.SetRetrieved(true)
|
||||
// mfs.SetFileToServe(f.Name())
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
GetPackage(log, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusOK)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "{data: \"test data\"}")
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationGet(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping getPackage integration test")
|
||||
}
|
||||
|
||||
token := "0N89nr/hmKXoBzG]R{fKH%YE1X"
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
t.Logf("Temp Dir: %s", tmpDir)
|
||||
|
||||
// cpFolders(t, "intestdata/@df", fmt.Sprintf("%s/@df", tmpDir))
|
||||
|
||||
mkDir(t, fmt.Sprintf("%s/@df/simplepackone", tmpDir))
|
||||
|
||||
indexJsonFp := "intestdata/get/index.json"
|
||||
tgzFile := "intestdata/get/simplepackone-1.0.0.tgz"
|
||||
|
||||
cpFile(t, indexJsonFp, fmt.Sprintf("%s/@df/simplepackone/index.json", tmpDir))
|
||||
cpFile(t, tgzFile, fmt.Sprintf("%s/@df/simplepackone/simplepackone-1.0.0.tgz", tmpDir))
|
||||
|
||||
listDir(t, fmt.Sprintf("%s/", tmpDir), true)
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: tmpDir,
|
||||
Token: token,
|
||||
}
|
||||
|
||||
app := newTestApp(t, cfg)
|
||||
app.Routes()
|
||||
ts := newTestServer(t, app.Mux)
|
||||
defer ts.Close()
|
||||
|
||||
code, _, body := ts.get(t, fmt.Sprintf("/%s", url.PathEscape("@df/simplepackone")))
|
||||
|
||||
assert.Equal(t, code, http.StatusOK)
|
||||
|
||||
expected := readTestFile(t, indexJsonFp)
|
||||
var resultExpected map[string]interface{}
|
||||
var resultBody map[string]interface{}
|
||||
json.Unmarshal(expected, &resultExpected)
|
||||
json.Unmarshal(body, &resultBody)
|
||||
|
||||
assert.Equal(t, resultBody, resultExpected)
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
{"_id":"@df/simplepackone","name":"@df/simplepackone","description":"This is a very rough implementation of a private npm registry.","dist-tags":{"latest":"1.0.0"},"time":{"1.0.0":"2023-12-24T11:04:34-05:00","created":"2023-12-24T11:04:34-05:00","modified":"2023-12-24T11:04:34-05:00","unpublished":""},"versions":{"1.0.0":{"name":"@df/simplepackone","version":"1.0.0","description":"This is a very rough implementation of a private npm registry.","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" \u0026\u0026 exit 1"},"license":"MIT","repository":{"type":"","url":"","directory":""},"readme":"# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...","readmeFilename":"README.md","_id":"@df/simplepackone@1.0.0","maintainers.omitempty":null,"bugs":{},"bin":null,"engines":{},"_nodeVersion":"16.20.0","_npmVersion":"8.19.4","dist":{"integrity":"sha512-9NI+Kqf+4C8Rr5GPKen8o6hhp/LMMeox96du65v6T+W27irVSsxZP0grBHBFyfd9whDXOoSliRWvYBnGnELlnA==","shasum":"a3974e824557b8e7ba00b87fb4ec51e4d132fff2","tarball":"http://localhost:6000/@df/simplepackone/-/@df/simplepackone-1.0.0.tgz","fileCount":0,"unpackedSize":0},"dependencies":{"eslint":"^7.x","mocha":"^10.x","uuid":"^9.x"}}},"access":"","_attachments":{"@df/simplepackone-1.0.0.tgz":{"content_type":"application/octet-stream","data":"H4sIAAAAAAAC/+1XbW/bNhDOZ/2KgwoUCSAospsXLJ+m2HQsTJYMSW4WoCggS7TFVhYFksoLhv73HSknTtp93Dys88EAwXt77o6+I9Xmxdd8TU9ZU9JH94s8+gfI87yLszP4K76h4Qc4+nB+4Q0GFxce6nkD73Lo4Xq0B+qkygWG8jckiQQv63+ECt5IXlO35utje0oFtU+ODvT/oXbb/9sVJwBv9tz/Z+dn3/f/4Px8cOj/fdAfFoDd5BtqX4H9a7k6lWzT1lT/HXhDbUeL76mQjDdaY+B6rtdzSyoLwVq1lfTMTc7M7vk66bm9okSBhkOGolJpNVpUHD7ZRAgurqDhoAUgW1qwFaPlJxvevwf6yBQMbLT8Zrzlnaq42GHWrKCNNAnMguw5uJZiCE3B6CtYKmvWGODPl+6j0dQx86LKDXPg7bhdx0r7yv78C7I0tPXN+pn7PyH+eEbcTbn/+384HPxw/59/GB76fx/0Dm543/JNu7GsrGIS8JcDNv0TCN6tKzDiDW1Urpsd+ArFrWD3uaKAViDomkklnlzLegcTmqtOUIm+4nHsuoZJmipvCuPjreCWi68QovUL93Al/yv9HwYjEqXEVY9qz/0/9M68H+5/7/Ly0P/7IGvE2yfB1pWC4+IEhvpj7M1EmFOxYVLf/3ouVPiFsHyCtcgbRUsHVoJSPRDwBhVr6oDikDdP0OKLQU+KpcL3AGvWVg4F4mhNpSeM5Cv1kAuKyiXkUvKC4TApoeRFtxs0K1ZTCceqopadbi3sEwNS0rwG1qA3Cs8ieGD4MOgUziOcRqzQPhxUKuqu1DE8i2u2YVsEbW6Sl9ppJzEDHacDG16ylV6pSavtljWTlWOVetCxZaeQKTXTPD0cnccpFyBpXWsP+Oroc91FZ3QQxWp1QdW2RAb3oeKbt5lgiVadaBCSliZdjiUziF9oobQXrb7idc0fMDWEbEqmM5JXeoZjXZf8nppc+rNtuMJQ+xD0AbS7U92KZJVj7Etq9QVDXCxv/iodoeGxWRrFsPYtFwbv+zRxqmdTAmk8yW79hECQwjyJPwZjMgbbT3FvO3AbZNN4kQFqJH6U3UE8AT+6g9+CaOxY5Pd5QtIU4gSC2TwMyNiBIBqFi3EQ3cA12kVxBmGAjz10msWgAbeuAoJ2E2tGktEUt/51EAbZnQOTIIu0zwk69WHuJ1kwWoR+AvNFMo9TgvBjK4qjIJokiEJmJMpcREUoIB9xA+nUD0MD5S8w+sTEN4rnd0lwM81gGodjgsxrYoWBfx2SHgqTGoV+MHNg7M/8G2KsYvSSgFbbRnc7JYaFeD7+RlkQRxbWZBRHWYJbB7NMshfT2yAlDvhJkOqCTJIY3etyokVsnKBdRHovutTw5kRQRe8XKdnFMiZ+iL5Sbfxa2bUON+SBDnSgA/2c9CfXO4iiABgAAA==","length":1075}}}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "@df/simplepackone",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eslint": "^7.x",
|
||||
"mocha": "^10.x",
|
||||
"uuid":"^9.x"
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
{"_id":"@df/simplepackone","name":"@df/simplepackone","description":"This is a very rough implementation of a private npm registry.","dist-tags":{"latest":"1.0.0"},"time":{"1.0.0":"2023-12-24T11:04:34-05:00","created":"2023-12-24T11:04:34-05:00","modified":"2023-12-24T11:04:34-05:00","unpublished":""},"versions":{"1.0.0":{"name":"@df/simplepackone","version":"1.0.0","description":"This is a very rough implementation of a private npm registry.","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" \u0026\u0026 exit 1"},"license":"MIT","repository":{"type":"","url":"","directory":""},"readme":"# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...","readmeFilename":"README.md","_id":"@df/simplepackone@1.0.0","maintainers.omitempty":null,"bugs":{},"bin":null,"engines":{},"_nodeVersion":"16.20.0","_npmVersion":"8.19.4","dist":{"integrity":"sha512-9NI+Kqf+4C8Rr5GPKen8o6hhp/LMMeox96du65v6T+W27irVSsxZP0grBHBFyfd9whDXOoSliRWvYBnGnELlnA==","shasum":"a3974e824557b8e7ba00b87fb4ec51e4d132fff2","tarball":"http://localhost:6000/@df/simplepackone/-/@df/simplepackone-1.0.0.tgz","fileCount":0,"unpackedSize":0},"dependencies":{"eslint":"^7.x","mocha":"^10.x","uuid":"^9.x"}}},"access":"","_attachments":{"@df/simplepackone-1.0.0.tgz":{"content_type":"application/octet-stream","data":"H4sIAAAAAAAC/+1XbW/bNhDOZ/2KgwoUCSAospsXLJ+m2HQsTJYMSW4WoCggS7TFVhYFksoLhv73HSknTtp93Dys88EAwXt77o6+I9Xmxdd8TU9ZU9JH94s8+gfI87yLszP4K76h4Qc4+nB+4Q0GFxce6nkD73Lo4Xq0B+qkygWG8jckiQQv63+ECt5IXlO35utje0oFtU+ODvT/oXbb/9sVJwBv9tz/Z+dn3/f/4Px8cOj/fdAfFoDd5BtqX4H9a7k6lWzT1lT/HXhDbUeL76mQjDdaY+B6rtdzSyoLwVq1lfTMTc7M7vk66bm9okSBhkOGolJpNVpUHD7ZRAgurqDhoAUgW1qwFaPlJxvevwf6yBQMbLT8Zrzlnaq42GHWrKCNNAnMguw5uJZiCE3B6CtYKmvWGODPl+6j0dQx86LKDXPg7bhdx0r7yv78C7I0tPXN+pn7PyH+eEbcTbn/+384HPxw/59/GB76fx/0Dm543/JNu7GsrGIS8JcDNv0TCN6tKzDiDW1Urpsd+ArFrWD3uaKAViDomkklnlzLegcTmqtOUIm+4nHsuoZJmipvCuPjreCWi68QovUL93Al/yv9HwYjEqXEVY9qz/0/9M68H+5/7/Ly0P/7IGvE2yfB1pWC4+IEhvpj7M1EmFOxYVLf/3ouVPiFsHyCtcgbRUsHVoJSPRDwBhVr6oDikDdP0OKLQU+KpcL3AGvWVg4F4mhNpSeM5Cv1kAuKyiXkUvKC4TApoeRFtxs0K1ZTCceqopadbi3sEwNS0rwG1qA3Cs8ieGD4MOgUziOcRqzQPhxUKuqu1DE8i2u2YVsEbW6Sl9ppJzEDHacDG16ylV6pSavtljWTlWOVetCxZaeQKTXTPD0cnccpFyBpXWsP+Oroc91FZ3QQxWp1QdW2RAb3oeKbt5lgiVadaBCSliZdjiUziF9oobQXrb7idc0fMDWEbEqmM5JXeoZjXZf8nppc+rNtuMJQ+xD0AbS7U92KZJVj7Etq9QVDXCxv/iodoeGxWRrFsPYtFwbv+zRxqmdTAmk8yW79hECQwjyJPwZjMgbbT3FvO3AbZNN4kQFqJH6U3UE8AT+6g9+CaOxY5Pd5QtIU4gSC2TwMyNiBIBqFi3EQ3cA12kVxBmGAjz10msWgAbeuAoJ2E2tGktEUt/51EAbZnQOTIIu0zwk69WHuJ1kwWoR+AvNFMo9TgvBjK4qjIJokiEJmJMpcREUoIB9xA+nUD0MD5S8w+sTEN4rnd0lwM81gGodjgsxrYoWBfx2SHgqTGoV+MHNg7M/8G2KsYvSSgFbbRnc7JYaFeD7+RlkQRxbWZBRHWYJbB7NMshfT2yAlDvhJkOqCTJIY3etyokVsnKBdRHovutTw5kRQRe8XKdnFMiZ+iL5Sbfxa2bUON+SBDnSgA/2c9CfXO4iiABgAAA==","length":1075}}}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "@df/simplepackone",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eslint": "^7.x",
|
||||
"mocha": "^10.x",
|
||||
"uuid":"^9.x"
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
{"_id":"@df/simplepackone","name":"@df/simplepackone","description":"This is a very rough implementation of a private npm registry.","dist-tags":{"latest":"1.9.0"},"time":{"1.0.0":"2023-12-24T11:04:34-05:00","1.9.0":"2023-12-24T13:16:16-05:00","created":"2023-12-24T11:04:34-05:00","modified":"2023-12-24T13:16:16-05:00","unpublished":""},"versions":{"1.0.0":{"name":"@df/simplepackone","version":"1.0.0","description":"This is a very rough implementation of a private npm registry.","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" \u0026\u0026 exit 1"},"license":"MIT","repository":{"type":"","url":"","directory":""},"readme":"# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...","readmeFilename":"README.md","_id":"@df/simplepackone@1.0.0","maintainers.omitempty":null,"bugs":{},"bin":null,"engines":{},"_nodeVersion":"16.20.0","_npmVersion":"8.19.4","dist":{"integrity":"sha512-9NI+Kqf+4C8Rr5GPKen8o6hhp/LMMeox96du65v6T+W27irVSsxZP0grBHBFyfd9whDXOoSliRWvYBnGnELlnA==","shasum":"a3974e824557b8e7ba00b87fb4ec51e4d132fff2","tarball":"http://localhost:6000/@df/simplepackone/-/@df/simplepackone-1.0.0.tgz","fileCount":0,"unpackedSize":0},"dependencies":{"eslint":"^7.x","mocha":"^10.x","uuid":"^9.x"}},"1.9.0":{"name":"@df/simplepackone","version":"1.9.0","description":"This is a very rough implementation of a private npm registry.","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" \u0026\u0026 exit 1"},"license":"MIT","repository":{"type":"","url":"","directory":""},"readme":"# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...","readmeFilename":"README.md","_id":"@df/simplepackone@1.9.0","maintainers.omitempty":null,"bugs":{},"bin":null,"engines":{},"_nodeVersion":"16.20.0","_npmVersion":"8.19.4","dist":{"integrity":"sha512-TU3UpYnWUeZ4wQmwpMIZGLSPWYkbAP3pOdHmlX2nelmd2zhsCRWHoOLaCyxOrsn6KAmR0M9da5ggHZ9NUnGiCA==","shasum":"047e8445501a52ce658a036f8bff356f8d762762","tarball":"http://localhost:6000/@df%2Fsimplepackone/-/@df%2Fsimplepackone-1.9.0.tgz","fileCount":0,"unpackedSize":0},"dependencies":{"csv":"^6.x","eslint":"^7.x","mocha":"^10.x","uuid":"^9.x"}}},"access":"","_attachments":{"@df/simplepackone-1.9.0.tgz":{"content_type":"application/octet-stream","data":"H4sIAAAAAAAC/+1XbW/bNhDOZ/2KgwoUCSAospM4aD5NselYqCwZktwsQFFAlmiLrSwKJJUXDP3vO9JOnLT7uHlY54MBgvf23B19R6rNi2/5ip6ypqSP7ld59A+Q53mD83P4K76h/hkcnV0MvF5vMPBQz+t5l30P16M9UCdVLjCUvyFJJHhZ/yNU8Ebymro1Xx3bEyqofXJ0oP8Ptdv+3644AXiz5/4/vxz82P+9i4uLQ//vg/6wAOwmX1P7CuzfyuWpZOu2pvrvwBtqO1p8T4VkvNEaPfeD6224JZWFYK3aSjbMdc7M7vk62XA3ihIFGg4Zikql1WhRcfhsEyG4uIKGgxaAbGnBloyWn214/x7oI1PQs9Hyu/GWd6riYodZs4I20iQwDbLn4FqKITQFo69gqaxZY4C/XLqPRlPHzIsqN8yet+N2HSvtK/vLhx2rkPeaM0CODsb6bv06/Z8QfzQl7rrc//3f7/d+uv8vzvqH/t8HvYMbvmn5pl1bVlYxCfjLAZv+CQTvVhUY8Zo2KtfNDnyJ4law+1xRQCsQdMWkEk+uZb2DMc1VJ6hEX/Eodl3DJE2VN4Xx8VZwy8U3CNH6hXu4kv+V/g+DIYlS4qpHtef+73vn3k/3v3d5eej/fZA15O2TYKtKwXFxAn39MfZmIsyoWDOp7389Fyr8Qlg8wUrkjaKlA0tBqR4IeIOKFXVAccibJ2jxxaAnxULhe4A1KyuHAnG0ptITRvKlesgFReUScil5wXCYlFDyotsNmiWrqYRjVVHLTrcW9okBKWleA2vQG4VnETwwfBh0CucRTiNWaB8OKhV1V+oYnsU1W7MtgjY3yUvttJOYgY7TgTUv2VKv1KTVdouaycqxSj3o2KJTyJSaaZ4ejs7jlAuQtK61B3x1bHLdRWd0EMVqdUHVtkQG96Hi67eZYImWnWgQkpYmXY4lM4hfaaG0F62+5HXNHzA1hGxKpjOSV3qGY10X/J6aXDZn23CFoW5C0AfQ7k51K5JVjrEvqLUpGOJiefNX6QgNj83SKIa1b7kweD+miVM9mxBI43F26ycEghRmSfwpGJER2H6Ke9uB2yCbxPMMUCPxo+wO4jH40R18DKKRY5HfZwlJU4gTCKazMCAjB4JoGM5HQXQD12gXxRmEAT720GkWgwbcugoI2o2tKUmGE9z610EYZHcOjIMs0j7H6NSHmZ9kwXAe+gnM5sksTgnCj6wojoJonCAKmZIocxEVoYB8wg2kEz8MDZQ/x+gTE98wnt0lwc0kg0kcjggyr4kVBv51SDZQmNQw9IOpAyN/6t8QYxWjlwS02ja62wkxLMTz8TfMgjiysCbDOMoS3DqYZZK9mN4GKXHAT4JUF2ScxOhelxMtYuME7SKy8aJLDW9OBFX0fp6SXSwj4ofoK9XGr5Vd63BDHuhABzrQr0l/AlppbUsAGAAA","length":1083}}}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "@df/simplepackone",
|
||||
"version": "1.9.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eslint": "^7.x",
|
||||
"mocha": "^10.x",
|
||||
"uuid":"^9.x",
|
||||
"csv":"^6.x"
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
{"_id":"@df/simplepackone","name":"@df/simplepackone","description":"This is a very rough implementation of a private npm registry.","dist-tags":{"latest":"1.9.0", "pre-alpha":"1.0.0"},"time":{"1.0.0":"2023-12-24T11:04:34-05:00","1.9.0":"2023-12-24T13:16:16-05:00","created":"2023-12-24T11:04:34-05:00","modified":"2023-12-24T13:16:16-05:00","unpublished":""},"versions":{"1.0.0":{"name":"@df/simplepackone","version":"1.0.0","description":"This is a very rough implementation of a private npm registry.","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" \u0026\u0026 exit 1"},"license":"MIT","repository":{"type":"","url":"","directory":""},"readme":"# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...","readmeFilename":"README.md","_id":"@df/simplepackone@1.0.0","maintainers.omitempty":null,"bugs":{},"bin":null,"engines":{},"_nodeVersion":"16.20.0","_npmVersion":"8.19.4","dist":{"integrity":"sha512-9NI+Kqf+4C8Rr5GPKen8o6hhp/LMMeox96du65v6T+W27irVSsxZP0grBHBFyfd9whDXOoSliRWvYBnGnELlnA==","shasum":"a3974e824557b8e7ba00b87fb4ec51e4d132fff2","tarball":"http://localhost:6000/@df/simplepackone/-/@df/simplepackone-1.0.0.tgz","fileCount":0,"unpackedSize":0},"dependencies":{"eslint":"^7.x","mocha":"^10.x","uuid":"^9.x"}},"1.9.0":{"name":"@df/simplepackone","version":"1.9.0","description":"This is a very rough implementation of a private npm registry.","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" \u0026\u0026 exit 1"},"license":"MIT","repository":{"type":"","url":"","directory":""},"readme":"# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...","readmeFilename":"README.md","_id":"@df/simplepackone@1.9.0","maintainers.omitempty":null,"bugs":{},"bin":null,"engines":{},"_nodeVersion":"16.20.0","_npmVersion":"8.19.4","dist":{"integrity":"sha512-TU3UpYnWUeZ4wQmwpMIZGLSPWYkbAP3pOdHmlX2nelmd2zhsCRWHoOLaCyxOrsn6KAmR0M9da5ggHZ9NUnGiCA==","shasum":"047e8445501a52ce658a036f8bff356f8d762762","tarball":"http://localhost:6000/@df%2Fsimplepackone/-/@df%2Fsimplepackone-1.9.0.tgz","fileCount":0,"unpackedSize":0},"dependencies":{"csv":"^6.x","eslint":"^7.x","mocha":"^10.x","uuid":"^9.x"}}},"access":"","_attachments":{"@df/simplepackone-1.9.0.tgz":{"content_type":"application/octet-stream","data":"H4sIAAAAAAAC/+1XbW/bNhDOZ/2KgwoUCSAospM4aD5NselYqCwZktwsQFFAlmiLrSwKJJUXDP3vO9JOnLT7uHlY54MBgvf23B19R6rNi2/5ip6ypqSP7ld59A+Q53mD83P4K76h/hkcnV0MvF5vMPBQz+t5l30P16M9UCdVLjCUvyFJJHhZ/yNU8Ebymro1Xx3bEyqofXJ0oP8Ptdv+3644AXiz5/4/vxz82P+9i4uLQ//vg/6wAOwmX1P7CuzfyuWpZOu2pvrvwBtqO1p8T4VkvNEaPfeD6224JZWFYK3aSjbMdc7M7vk62XA3ihIFGg4Zikql1WhRcfhsEyG4uIKGgxaAbGnBloyWn214/x7oI1PQs9Hyu/GWd6riYodZs4I20iQwDbLn4FqKITQFo69gqaxZY4C/XLqPRlPHzIsqN8yet+N2HSvtK/vLhx2rkPeaM0CODsb6bv06/Z8QfzQl7rrc//3f7/d+uv8vzvqH/t8HvYMbvmn5pl1bVlYxCfjLAZv+CQTvVhUY8Zo2KtfNDnyJ4law+1xRQCsQdMWkEk+uZb2DMc1VJ6hEX/Eodl3DJE2VN4Xx8VZwy8U3CNH6hXu4kv+V/g+DIYlS4qpHtef+73vn3k/3v3d5eej/fZA15O2TYKtKwXFxAn39MfZmIsyoWDOp7389Fyr8Qlg8wUrkjaKlA0tBqR4IeIOKFXVAccibJ2jxxaAnxULhe4A1KyuHAnG0ptITRvKlesgFReUScil5wXCYlFDyotsNmiWrqYRjVVHLTrcW9okBKWleA2vQG4VnETwwfBh0CucRTiNWaB8OKhV1V+oYnsU1W7MtgjY3yUvttJOYgY7TgTUv2VKv1KTVdouaycqxSj3o2KJTyJSaaZ4ejs7jlAuQtK61B3x1bHLdRWd0EMVqdUHVtkQG96Hi67eZYImWnWgQkpYmXY4lM4hfaaG0F62+5HXNHzA1hGxKpjOSV3qGY10X/J6aXDZn23CFoW5C0AfQ7k51K5JVjrEvqLUpGOJiefNX6QgNj83SKIa1b7kweD+miVM9mxBI43F26ycEghRmSfwpGJER2H6Ke9uB2yCbxPMMUCPxo+wO4jH40R18DKKRY5HfZwlJU4gTCKazMCAjB4JoGM5HQXQD12gXxRmEAT720GkWgwbcugoI2o2tKUmGE9z610EYZHcOjIMs0j7H6NSHmZ9kwXAe+gnM5sksTgnCj6wojoJonCAKmZIocxEVoYB8wg2kEz8MDZQ/x+gTE98wnt0lwc0kg0kcjggyr4kVBv51SDZQmNQw9IOpAyN/6t8QYxWjlwS02ja62wkxLMTz8TfMgjiysCbDOMoS3DqYZZK9mN4GKXHAT4JUF2ScxOhelxMtYuME7SKy8aJLDW9OBFX0fp6SXSwj4ofoK9XGr5Vd63BDHuhABzrQr0l/AlppbUsAGAAA","length":1083}}}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "@df/simplepackone",
|
||||
"version": "1.9.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eslint": "^7.x",
|
||||
"mocha": "^10.x",
|
||||
"uuid":"^9.x",
|
||||
"csv":"^6.x"
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
{"_id":"@df/simplepackone","name":"@df/simplepackone","description":"This is a very rough implementation of a private npm registry.","dist-tags":{"latest":"1.0.0"},"time":{"1.0.0":"2023-12-24T11:04:34-05:00","created":"2023-12-24T11:04:34-05:00","modified":"2023-12-24T11:04:34-05:00","unpublished":""},"versions":{"1.0.0":{"name":"@df/simplepackone","version":"1.0.0","description":"This is a very rough implementation of a private npm registry.","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" \u0026\u0026 exit 1"},"license":"MIT","repository":{"type":"","url":"","directory":""},"readme":"# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...","readmeFilename":"README.md","_id":"@df/simplepackone@1.0.0","maintainers.omitempty":null,"bugs":{},"bin":null,"engines":{},"_nodeVersion":"16.20.0","_npmVersion":"8.19.4","dist":{"integrity":"sha512-9NI+Kqf+4C8Rr5GPKen8o6hhp/LMMeox96du65v6T+W27irVSsxZP0grBHBFyfd9whDXOoSliRWvYBnGnELlnA==","shasum":"a3974e824557b8e7ba00b87fb4ec51e4d132fff2","tarball":"http://localhost:6000/@df/simplepackone/-/@df/simplepackone-1.0.0.tgz","fileCount":0,"unpackedSize":0},"dependencies":{"eslint":"^7.x","mocha":"^10.x","uuid":"^9.x"}}},"access":"","_attachments":{"@df/simplepackone-1.0.0.tgz":{"content_type":"application/octet-stream","data":"H4sIAAAAAAAC/+1XbW/bNhDOZ/2KgwoUCSAospsXLJ+m2HQsTJYMSW4WoCggS7TFVhYFksoLhv73HSknTtp93Dys88EAwXt77o6+I9Xmxdd8TU9ZU9JH94s8+gfI87yLszP4K76h4Qc4+nB+4Q0GFxce6nkD73Lo4Xq0B+qkygWG8jckiQQv63+ECt5IXlO35utje0oFtU+ODvT/oXbb/9sVJwBv9tz/Z+dn3/f/4Px8cOj/fdAfFoDd5BtqX4H9a7k6lWzT1lT/HXhDbUeL76mQjDdaY+B6rtdzSyoLwVq1lfTMTc7M7vk66bm9okSBhkOGolJpNVpUHD7ZRAgurqDhoAUgW1qwFaPlJxvevwf6yBQMbLT8Zrzlnaq42GHWrKCNNAnMguw5uJZiCE3B6CtYKmvWGODPl+6j0dQx86LKDXPg7bhdx0r7yv78C7I0tPXN+pn7PyH+eEbcTbn/+384HPxw/59/GB76fx/0Dm543/JNu7GsrGIS8JcDNv0TCN6tKzDiDW1Urpsd+ArFrWD3uaKAViDomkklnlzLegcTmqtOUIm+4nHsuoZJmipvCuPjreCWi68QovUL93Al/yv9HwYjEqXEVY9qz/0/9M68H+5/7/Ly0P/7IGvE2yfB1pWC4+IEhvpj7M1EmFOxYVLf/3ouVPiFsHyCtcgbRUsHVoJSPRDwBhVr6oDikDdP0OKLQU+KpcL3AGvWVg4F4mhNpSeM5Cv1kAuKyiXkUvKC4TApoeRFtxs0K1ZTCceqopadbi3sEwNS0rwG1qA3Cs8ieGD4MOgUziOcRqzQPhxUKuqu1DE8i2u2YVsEbW6Sl9ppJzEDHacDG16ylV6pSavtljWTlWOVetCxZaeQKTXTPD0cnccpFyBpXWsP+Oroc91FZ3QQxWp1QdW2RAb3oeKbt5lgiVadaBCSliZdjiUziF9oobQXrb7idc0fMDWEbEqmM5JXeoZjXZf8nppc+rNtuMJQ+xD0AbS7U92KZJVj7Etq9QVDXCxv/iodoeGxWRrFsPYtFwbv+zRxqmdTAmk8yW79hECQwjyJPwZjMgbbT3FvO3AbZNN4kQFqJH6U3UE8AT+6g9+CaOxY5Pd5QtIU4gSC2TwMyNiBIBqFi3EQ3cA12kVxBmGAjz10msWgAbeuAoJ2E2tGktEUt/51EAbZnQOTIIu0zwk69WHuJ1kwWoR+AvNFMo9TgvBjK4qjIJokiEJmJMpcREUoIB9xA+nUD0MD5S8w+sTEN4rnd0lwM81gGodjgsxrYoWBfx2SHgqTGoV+MHNg7M/8G2KsYvSSgFbbRnc7JYaFeD7+RlkQRxbWZBRHWYJbB7NMshfT2yAlDvhJkOqCTJIY3etyokVsnKBdRHovutTw5kRQRe8XKdnFMiZ+iL5Sbfxa2bUON+SBDnSgA/2c9CfXO4iiABgAAA==","length":1075}}}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "@df/simplepackone",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eslint": "^7.x",
|
||||
"mocha": "^10.x",
|
||||
"uuid":"^9.x"
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,10 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// This handler is executed when the router cannot match any route
|
||||
func NotFound(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "Invalid url", http.StatusBadRequest)
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gosimplenpm/internal/config"
|
||||
"gosimplenpm/internal/serviceidos"
|
||||
"gosimplenpm/internal/storage"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type NPMClientPutRequest struct {
|
||||
Request serviceidos.IndexJson
|
||||
}
|
||||
|
||||
func Publish(lg *logrus.Logger, cfg config.Config, stg storage.Storage) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// (1) Parse Json Body
|
||||
// (2) Check if package exists in the folder.
|
||||
// (a) if it does, ckeck if it is the same version. If it is, return error. Else modify index.json from (2)
|
||||
// (b) If it does not, add the latest tag to the new index.json
|
||||
|
||||
escapedName := mux.Vars(r)["name"]
|
||||
packageName, _ := url.PathUnescape(escapedName)
|
||||
lg.WithFields(logrus.Fields{
|
||||
"function": "publish",
|
||||
}).Debugf("Package name => %s\n", packageName)
|
||||
|
||||
var cr NPMClientPutRequest
|
||||
// Parse json body
|
||||
err := json.NewDecoder(r.Body).Decode(&cr.Request)
|
||||
|
||||
if err != nil {
|
||||
lg.WithFields(logrus.Fields{
|
||||
"function": "publish",
|
||||
}).Debugf("Error unmarshaling put request: %+v\n", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract relevant data from index.json
|
||||
fmt.Printf("cRequest => %+v\n", cr)
|
||||
index := 0
|
||||
var tag string
|
||||
var version string
|
||||
var versionData serviceidos.IndexJsonVersions
|
||||
// TODO: Fix this as the order is not guaranteed
|
||||
for key, value := range cr.Request.DistTags {
|
||||
if index == 0 {
|
||||
tag = key
|
||||
version = value
|
||||
break
|
||||
}
|
||||
index++
|
||||
}
|
||||
versionData = cr.Request.Versions[version]
|
||||
|
||||
lg.WithFields(logrus.Fields{
|
||||
"function": "publish",
|
||||
}).Debugf("For version(%s) with tag(%s), versionData => %+v\n", version, tag, versionData)
|
||||
|
||||
// Rewrite the tarball path
|
||||
tarballFileName := strings.Split(versionData.Dist.Tarball, "/-/")[1]
|
||||
tarballFileName, _ = url.PathUnescape(tarballFileName)
|
||||
|
||||
lg.WithFields(logrus.Fields{
|
||||
"function": "publish",
|
||||
}).Debugf("TarballName => %s\n", tarballFileName)
|
||||
// versionData.Dist.Tarball = fmt.Sprintf("file://%s", packageFilePath)
|
||||
versionData.Dist.Tarball = fmt.Sprintf("http://%s/%s/-/%s", r.Host, url.PathEscape(packageName), url.PathEscape(tarballFileName))
|
||||
|
||||
lg.WithFields(logrus.Fields{
|
||||
"function": "publish",
|
||||
}).Debugf("versionData.Dist.Tarball => %s\n", versionData.Dist.Tarball)
|
||||
|
||||
tarBallFile := strings.Split(tarballFileName, "/")[1]
|
||||
packageFilePath := path.Join(cfg.RepoDir, packageName, tarBallFile)
|
||||
lg.WithFields(logrus.Fields{
|
||||
"function": "publish",
|
||||
}).Debugf("PackageFilePath => %s\n", packageFilePath)
|
||||
fmt.Printf("PackageFilePath => %s\n", packageFilePath)
|
||||
|
||||
// Try to get the index.json from the store
|
||||
fileToServe, found, err := stg.GetIndexJsonFromStore(packageName, cfg.RepoDir, lg)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var jsonFile serviceidos.IndexJson
|
||||
if !found {
|
||||
// new package
|
||||
jsonFile = cr.Request
|
||||
jsonFile.DistTags["latest"] = version
|
||||
curTime := time.Now().Format(time.RFC3339)
|
||||
jsonFile.TimesPackage = map[string]string{
|
||||
version: curTime,
|
||||
"created": curTime,
|
||||
"modified": curTime,
|
||||
"unpublished": "",
|
||||
}
|
||||
} else {
|
||||
// old package
|
||||
err = stg.ReadIndexJson(fileToServe, &jsonFile, lg)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Checking that you are not publishing over a pervious published version
|
||||
if jsonFile.Versions[version].Version == version {
|
||||
lg.WithFields(logrus.Fields{
|
||||
"function": "publish",
|
||||
}).Debugf("Version %s of package %s already exists!!\n", version, packageName)
|
||||
http.Error(w, fmt.Sprintf("Version %s of package %s already exists!!\n", version, packageName), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Rewrite attachments
|
||||
jsonFile.DistTags[tag] = version
|
||||
nAttachments := make(map[string]serviceidos.IndexJsonAttachments)
|
||||
nAttachments[fmt.Sprintf("%s-%s.tgz", packageName, version)] = cr.Request.Attachments[fmt.Sprintf("%s-%s.tgz", packageName, version)]
|
||||
jsonFile.Attachments = nAttachments
|
||||
|
||||
// Merge in the new version data
|
||||
jsonFile.Versions[version] = versionData
|
||||
|
||||
// Update the time field
|
||||
timesPackages := jsonFile.TimesPackage
|
||||
curTime := time.Now().Format(time.RFC3339)
|
||||
timesPackages["modified"] = curTime
|
||||
timesPackages[version] = curTime
|
||||
}
|
||||
|
||||
lg.WithFields(logrus.Fields{
|
||||
"function": "publish",
|
||||
}).Debugln("FiletoServe ==> ", fileToServe)
|
||||
|
||||
// Write index.json
|
||||
err = stg.WriteIndexJson(fileToServe, &jsonFile, lg)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
lg.WithFields(logrus.Fields{
|
||||
"function": "publish",
|
||||
}).Debugln("Package path => ", packageFilePath)
|
||||
// Write bundled package
|
||||
packageData := jsonFile.Attachments[fmt.Sprintf("%s-%s.tgz", packageName, version)].Data
|
||||
err = stg.WritePackageToStore(packageFilePath, packageData, lg)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := serviceidos.PublishPutResponse{
|
||||
Ok: true,
|
||||
Name: packageName,
|
||||
}
|
||||
jsonString, _ := json.Marshal(response)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(jsonString)))
|
||||
w.Write(jsonString)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,715 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"gosimplenpm/internal/config"
|
||||
"gosimplenpm/internal/serviceidos"
|
||||
"gosimplenpm/internal/storage"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUnitPublish(t *testing.T) {
|
||||
t.Run("return `Internal Server` error if index.json cannot be retrieved", func(t *testing.T) {
|
||||
jsonBody := []byte(
|
||||
`{
|
||||
"_id": "@ookusanya/simplepackone",
|
||||
"name": "@ookusanya/simplepackone",
|
||||
"description": "This is a very rough implementation of a private npm registry.",
|
||||
"dist-tags": {
|
||||
"latest": "1.2.0"
|
||||
},
|
||||
"versions": {
|
||||
"1.2.0": {
|
||||
"name": "@ookusanya/simplepackone",
|
||||
"version": "1.2.0",
|
||||
"description": "This is a very rough implementation of a private npm registry.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"license": "MIT",
|
||||
"files": null,
|
||||
"readme": "# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...",
|
||||
"_id": "@ookusanya/simplepackone@1.2.0",
|
||||
"_nodeVersion": "16.20.0",
|
||||
"_npmVersion": "8.19.4",
|
||||
"dist": {
|
||||
"integrity": "sha512-coOiU+ywV/do/+HwK91mfuei9491yfQUedLWjspAjDa58RniJvQNTF/cXDp/sooVdpjgEbCqKVyLOju6C1i3pw==",
|
||||
"shasum": "1adafdbe8878372c9f4b554bf30d5bfb1b7896c5",
|
||||
"tarball": "http://localhost:4000/@ookusanya%2Fsimplepackone/-/@ookusanya%2Fsimplepackone-1.2.0.tgz"
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint": "^7.x",
|
||||
"mocha": "^10.x",
|
||||
"uuid": "^9.x"
|
||||
}
|
||||
}
|
||||
},
|
||||
"access": "",
|
||||
"_attachments": {
|
||||
"@ookusanya/simplepackone-1.2.0.tgz": {
|
||||
"content_type": "application/octet-stream",
|
||||
"data": "H4sIAAAAAAAC/+1XbWvjOBDuZ/+KwQtLCyZ10pfl+uncRGnMOnawne0VlgXVVmJtHctIcl849r/fSEmb9nof73LcXoaA0Lw9M6PMSG5pcUeX7Jg3JXvsfVcH/wD5vn9+egp/xbc0OIGDk7Nzv98/P/dRz+/7nwY+rgc7oE5pKjGUvyFJJHhZ/yNUiEaJmvVqsTx0J0wy9+hgT/8fajf9v1lxAohmx/1/ev6u//tnZ/19/++CfncA3IaumHsB7q9C3HWKNk/0WPFVWzPzrxANcz2jdc+k4qIxiv3eoOevuSVTheSt3kjWzBXldvd8q6y5a0WFAoOKDM2UNmqsqAR8dYmUQl5AI8AIQLWs4AvOyq8ufPwI7JFr6Lto+cN6o52uhNxi1rxgjbJ5TMP8ObiWYQhNwdkrWKZq3ljgb596j1bTxCyKilpm399yu46X7oX77RdkGWjnh/NT9n9KgtGU9Fbl7u//waD/7v4/Oxns+38X9AGuxLrXm3blOHnFFeCPAnb7E0jRLSuw4hVrNDVdDmKB4lbye6oZoBVItuRKy6ee43yAMaO6k0yhr2SU9HqWSZqKNoX18VZwLeQdRGj9wt1fyf9K/0fhkMQZ6elHveP+H/hng3f3f9/f9/9OyBmK9knyZaXhsDiCgfkYS+rugZZ0xWtRU0g2TwLHmTG54sq8AcyIqPBj4fYJlpI2mpUeLCRjZjbgLSqXzAMtAM2gxVeDGRq3Gt8EvFk6FAqENJraDBslFvqBSobKJVClRMFxrpRQiqLbzpwFr5mCQ10xx802Fu6RBSkZrYE36I3BswgeOD4OOo2jCQcTL4wPD5WKuitNDM/imq/4BsGY2zoo47RTmIGJ04OVKPnCrMym1Xa3NVeV55Rm5vHbTiNTGaZ9fngmj2MhQbG6Nh7w5bHOdRud1UEUpzUF1ZsSWdyHSqzeZoIlWnSyQUhW2nQFlswifmeFNl6M+kLUtXjA1BCyKbnJSF2YcY51vRX3zOayPuZGaAx1HYI5gHZ7qhuRqijGfsucdcEQF8tLX6UjDTz2TaM51r4V0uL9OU0c8PmEQJaM8+sgJRBmMEuTL+GIjMANMty7HlyH+SSZ54AaaRDnN5CMIYhv4HMYjzyH/DZLSZZBkkI4nUUhGXkQxsNoPgrjK7hEuzjJIQrxwYdO8wQM4MZVSNBu7ExJOpzgNrgMozC/8WAc5rHxOUanAcyCNA+H8yhIYTZPZ0lGEH7kxEkcxuMUUciUxHkPUREKyBfcQDYJoshCBXOMPrXxDZPZTRpeTXKYJNGIIPOSOFEYXEZkDYVJDaMgnHowCqbBFbFWCXpJwahtorueEMtCvAB/wzxMYgdrMkziPMWth1mm+YvpdZgRD4I0zExBxmmC7k050SKxTtAuJmsvptTw5kRQxeznGdnGMiJBhL4yY/xaeX8x72lPe9rTT0V/APlm4rcAGAAA",
|
||||
"length": 1089
|
||||
}
|
||||
}
|
||||
}`,
|
||||
)
|
||||
req := httptest.NewRequest(http.MethodPut, "/{name}", bytes.NewReader(jsonBody))
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return "", false, fmt.Errorf("Filesystem error")
|
||||
}
|
||||
|
||||
vars := map[string]string{
|
||||
"name": "@ookusanya%2Fsimplepackone",
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
Publish(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Filesystem error\n")
|
||||
})
|
||||
|
||||
t.Run("return `Internal Server` error if index.json cannot be decoded", func(t *testing.T) {
|
||||
jsonBody := []byte(
|
||||
`{
|
||||
"_id": "@ookusanya/simplepackone",
|
||||
"name": "@ookusanya/simplepackone",
|
||||
"description": "This is a very rough implementation of a private npm registry.",
|
||||
"dist-tags": {
|
||||
"latest": "1.2.0"
|
||||
},
|
||||
"versions": {
|
||||
"1.2.0": {
|
||||
"name": "@ookusanya/simplepackone",
|
||||
"version": "1.2.0",
|
||||
"description": "This is a very rough implementation of a private npm registry.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"license": "MIT",
|
||||
"files": null,
|
||||
"readme": "# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...",
|
||||
"_id": "@ookusanya/simplepackone@1.2.0",
|
||||
"_nodeVersion": "16.20.0",
|
||||
"_npmVersion": "8.19.4",
|
||||
"dist": {
|
||||
"integrity": "sha512-coOiU+ywV/do/+HwK91mfuei9491yfQUedLWjspAjDa58RniJvQNTF/cXDp/sooVdpjgEbCqKVyLOju6C1i3pw==",
|
||||
"shasum": "1adafdbe8878372c9f4b554bf30d5bfb1b7896c5",
|
||||
"tarball": "http://localhost:4000/@ookusanya%2Fsimplepackone/-/@ookusanya%2Fsimplepackone-1.2.0.tgz"
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint": "^7.x",
|
||||
"mocha": "^10.x",
|
||||
"uuid": "^9.x"
|
||||
}
|
||||
}
|
||||
},
|
||||
"access": "",
|
||||
"_attachments": {
|
||||
"@ookusanya/simplepackone-1.2.0.tgz": {
|
||||
"content_type": "application/octet-stream",
|
||||
"data": "H4sIAAAAAAAC/+1XbWvjOBDuZ/+KwQtLCyZ10pfl+uncRGnMOnawne0VlgXVVmJtHctIcl849r/fSEmb9nof73LcXoaA0Lw9M6PMSG5pcUeX7Jg3JXvsfVcH/wD5vn9+egp/xbc0OIGDk7Nzv98/P/dRz+/7nwY+rgc7oE5pKjGUvyFJJHhZ/yNUiEaJmvVqsTx0J0wy9+hgT/8fajf9v1lxAohmx/1/ev6u//tnZ/19/++CfncA3IaumHsB7q9C3HWKNk/0WPFVWzPzrxANcz2jdc+k4qIxiv3eoOevuSVTheSt3kjWzBXldvd8q6y5a0WFAoOKDM2UNmqsqAR8dYmUQl5AI8AIQLWs4AvOyq8ufPwI7JFr6Lto+cN6o52uhNxi1rxgjbJ5TMP8ObiWYQhNwdkrWKZq3ljgb596j1bTxCyKilpm399yu46X7oX77RdkGWjnh/NT9n9KgtGU9Fbl7u//waD/7v4/Oxns+38X9AGuxLrXm3blOHnFFeCPAnb7E0jRLSuw4hVrNDVdDmKB4lbye6oZoBVItuRKy6ee43yAMaO6k0yhr2SU9HqWSZqKNoX18VZwLeQdRGj9wt1fyf9K/0fhkMQZ6elHveP+H/hng3f3f9/f9/9OyBmK9knyZaXhsDiCgfkYS+rugZZ0xWtRU0g2TwLHmTG54sq8AcyIqPBj4fYJlpI2mpUeLCRjZjbgLSqXzAMtAM2gxVeDGRq3Gt8EvFk6FAqENJraDBslFvqBSobKJVClRMFxrpRQiqLbzpwFr5mCQ10xx802Fu6RBSkZrYE36I3BswgeOD4OOo2jCQcTL4wPD5WKuitNDM/imq/4BsGY2zoo47RTmIGJ04OVKPnCrMym1Xa3NVeV55Rm5vHbTiNTGaZ9fngmj2MhQbG6Nh7w5bHOdRud1UEUpzUF1ZsSWdyHSqzeZoIlWnSyQUhW2nQFlswifmeFNl6M+kLUtXjA1BCyKbnJSF2YcY51vRX3zOayPuZGaAx1HYI5gHZ7qhuRqijGfsucdcEQF8tLX6UjDTz2TaM51r4V0uL9OU0c8PmEQJaM8+sgJRBmMEuTL+GIjMANMty7HlyH+SSZ54AaaRDnN5CMIYhv4HMYjzyH/DZLSZZBkkI4nUUhGXkQxsNoPgrjK7hEuzjJIQrxwYdO8wQM4MZVSNBu7ExJOpzgNrgMozC/8WAc5rHxOUanAcyCNA+H8yhIYTZPZ0lGEH7kxEkcxuMUUciUxHkPUREKyBfcQDYJoshCBXOMPrXxDZPZTRpeTXKYJNGIIPOSOFEYXEZkDYVJDaMgnHowCqbBFbFWCXpJwahtorueEMtCvAB/wzxMYgdrMkziPMWth1mm+YvpdZgRD4I0zExBxmmC7k050SKxTtAuJmsvptTw5kRQxeznGdnGMiJBhL4yY/xaeX8x72lPe9rTT0V/APlm4rcAGAAA",
|
||||
"length": 1089
|
||||
}
|
||||
}
|
||||
}`,
|
||||
)
|
||||
req := httptest.NewRequest(http.MethodPut, "/{name}", bytes.NewReader(jsonBody))
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return string(jsonBody), true, nil
|
||||
}
|
||||
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
return fmt.Errorf("Filesystem error")
|
||||
}
|
||||
|
||||
vars := map[string]string{
|
||||
"name": "@ookusanya%2Fsimplepackone",
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
Publish(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Filesystem error\n")
|
||||
})
|
||||
|
||||
t.Run("return `Bad Request` error if version to publish already exists", func(t *testing.T) {
|
||||
jsonBody := []byte(
|
||||
`{
|
||||
"_id": "@ookusanya/simplepackone",
|
||||
"name": "@ookusanya/simplepackone",
|
||||
"description": "This is a very rough implementation of a private npm registry.",
|
||||
"dist-tags": {
|
||||
"latest": "1.2.0"
|
||||
},
|
||||
"versions": {
|
||||
"1.2.0": {
|
||||
"name": "@ookusanya/simplepackone",
|
||||
"version": "1.2.0",
|
||||
"description": "This is a very rough implementation of a private npm registry.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"license": "MIT",
|
||||
"files": null,
|
||||
"readme": "# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...",
|
||||
"_id": "@ookusanya/simplepackone@1.2.0",
|
||||
"_nodeVersion": "16.20.0",
|
||||
"_npmVersion": "8.19.4",
|
||||
"dist": {
|
||||
"integrity": "sha512-coOiU+ywV/do/+HwK91mfuei9491yfQUedLWjspAjDa58RniJvQNTF/cXDp/sooVdpjgEbCqKVyLOju6C1i3pw==",
|
||||
"shasum": "1adafdbe8878372c9f4b554bf30d5bfb1b7896c5",
|
||||
"tarball": "http://localhost:4000/@ookusanya%2Fsimplepackone/-/@ookusanya%2Fsimplepackone-1.2.0.tgz"
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint": "^7.x",
|
||||
"mocha": "^10.x",
|
||||
"uuid": "^9.x"
|
||||
}
|
||||
}
|
||||
},
|
||||
"access": "",
|
||||
"_attachments": {
|
||||
"@ookusanya/simplepackone-1.2.0.tgz": {
|
||||
"content_type": "application/octet-stream",
|
||||
"data": "H4sIAAAAAAAC/+1XbWvjOBDuZ/+KwQtLCyZ10pfl+uncRGnMOnawne0VlgXVVmJtHctIcl849r/fSEmb9nof73LcXoaA0Lw9M6PMSG5pcUeX7Jg3JXvsfVcH/wD5vn9+egp/xbc0OIGDk7Nzv98/P/dRz+/7nwY+rgc7oE5pKjGUvyFJJHhZ/yNUiEaJmvVqsTx0J0wy9+hgT/8fajf9v1lxAohmx/1/ev6u//tnZ/19/++CfncA3IaumHsB7q9C3HWKNk/0WPFVWzPzrxANcz2jdc+k4qIxiv3eoOevuSVTheSt3kjWzBXldvd8q6y5a0WFAoOKDM2UNmqsqAR8dYmUQl5AI8AIQLWs4AvOyq8ufPwI7JFr6Lto+cN6o52uhNxi1rxgjbJ5TMP8ObiWYQhNwdkrWKZq3ljgb596j1bTxCyKilpm399yu46X7oX77RdkGWjnh/NT9n9KgtGU9Fbl7u//waD/7v4/Oxns+38X9AGuxLrXm3blOHnFFeCPAnb7E0jRLSuw4hVrNDVdDmKB4lbye6oZoBVItuRKy6ee43yAMaO6k0yhr2SU9HqWSZqKNoX18VZwLeQdRGj9wt1fyf9K/0fhkMQZ6elHveP+H/hng3f3f9/f9/9OyBmK9knyZaXhsDiCgfkYS+rugZZ0xWtRU0g2TwLHmTG54sq8AcyIqPBj4fYJlpI2mpUeLCRjZjbgLSqXzAMtAM2gxVeDGRq3Gt8EvFk6FAqENJraDBslFvqBSobKJVClRMFxrpRQiqLbzpwFr5mCQ10xx802Fu6RBSkZrYE36I3BswgeOD4OOo2jCQcTL4wPD5WKuitNDM/imq/4BsGY2zoo47RTmIGJ04OVKPnCrMym1Xa3NVeV55Rm5vHbTiNTGaZ9fngmj2MhQbG6Nh7w5bHOdRud1UEUpzUF1ZsSWdyHSqzeZoIlWnSyQUhW2nQFlswifmeFNl6M+kLUtXjA1BCyKbnJSF2YcY51vRX3zOayPuZGaAx1HYI5gHZ7qhuRqijGfsucdcEQF8tLX6UjDTz2TaM51r4V0uL9OU0c8PmEQJaM8+sgJRBmMEuTL+GIjMANMty7HlyH+SSZ54AaaRDnN5CMIYhv4HMYjzyH/DZLSZZBkkI4nUUhGXkQxsNoPgrjK7hEuzjJIQrxwYdO8wQM4MZVSNBu7ExJOpzgNrgMozC/8WAc5rHxOUanAcyCNA+H8yhIYTZPZ0lGEH7kxEkcxuMUUciUxHkPUREKyBfcQDYJoshCBXOMPrXxDZPZTRpeTXKYJNGIIPOSOFEYXEZkDYVJDaMgnHowCqbBFbFWCXpJwahtorueEMtCvAB/wzxMYgdrMkziPMWth1mm+YvpdZgRD4I0zExBxmmC7k050SKxTtAuJmsvptTw5kRQxeznGdnGMiJBhL4yY/xaeX8x72lPe9rTT0V/APlm4rcAGAAA",
|
||||
"length": 1089
|
||||
}
|
||||
}
|
||||
}`,
|
||||
)
|
||||
req := httptest.NewRequest(http.MethodPut, "/{name}", bytes.NewReader(jsonBody))
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return string(jsonBody), true, nil
|
||||
}
|
||||
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
temp := serviceidos.IndexJson{
|
||||
Versions: map[string]serviceidos.IndexJsonVersions{
|
||||
"1.2.0": {
|
||||
Version: "1.2.0",
|
||||
Name: "@ookusanya/simplepackone",
|
||||
},
|
||||
},
|
||||
}
|
||||
*res = temp
|
||||
return nil
|
||||
}
|
||||
mfs.WriteIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
return fmt.Errorf("Filesystem error")
|
||||
}
|
||||
|
||||
vars := map[string]string{
|
||||
"name": "@ookusanya%2Fsimplepackone",
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
Publish(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusBadRequest)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Version 1.2.0 of package @ookusanya/simplepackone already exists!!\n\n")
|
||||
})
|
||||
|
||||
t.Run("return `Internal Server` error if writing index.json fails", func(t *testing.T) {
|
||||
jsonBody := []byte(
|
||||
`{
|
||||
"_id": "@ookusanya/simplepackone",
|
||||
"name": "@ookusanya/simplepackone",
|
||||
"description": "This is a very rough implementation of a private npm registry.",
|
||||
"dist-tags": {
|
||||
"latest": "1.3.0"
|
||||
},
|
||||
"versions": {
|
||||
"1.3.0": {
|
||||
"name": "@ookusanya/simplepackone",
|
||||
"version": "1.3.0",
|
||||
"description": "This is a very rough implementation of a private npm registry.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"license": "MIT",
|
||||
"files": null,
|
||||
"readme": "# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...",
|
||||
"_id": "@ookusanya/simplepackone@1.2.0",
|
||||
"_nodeVersion": "16.20.0",
|
||||
"_npmVersion": "8.19.4",
|
||||
"dist": {
|
||||
"integrity": "sha512-coOiU+ywV/do/+HwK91mfuei9491yfQUedLWjspAjDa58RniJvQNTF/cXDp/sooVdpjgEbCqKVyLOju6C1i3pw==",
|
||||
"shasum": "1adafdbe8878372c9f4b554bf30d5bfb1b7896c5",
|
||||
"tarball": "http://localhost:4000/@ookusanya%2Fsimplepackone/-/@ookusanya%2Fsimplepackone-1.2.0.tgz"
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint": "^7.x",
|
||||
"mocha": "^10.x",
|
||||
"uuid": "^9.x"
|
||||
}
|
||||
}
|
||||
},
|
||||
"access": "",
|
||||
"_attachments": {
|
||||
"@ookusanya/simplepackone-1.3.0.tgz": {
|
||||
"content_type": "application/octet-stream",
|
||||
"data": "H4sIAAAAAAAC/+1XbWvjOBDuZ/+KwQtLCyZ10pfl+uncRGnMOnawne0VlgXVVmJtHctIcl849r/fSEmb9nof73LcXoaA0Lw9M6PMSG5pcUeX7Jg3JXvsfVcH/wD5vn9+egp/xbc0OIGDk7Nzv98/P/dRz+/7nwY+rgc7oE5pKjGUvyFJJHhZ/yNUiEaJmvVqsTx0J0wy9+hgT/8fajf9v1lxAohmx/1/ev6u//tnZ/19/++CfncA3IaumHsB7q9C3HWKNk/0WPFVWzPzrxANcz2jdc+k4qIxiv3eoOevuSVTheSt3kjWzBXldvd8q6y5a0WFAoOKDM2UNmqsqAR8dYmUQl5AI8AIQLWs4AvOyq8ufPwI7JFr6Lto+cN6o52uhNxi1rxgjbJ5TMP8ObiWYQhNwdkrWKZq3ljgb596j1bTxCyKilpm399yu46X7oX77RdkGWjnh/NT9n9KgtGU9Fbl7u//waD/7v4/Oxns+38X9AGuxLrXm3blOHnFFeCPAnb7E0jRLSuw4hVrNDVdDmKB4lbye6oZoBVItuRKy6ee43yAMaO6k0yhr2SU9HqWSZqKNoX18VZwLeQdRGj9wt1fyf9K/0fhkMQZ6elHveP+H/hng3f3f9/f9/9OyBmK9knyZaXhsDiCgfkYS+rugZZ0xWtRU0g2TwLHmTG54sq8AcyIqPBj4fYJlpI2mpUeLCRjZjbgLSqXzAMtAM2gxVeDGRq3Gt8EvFk6FAqENJraDBslFvqBSobKJVClRMFxrpRQiqLbzpwFr5mCQ10xx802Fu6RBSkZrYE36I3BswgeOD4OOo2jCQcTL4wPD5WKuitNDM/imq/4BsGY2zoo47RTmIGJ04OVKPnCrMym1Xa3NVeV55Rm5vHbTiNTGaZ9fngmj2MhQbG6Nh7w5bHOdRud1UEUpzUF1ZsSWdyHSqzeZoIlWnSyQUhW2nQFlswifmeFNl6M+kLUtXjA1BCyKbnJSF2YcY51vRX3zOayPuZGaAx1HYI5gHZ7qhuRqijGfsucdcEQF8tLX6UjDTz2TaM51r4V0uL9OU0c8PmEQJaM8+sgJRBmMEuTL+GIjMANMty7HlyH+SSZ54AaaRDnN5CMIYhv4HMYjzyH/DZLSZZBkkI4nUUhGXkQxsNoPgrjK7hEuzjJIQrxwYdO8wQM4MZVSNBu7ExJOpzgNrgMozC/8WAc5rHxOUanAcyCNA+H8yhIYTZPZ0lGEH7kxEkcxuMUUciUxHkPUREKyBfcQDYJoshCBXOMPrXxDZPZTRpeTXKYJNGIIPOSOFEYXEZkDYVJDaMgnHowCqbBFbFWCXpJwahtorueEMtCvAB/wzxMYgdrMkziPMWth1mm+YvpdZgRD4I0zExBxmmC7k050SKxTtAuJmsvptTw5kRQxeznGdnGMiJBhL4yY/xaeX8x72lPe9rTT0V/APlm4rcAGAAA",
|
||||
"length": 1089
|
||||
}
|
||||
}
|
||||
}`,
|
||||
)
|
||||
req := httptest.NewRequest(http.MethodPut, "/{name}", bytes.NewReader(jsonBody))
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return string(jsonBody), true, nil
|
||||
}
|
||||
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
temp := serviceidos.IndexJson{
|
||||
Versions: map[string]serviceidos.IndexJsonVersions{
|
||||
"1.2.0": {
|
||||
Version: "1.2.0",
|
||||
Name: "@ookusanya/simplepackone",
|
||||
},
|
||||
},
|
||||
DistTags: map[string]string{
|
||||
"latest": "1.2.0",
|
||||
},
|
||||
TimesPackage: map[string]string{
|
||||
"modified": "",
|
||||
"created": "",
|
||||
},
|
||||
}
|
||||
*res = temp
|
||||
return nil
|
||||
}
|
||||
mfs.WriteIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
return fmt.Errorf("Filesystem error")
|
||||
}
|
||||
|
||||
vars := map[string]string{
|
||||
"name": "@ookusanya%2Fsimplepackone",
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
Publish(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Filesystem error\n")
|
||||
})
|
||||
|
||||
t.Run("return `Internal Server` error if writing tar package fails", func(t *testing.T) {
|
||||
jsonBody := []byte(
|
||||
`{
|
||||
"_id": "@ookusanya/simplepackone",
|
||||
"name": "@ookusanya/simplepackone",
|
||||
"description": "This is a very rough implementation of a private npm registry.",
|
||||
"dist-tags": {
|
||||
"latest": "1.3.0"
|
||||
},
|
||||
"versions": {
|
||||
"1.3.0": {
|
||||
"name": "@ookusanya/simplepackone",
|
||||
"version": "1.3.0",
|
||||
"description": "This is a very rough implementation of a private npm registry.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"license": "MIT",
|
||||
"files": null,
|
||||
"readme": "# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...",
|
||||
"_id": "@ookusanya/simplepackone@1.2.0",
|
||||
"_nodeVersion": "16.20.0",
|
||||
"_npmVersion": "8.19.4",
|
||||
"dist": {
|
||||
"integrity": "sha512-coOiU+ywV/do/+HwK91mfuei9491yfQUedLWjspAjDa58RniJvQNTF/cXDp/sooVdpjgEbCqKVyLOju6C1i3pw==",
|
||||
"shasum": "1adafdbe8878372c9f4b554bf30d5bfb1b7896c5",
|
||||
"tarball": "http://localhost:4000/@ookusanya%2Fsimplepackone/-/@ookusanya%2Fsimplepackone-1.2.0.tgz"
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint": "^7.x",
|
||||
"mocha": "^10.x",
|
||||
"uuid": "^9.x"
|
||||
}
|
||||
}
|
||||
},
|
||||
"access": "",
|
||||
"_attachments": {
|
||||
"@ookusanya/simplepackone-1.3.0.tgz": {
|
||||
"content_type": "application/octet-stream",
|
||||
"data": "H4sIAAAAAAAC/+1XbWvjOBDuZ/+KwQtLCyZ10pfl+uncRGnMOnawne0VlgXVVmJtHctIcl849r/fSEmb9nof73LcXoaA0Lw9M6PMSG5pcUeX7Jg3JXvsfVcH/wD5vn9+egp/xbc0OIGDk7Nzv98/P/dRz+/7nwY+rgc7oE5pKjGUvyFJJHhZ/yNUiEaJmvVqsTx0J0wy9+hgT/8fajf9v1lxAohmx/1/ev6u//tnZ/19/++CfncA3IaumHsB7q9C3HWKNk/0WPFVWzPzrxANcz2jdc+k4qIxiv3eoOevuSVTheSt3kjWzBXldvd8q6y5a0WFAoOKDM2UNmqsqAR8dYmUQl5AI8AIQLWs4AvOyq8ufPwI7JFr6Lto+cN6o52uhNxi1rxgjbJ5TMP8ObiWYQhNwdkrWKZq3ljgb596j1bTxCyKilpm399yu46X7oX77RdkGWjnh/NT9n9KgtGU9Fbl7u//waD/7v4/Oxns+38X9AGuxLrXm3blOHnFFeCPAnb7E0jRLSuw4hVrNDVdDmKB4lbye6oZoBVItuRKy6ee43yAMaO6k0yhr2SU9HqWSZqKNoX18VZwLeQdRGj9wt1fyf9K/0fhkMQZ6elHveP+H/hng3f3f9/f9/9OyBmK9knyZaXhsDiCgfkYS+rugZZ0xWtRU0g2TwLHmTG54sq8AcyIqPBj4fYJlpI2mpUeLCRjZjbgLSqXzAMtAM2gxVeDGRq3Gt8EvFk6FAqENJraDBslFvqBSobKJVClRMFxrpRQiqLbzpwFr5mCQ10xx802Fu6RBSkZrYE36I3BswgeOD4OOo2jCQcTL4wPD5WKuitNDM/imq/4BsGY2zoo47RTmIGJ04OVKPnCrMym1Xa3NVeV55Rm5vHbTiNTGaZ9fngmj2MhQbG6Nh7w5bHOdRud1UEUpzUF1ZsSWdyHSqzeZoIlWnSyQUhW2nQFlswifmeFNl6M+kLUtXjA1BCyKbnJSF2YcY51vRX3zOayPuZGaAx1HYI5gHZ7qhuRqijGfsucdcEQF8tLX6UjDTz2TaM51r4V0uL9OU0c8PmEQJaM8+sgJRBmMEuTL+GIjMANMty7HlyH+SSZ54AaaRDnN5CMIYhv4HMYjzyH/DZLSZZBkkI4nUUhGXkQxsNoPgrjK7hEuzjJIQrxwYdO8wQM4MZVSNBu7ExJOpzgNrgMozC/8WAc5rHxOUanAcyCNA+H8yhIYTZPZ0lGEH7kxEkcxuMUUciUxHkPUREKyBfcQDYJoshCBXOMPrXxDZPZTRpeTXKYJNGIIPOSOFEYXEZkDYVJDaMgnHowCqbBFbFWCXpJwahtorueEMtCvAB/wzxMYgdrMkziPMWth1mm+YvpdZgRD4I0zExBxmmC7k050SKxTtAuJmsvptTw5kRQxeznGdnGMiJBhL4yY/xaeX8x72lPe9rTT0V/APlm4rcAGAAA",
|
||||
"length": 1089
|
||||
}
|
||||
}
|
||||
}`,
|
||||
)
|
||||
req := httptest.NewRequest(http.MethodPut, "/{name}", bytes.NewReader(jsonBody))
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return string(jsonBody), true, nil
|
||||
}
|
||||
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
temp := serviceidos.IndexJson{
|
||||
Versions: map[string]serviceidos.IndexJsonVersions{
|
||||
"1.2.0": {
|
||||
Version: "1.2.0",
|
||||
Name: "@ookusanya/simplepackone",
|
||||
},
|
||||
},
|
||||
DistTags: map[string]string{
|
||||
"latest": "1.2.0",
|
||||
},
|
||||
TimesPackage: map[string]string{
|
||||
"modified": "",
|
||||
"created": "",
|
||||
},
|
||||
}
|
||||
*res = temp
|
||||
return nil
|
||||
}
|
||||
mfs.WriteIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
return nil
|
||||
}
|
||||
mfs.WritePackageToStoreFunc = func(fPath string, data string, lg *logrus.Logger) error {
|
||||
return fmt.Errorf("Filesystem error")
|
||||
}
|
||||
|
||||
vars := map[string]string{
|
||||
"name": "@ookusanya%2Fsimplepackone",
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
Publish(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Filesystem error\n")
|
||||
})
|
||||
|
||||
t.Run("return 201 Created if publish is successful", func(t *testing.T) {
|
||||
jsonBody := []byte(
|
||||
`{
|
||||
"_id": "@ookusanya/simplepackone",
|
||||
"name": "@ookusanya/simplepackone",
|
||||
"description": "This is a very rough implementation of a private npm registry.",
|
||||
"dist-tags": {
|
||||
"latest": "1.3.0"
|
||||
},
|
||||
"versions": {
|
||||
"1.3.0": {
|
||||
"name": "@ookusanya/simplepackone",
|
||||
"version": "1.3.0",
|
||||
"description": "This is a very rough implementation of a private npm registry.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"license": "MIT",
|
||||
"files": null,
|
||||
"readme": "# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...",
|
||||
"_id": "@ookusanya/simplepackone@1.2.0",
|
||||
"_nodeVersion": "16.20.0",
|
||||
"_npmVersion": "8.19.4",
|
||||
"dist": {
|
||||
"integrity": "sha512-coOiU+ywV/do/+HwK91mfuei9491yfQUedLWjspAjDa58RniJvQNTF/cXDp/sooVdpjgEbCqKVyLOju6C1i3pw==",
|
||||
"shasum": "1adafdbe8878372c9f4b554bf30d5bfb1b7896c5",
|
||||
"tarball": "http://localhost:4000/@ookusanya%2Fsimplepackone/-/@ookusanya%2Fsimplepackone-1.2.0.tgz"
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint": "^7.x",
|
||||
"mocha": "^10.x",
|
||||
"uuid": "^9.x"
|
||||
}
|
||||
}
|
||||
},
|
||||
"access": "",
|
||||
"_attachments": {
|
||||
"@ookusanya/simplepackone-1.3.0.tgz": {
|
||||
"content_type": "application/octet-stream",
|
||||
"data": "H4sIAAAAAAAC/+1XbWvjOBDuZ/+KwQtLCyZ10pfl+uncRGnMOnawne0VlgXVVmJtHctIcl849r/fSEmb9nof73LcXoaA0Lw9M6PMSG5pcUeX7Jg3JXvsfVcH/wD5vn9+egp/xbc0OIGDk7Nzv98/P/dRz+/7nwY+rgc7oE5pKjGUvyFJJHhZ/yNUiEaJmvVqsTx0J0wy9+hgT/8fajf9v1lxAohmx/1/ev6u//tnZ/19/++CfncA3IaumHsB7q9C3HWKNk/0WPFVWzPzrxANcz2jdc+k4qIxiv3eoOevuSVTheSt3kjWzBXldvd8q6y5a0WFAoOKDM2UNmqsqAR8dYmUQl5AI8AIQLWs4AvOyq8ufPwI7JFr6Lto+cN6o52uhNxi1rxgjbJ5TMP8ObiWYQhNwdkrWKZq3ljgb596j1bTxCyKilpm399yu46X7oX77RdkGWjnh/NT9n9KgtGU9Fbl7u//waD/7v4/Oxns+38X9AGuxLrXm3blOHnFFeCPAnb7E0jRLSuw4hVrNDVdDmKB4lbye6oZoBVItuRKy6ee43yAMaO6k0yhr2SU9HqWSZqKNoX18VZwLeQdRGj9wt1fyf9K/0fhkMQZ6elHveP+H/hng3f3f9/f9/9OyBmK9knyZaXhsDiCgfkYS+rugZZ0xWtRU0g2TwLHmTG54sq8AcyIqPBj4fYJlpI2mpUeLCRjZjbgLSqXzAMtAM2gxVeDGRq3Gt8EvFk6FAqENJraDBslFvqBSobKJVClRMFxrpRQiqLbzpwFr5mCQ10xx802Fu6RBSkZrYE36I3BswgeOD4OOo2jCQcTL4wPD5WKuitNDM/imq/4BsGY2zoo47RTmIGJ04OVKPnCrMym1Xa3NVeV55Rm5vHbTiNTGaZ9fngmj2MhQbG6Nh7w5bHOdRud1UEUpzUF1ZsSWdyHSqzeZoIlWnSyQUhW2nQFlswifmeFNl6M+kLUtXjA1BCyKbnJSF2YcY51vRX3zOayPuZGaAx1HYI5gHZ7qhuRqijGfsucdcEQF8tLX6UjDTz2TaM51r4V0uL9OU0c8PmEQJaM8+sgJRBmMEuTL+GIjMANMty7HlyH+SSZ54AaaRDnN5CMIYhv4HMYjzyH/DZLSZZBkkI4nUUhGXkQxsNoPgrjK7hEuzjJIQrxwYdO8wQM4MZVSNBu7ExJOpzgNrgMozC/8WAc5rHxOUanAcyCNA+H8yhIYTZPZ0lGEH7kxEkcxuMUUciUxHkPUREKyBfcQDYJoshCBXOMPrXxDZPZTRpeTXKYJNGIIPOSOFEYXEZkDYVJDaMgnHowCqbBFbFWCXpJwahtorueEMtCvAB/wzxMYgdrMkziPMWth1mm+YvpdZgRD4I0zExBxmmC7k050SKxTtAuJmsvptTw5kRQxeznGdnGMiJBhL4yY/xaeX8x72lPe9rTT0V/APlm4rcAGAAA",
|
||||
"length": 1089
|
||||
}
|
||||
}
|
||||
}`,
|
||||
)
|
||||
req := httptest.NewRequest(http.MethodPut, "/{name}", bytes.NewReader(jsonBody))
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return string(jsonBody), false, nil
|
||||
}
|
||||
mfs.WriteIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
return nil
|
||||
}
|
||||
mfs.WritePackageToStoreFunc = func(fPath string, data string, lg *logrus.Logger) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
vars := map[string]string{
|
||||
"name": "@ookusanya%2Fsimplepackone",
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
Publish(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusCreated)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "{\"ok\":true,\"package_name\":\"@ookusanya/simplepackone\"}")
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationPublishNormal(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping publishPackage integration test")
|
||||
}
|
||||
|
||||
token := "0N89nr/hmKXoBzG]R{fKH%YE1X"
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
t.Logf("Temp Dir: %s", tmpDir)
|
||||
|
||||
indexJsonFp := "intestdata/publish/normal/index.json"
|
||||
|
||||
isEmpty := IsDirEmpty(t, tmpDir)
|
||||
assert.Equal(t, true, isEmpty)
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: tmpDir,
|
||||
Token: token,
|
||||
}
|
||||
|
||||
app := newTestApp(t, cfg)
|
||||
app.Routes()
|
||||
ts := newTestServer(t, app.Mux)
|
||||
defer ts.Close()
|
||||
|
||||
dataToSend := readTestFile(t, indexJsonFp)
|
||||
code, _, body := ts.put(t, fmt.Sprintf("/%s", url.PathEscape("@df/simplepackone")), token, string(dataToSend))
|
||||
|
||||
assert.Equal(t, code, http.StatusCreated)
|
||||
|
||||
isEmpty = IsDirEmpty(t, tmpDir)
|
||||
assert.Equal(t, false, isEmpty)
|
||||
|
||||
// t.Logf("Body ==> %s", string(body))
|
||||
|
||||
filePaths := listDir(t, tmpDir, false)
|
||||
assert.Equal(t, true, containsSub(filePaths, "index.json"))
|
||||
assert.Equal(t, true, containsSub(filePaths, "simplepackone-1.0.0"))
|
||||
|
||||
assert.NotEmpty(t, string(body))
|
||||
}
|
||||
|
||||
func TestIntegrationPublishOverwrite(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping publishPackage integration test")
|
||||
}
|
||||
|
||||
token := "0N89nr/hmKXoBzG]R{fKH%YE1X"
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
t.Logf("Temp Dir: %s", tmpDir)
|
||||
|
||||
// Copy initial package
|
||||
indexJsonFp := "intestdata/publish/normal/index.json"
|
||||
tgzFp := "intestdata/get/simplepackone-1.0.0.tgz"
|
||||
mkDir(t, fmt.Sprintf("%s/@df/simplepackone", tmpDir))
|
||||
cpFile(t, indexJsonFp, fmt.Sprintf("%s/@df/simplepackone/index.json", tmpDir))
|
||||
cpFile(t, tgzFp, fmt.Sprintf("%s/@df/simplepackone/simplepackone-1.0.0.tgz", tmpDir))
|
||||
|
||||
isEmpty := IsDirEmpty(t, tmpDir)
|
||||
assert.Equal(t, false, isEmpty)
|
||||
|
||||
indexJsonFp = "intestdata/publish/overwrite/index.json"
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: tmpDir,
|
||||
Token: token,
|
||||
}
|
||||
|
||||
app := newTestApp(t, cfg)
|
||||
app.Routes()
|
||||
ts := newTestServer(t, app.Mux)
|
||||
defer ts.Close()
|
||||
|
||||
dataToSend := readTestFile(t, indexJsonFp)
|
||||
code, _, body := ts.put(t, fmt.Sprintf("/%s", url.PathEscape("@df/simplepackone")), token, string(dataToSend))
|
||||
|
||||
assert.Equal(t, code, http.StatusCreated)
|
||||
|
||||
filePaths := listDir(t, tmpDir, false)
|
||||
assert.Equal(t, true, containsSub(filePaths, "index.json"))
|
||||
assert.Equal(t, true, containsSub(filePaths, "simplepackone-1.0.0"))
|
||||
assert.Equal(t, true, containsSub(filePaths, "simplepackone-1.9.0"))
|
||||
|
||||
assert.NotEmpty(t, string(body))
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gosimplenpm/internal/config"
|
||||
"gosimplenpm/internal/serviceidos"
|
||||
"gosimplenpm/internal/storage"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
func DistTagDelete(lg *logrus.Logger, cfg config.Config, stg storage.Storage) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
escapedName := mux.Vars(r)["name"]
|
||||
packageName, _ := url.PathUnescape(escapedName)
|
||||
lg.WithFields(logrus.Fields{
|
||||
"function": "dist-tags-delete",
|
||||
}).Debugf("Package name => %s\n", packageName)
|
||||
|
||||
escapedName = mux.Vars(r)["tag"]
|
||||
tag, _ := url.PathUnescape(escapedName)
|
||||
lg.WithFields(logrus.Fields{
|
||||
"function": "dist-tags-delete",
|
||||
}).Debugf("Tag => %s\n", tag)
|
||||
|
||||
if semver.IsValid(tag) {
|
||||
http.Error(w, fmt.Sprintf("Tag %s cannot be a semver version", tag), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if tag == "latest" {
|
||||
http.Error(w, "Cannot delete the latest tag", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
fileToServe, found, err := stg.GetIndexJsonFromStore(packageName, cfg.RepoDir, lg)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if !found {
|
||||
ret := fmt.Sprintf("Package not found: %s", packageName)
|
||||
http.Error(w, ret, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
var jsonFile serviceidos.IndexJson
|
||||
err = stg.ReadIndexJson(fileToServe, &jsonFile, lg)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
delete(jsonFile.DistTags, tag)
|
||||
|
||||
// Write index.json
|
||||
err = stg.WriteIndexJson(fileToServe, &jsonFile, lg)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
distTagsString, _ := json.Marshal(jsonFile.DistTags)
|
||||
|
||||
response := serviceidos.TagPutResponse{
|
||||
Ok: true,
|
||||
ID: escapedName,
|
||||
DistTags: string(distTagsString),
|
||||
}
|
||||
|
||||
jsonString, _ := json.Marshal(response)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(jsonString)))
|
||||
w.Write(jsonString)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,429 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gosimplenpm/internal/config"
|
||||
"gosimplenpm/internal/serviceidos"
|
||||
"gosimplenpm/internal/storage"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUnitTagDelete(t *testing.T) {
|
||||
t.Run("return `Bad request` error if tag is semver", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodDelete, "/-/package/{name}/dist-tags/{tag}", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
|
||||
tag := "v1.0.0"
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
"tag": tag,
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
DistTagDelete(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusBadRequest)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), fmt.Sprintf("Tag %s cannot be a semver version\n", tag))
|
||||
})
|
||||
|
||||
t.Run("return `Bad request` error if tag is latest", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodDelete, "/-/package/{name}/dist-tags/{tag}", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
|
||||
tag := "latest"
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
"tag": tag,
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
DistTagDelete(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusBadRequest)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Cannot delete the latest tag\n")
|
||||
})
|
||||
|
||||
t.Run("return `Internal Server` error if index json cannot be retrieved", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodDelete, "/-/package/{name}/dist-tags/{tag}", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return "", false, fmt.Errorf("Filesystem error")
|
||||
}
|
||||
|
||||
tag := "current"
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
"tag": tag,
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
DistTagDelete(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Filesystem error\n")
|
||||
})
|
||||
|
||||
t.Run("return `Not found` error if package cannot be retrieved", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodDelete, "/-/package/{name}/dist-tags/{tag}", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
tag := "current"
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
"tag": tag,
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
DistTagDelete(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusNotFound)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Package not found: test-package\n")
|
||||
})
|
||||
|
||||
t.Run("return `Internal Server` error if package.json cannot be parsed", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodDelete, "/-/package/{name}/dist-tags/{tag}", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return "", true, nil
|
||||
}
|
||||
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
return fmt.Errorf("Parsing failed")
|
||||
}
|
||||
|
||||
tag := "current"
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
"tag": tag,
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
DistTagDelete(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Parsing failed\n")
|
||||
})
|
||||
|
||||
t.Run("return `Internal Server` error if package.json cannot be written", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodDelete, "/-/package/{name}/dist-tags/{tag}", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return "", true, nil
|
||||
}
|
||||
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
temp := serviceidos.IndexJson{
|
||||
DistTags: map[string]string{
|
||||
"current": "1.2.0",
|
||||
"latest": "1.2.0",
|
||||
},
|
||||
}
|
||||
*res = temp
|
||||
return nil
|
||||
}
|
||||
mfs.WriteIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
return fmt.Errorf("File cannot be written")
|
||||
}
|
||||
|
||||
tag := "current"
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
"tag": tag,
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
DistTagDelete(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "File cannot be written\n")
|
||||
})
|
||||
|
||||
t.Run("return 200 OK if tag can be deleted", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodDelete, "/-/package/{name}/dist-tags/{tag}", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return "", true, nil
|
||||
}
|
||||
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
temp := serviceidos.IndexJson{
|
||||
DistTags: map[string]string{
|
||||
"current": "1.2.0",
|
||||
"latest": "1.2.0",
|
||||
},
|
||||
}
|
||||
*res = temp
|
||||
return nil
|
||||
}
|
||||
mfs.WriteIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
tag := "current"
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
"tag": tag,
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
DistTagDelete(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusOK)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "{\"ok\":true,\"id\":\"current\",\"dist-tags\":\"{\\\"latest\\\":\\\"1.2.0\\\"}\"}")
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationTagDelete(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping deleteTags integration test")
|
||||
}
|
||||
|
||||
token := "0N89nr/hmKXoBzG]R{fKH%YE1X"
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
mkDir(t, fmt.Sprintf("%s/@df/simplepackone", tmpDir))
|
||||
mkDir(t, fmt.Sprintf("%s/output", tmpDir))
|
||||
|
||||
indexJsonFp := "intestdata/tags/index.json"
|
||||
oldTgzFp := "intestdata/tags/simplepackone-1.0.0.tgz"
|
||||
tgzFp := "intestdata/tags/simplepackone-1.9.0.tgz"
|
||||
|
||||
cpFile(t, indexJsonFp, fmt.Sprintf("%s/@df/simplepackone/index.json", tmpDir))
|
||||
cpFile(t, oldTgzFp, fmt.Sprintf("%s/@df/simplepackone/simplepackone-1.0.0.tgz", tmpDir))
|
||||
cpFile(t, tgzFp, fmt.Sprintf("%s/@df/simplepackone/simplepackone-1.9.0.tgz", tmpDir))
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: tmpDir,
|
||||
Token: token,
|
||||
}
|
||||
|
||||
app := newTestApp(t, cfg)
|
||||
app.Routes()
|
||||
ts := newTestServer(t, app.Mux)
|
||||
defer ts.Close()
|
||||
|
||||
code, _, body := ts.delete(t, fmt.Sprintf("/-/package/%s/dist-tags/pre-alpha", url.PathEscape("@df/simplepackone")), token)
|
||||
|
||||
assert.Equal(t, code, http.StatusOK)
|
||||
assert.NotEmpty(t, body)
|
||||
|
||||
old := readTestFile(t, indexJsonFp)
|
||||
oldRet := make(map[string]interface{})
|
||||
json.Unmarshal(old, &oldRet)
|
||||
|
||||
assert.Contains(t, oldRet["dist-tags"], "pre-alpha")
|
||||
|
||||
modified := readTestFile(t, fmt.Sprintf("%s/@df/simplepackone/index.json", tmpDir))
|
||||
modifiedRet := make(map[string]interface{})
|
||||
json.Unmarshal(modified, &modifiedRet)
|
||||
|
||||
assert.NotContains(t, modifiedRet["dist-tags"], "pre-alpha")
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gosimplenpm/internal/config"
|
||||
"gosimplenpm/internal/serviceidos"
|
||||
"gosimplenpm/internal/storage"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func DistTagGet(lg *logrus.Logger, cfg config.Config, stg storage.Storage) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
escapedName := mux.Vars(r)["name"]
|
||||
packageName, _ := url.PathUnescape(escapedName)
|
||||
lg.WithFields(logrus.Fields{
|
||||
"function": "dist-tags-get",
|
||||
}).Debugf("Package name => %s\n", packageName)
|
||||
|
||||
fileToServe, found, err := stg.GetIndexJsonFromStore(packageName, cfg.RepoDir, lg)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if !found {
|
||||
ret := fmt.Sprintf("Package not found: %s", packageName)
|
||||
http.Error(w, ret, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
var jsonFile serviceidos.IndexJson
|
||||
err = stg.ReadIndexJson(fileToServe, &jsonFile, lg)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
jsonString, _ := json.Marshal(jsonFile.DistTags)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(jsonString)))
|
||||
w.Write(jsonString)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,255 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gosimplenpm/internal/config"
|
||||
"gosimplenpm/internal/serviceidos"
|
||||
"gosimplenpm/internal/storage"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUnitTagGet(t *testing.T) {
|
||||
t.Run("return `Internal Server` error if index json cannot be retrieved", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/-/package/{name}/dist-tags", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return "", false, fmt.Errorf("Filesystem error")
|
||||
}
|
||||
|
||||
tag := "1.2.4"
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
"tag": tag,
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
DistTagGet(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Filesystem error\n")
|
||||
})
|
||||
|
||||
t.Run("return `Not Found` error if package cannot be retrieved", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/-/package/{name}/dist-tags", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
DistTagGet(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusNotFound)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Package not found: test-package\n")
|
||||
})
|
||||
|
||||
t.Run("return `Internal Server` error if package.json cannot be parsed", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodDelete, "/-/package/{name}/dist-tags", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return "", true, nil
|
||||
}
|
||||
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
return fmt.Errorf("Parsing failed")
|
||||
}
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
DistTagGet(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Parsing failed\n")
|
||||
})
|
||||
|
||||
t.Run("return 200 OK", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodDelete, "/-/package/{name}/dist-tags", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return "", true, nil
|
||||
}
|
||||
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
temp := serviceidos.IndexJson{
|
||||
DistTags: map[string]string{
|
||||
"current": "1.2.0",
|
||||
"latest": "1.2.0",
|
||||
},
|
||||
}
|
||||
*res = temp
|
||||
return nil
|
||||
}
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
DistTagGet(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusOK)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "{\"current\":\"1.2.0\",\"latest\":\"1.2.0\"}")
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationTagGet(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping getTags integration test")
|
||||
}
|
||||
|
||||
token := "0N89nr/hmKXoBzG]R{fKH%YE1X"
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
mkDir(t, fmt.Sprintf("%s/@df/simplepackone", tmpDir))
|
||||
mkDir(t, fmt.Sprintf("%s/output", tmpDir))
|
||||
|
||||
indexJsonFp := "intestdata/tags/index.json"
|
||||
tgzFp := "intestdata/tags/simplepackone-1.9.0.tgz"
|
||||
|
||||
cpFile(t, indexJsonFp, fmt.Sprintf("%s/@df/simplepackone/index.json", tmpDir))
|
||||
cpFile(t, tgzFp, fmt.Sprintf("%s/@df/simplepackone/simplepackone-1.9.0.tgz", tmpDir))
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: tmpDir,
|
||||
Token: token,
|
||||
}
|
||||
|
||||
app := newTestApp(t, cfg)
|
||||
app.Routes()
|
||||
ts := newTestServer(t, app.Mux)
|
||||
defer ts.Close()
|
||||
|
||||
code, _, body := ts.get(t, fmt.Sprintf("/-/package/%s/dist-tags", url.PathEscape("@df/simplepackone")))
|
||||
|
||||
assert.Equal(t, code, http.StatusOK)
|
||||
ret := make(map[string]interface{})
|
||||
json.Unmarshal(body, &ret)
|
||||
assert.Contains(t, ret, "latest")
|
||||
assert.Equal(t, "1.9.0", ret["latest"])
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gosimplenpm/internal/config"
|
||||
"gosimplenpm/internal/serviceidos"
|
||||
"gosimplenpm/internal/storage"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
func DistTagPut(lg *logrus.Logger, cfg config.Config, stg storage.Storage) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
escapedName := mux.Vars(r)["name"]
|
||||
packageName, _ := url.PathUnescape(escapedName)
|
||||
lg.WithFields(logrus.Fields{
|
||||
"function": "dist-tags-put",
|
||||
}).Debugf("Package name => %s\n", packageName)
|
||||
|
||||
escapedName = mux.Vars(r)["tag"]
|
||||
tag, _ := url.PathUnescape(escapedName)
|
||||
lg.WithFields(logrus.Fields{
|
||||
"function": "dist-tags-put",
|
||||
}).Debugf("Tag => %s\n", tag)
|
||||
|
||||
if semver.IsValid(tag) {
|
||||
http.Error(w, fmt.Sprintf("Tag %s cannot be a semver version", tag), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if tag == "latest" {
|
||||
http.Error(w, "Cannot modify the latest tag", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
var version string
|
||||
_ = json.Unmarshal(body, &version)
|
||||
lg.WithFields(logrus.Fields{
|
||||
"function": "dist-tags-put",
|
||||
}).Debugf("Body => %s", version)
|
||||
|
||||
fileToServe, found, err := stg.GetIndexJsonFromStore(packageName, cfg.RepoDir, lg)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if !found {
|
||||
ret := fmt.Sprintf("Package not found: %s", packageName)
|
||||
http.Error(w, ret, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
var jsonFile serviceidos.IndexJson
|
||||
err = stg.ReadIndexJson(fileToServe, &jsonFile, lg)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
jsonFile.DistTags[tag] = version
|
||||
|
||||
// Write index.json
|
||||
err = stg.WriteIndexJson(fileToServe, &jsonFile, lg)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
distTagsString, _ := json.Marshal(jsonFile.DistTags)
|
||||
|
||||
response := serviceidos.TagPutResponse{
|
||||
Ok: true,
|
||||
ID: escapedName,
|
||||
DistTags: string(distTagsString),
|
||||
}
|
||||
|
||||
jsonString, _ := json.Marshal(response)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(jsonString)))
|
||||
w.Write(jsonString)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,436 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gosimplenpm/internal/config"
|
||||
"gosimplenpm/internal/serviceidos"
|
||||
"gosimplenpm/internal/storage"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUnitTagPut(t *testing.T) {
|
||||
t.Run("return `Bad request` error if tag is semver", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPut, "/-/package/{name}/dist-tags/{tag}", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
|
||||
tag := "v1.0.0"
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
"tag": tag,
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
DistTagPut(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusBadRequest)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), fmt.Sprintf("Tag %s cannot be a semver version\n", tag))
|
||||
})
|
||||
|
||||
t.Run("return `Bad request` error if tag is latest", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPut, "/-/package/{name}/dist-tags/{tag}", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
|
||||
tag := "latest"
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
"tag": tag,
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
DistTagPut(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusBadRequest)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Cannot modify the latest tag\n")
|
||||
})
|
||||
|
||||
t.Run("return `Internal Server` error if index json cannot be retrieved", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPut, "/-/package/{name}/dist-tags/{tag}", strings.NewReader("development"))
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return "", false, fmt.Errorf("Filesystem error")
|
||||
}
|
||||
|
||||
tag := "current"
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
"tag": tag,
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
DistTagPut(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Filesystem error\n")
|
||||
})
|
||||
|
||||
t.Run("return `Not found` error if package cannot be retrieved", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPut, "/-/package/{name}/dist-tags/{tag}", strings.NewReader("development"))
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
tag := "current"
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
"tag": tag,
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
DistTagPut(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusNotFound)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Package not found: test-package\n")
|
||||
})
|
||||
|
||||
t.Run("return `Internal Server` error if package.json cannot be parsed", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPut, "/-/package/{name}/dist-tags/{tag}", strings.NewReader("development"))
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return "", true, nil
|
||||
}
|
||||
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
return fmt.Errorf("Parsing failed")
|
||||
}
|
||||
|
||||
tag := "current"
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
"tag": tag,
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
DistTagPut(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Parsing failed\n")
|
||||
})
|
||||
|
||||
t.Run("return `Internal Server` error if package.json cannot be written", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPut, "/-/package/{name}/dist-tags/{tag}", strings.NewReader("\"3.5.6-rc\""))
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return "", true, nil
|
||||
}
|
||||
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
temp := serviceidos.IndexJson{
|
||||
DistTags: map[string]string{
|
||||
"current": "1.2.0",
|
||||
"latest": "1.2.0",
|
||||
},
|
||||
}
|
||||
*res = temp
|
||||
return nil
|
||||
}
|
||||
mfs.WriteIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
return fmt.Errorf("File cannot be written")
|
||||
}
|
||||
|
||||
tag := "current"
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
"tag": tag,
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
DistTagPut(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "File cannot be written\n")
|
||||
})
|
||||
|
||||
t.Run("return 200 OK if tag can be overwritten", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPut, "/-/package/{name}/dist-tags/{tag}", strings.NewReader("\"3.5.6-rc\""))
|
||||
|
||||
// req := httptest.NewRequest(http.MethodPut, "/-/package/{name}/dist-tags/{tag}", strings.NewReader("{\"version\":\"development\"}"))
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
return "", true, nil
|
||||
}
|
||||
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
temp := serviceidos.IndexJson{
|
||||
DistTags: map[string]string{
|
||||
"current": "1.2.0",
|
||||
"latest": "1.2.0",
|
||||
},
|
||||
}
|
||||
*res = temp
|
||||
return nil
|
||||
}
|
||||
mfs.WriteIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
tag := "current"
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "test-package",
|
||||
"tag": tag,
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
DistTagPut(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusOK)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "{\"ok\":true,\"id\":\"current\",\"dist-tags\":\"{\\\"current\\\":\\\"3.5.6-rc\\\",\\\"latest\\\":\\\"1.2.0\\\"}\"}")
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationTagPut(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping putTags integration test")
|
||||
}
|
||||
|
||||
token := "0N89nr/hmKXoBzG]R{fKH%YE1X"
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
mkDir(t, fmt.Sprintf("%s/@df/simplepackone", tmpDir))
|
||||
mkDir(t, fmt.Sprintf("%s/output", tmpDir))
|
||||
|
||||
indexJsonFp := "intestdata/tags/index.json"
|
||||
oldTgzFp := "intestdata/tags/simplepackone-1.0.0.tgz"
|
||||
tgzFp := "intestdata/tags/simplepackone-1.9.0.tgz"
|
||||
|
||||
cpFile(t, indexJsonFp, fmt.Sprintf("%s/@df/simplepackone/index.json", tmpDir))
|
||||
cpFile(t, oldTgzFp, fmt.Sprintf("%s/@df/simplepackone/simplepackone-1.0.0.tgz", tmpDir))
|
||||
cpFile(t, tgzFp, fmt.Sprintf("%s/@df/simplepackone/simplepackone-1.9.0.tgz", tmpDir))
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: tmpDir,
|
||||
Token: token,
|
||||
}
|
||||
|
||||
app := newTestApp(t, cfg)
|
||||
app.Routes()
|
||||
ts := newTestServer(t, app.Mux)
|
||||
defer ts.Close()
|
||||
|
||||
code, _, body := ts.put(t, fmt.Sprintf("/-/package/%s/dist-tags/release-candidate", url.PathEscape("@df/simplepackone")), token, "\"1.0.0\"")
|
||||
|
||||
assert.Equal(t, code, http.StatusOK)
|
||||
|
||||
assert.NotEmpty(t, body)
|
||||
|
||||
old := readTestFile(t, indexJsonFp)
|
||||
oldRet := make(map[string]interface{})
|
||||
json.Unmarshal(old, &oldRet)
|
||||
|
||||
assert.NotContains(t, oldRet["dist-tags"], "release-candidate")
|
||||
|
||||
modified := readTestFile(t, fmt.Sprintf("%s/@df/simplepackone/index.json", tmpDir))
|
||||
modifiedRet := make(map[string]interface{})
|
||||
json.Unmarshal(modified, &modifiedRet)
|
||||
|
||||
assert.Contains(t, modifiedRet["dist-tags"], "release-candidate")
|
||||
version := modifiedRet["dist-tags"].(map[string]interface{})
|
||||
assert.Equal(t, version["release-candidate"], "1.0.0")
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"gosimplenpm/internal/config"
|
||||
"gosimplenpm/internal/storage"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func PackageTarGet(lg *logrus.Logger, cfg config.Config, stg storage.Storage) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Sample output of npm view
|
||||
// Public
|
||||
// dist
|
||||
// .tarball: https://registry.npmjs.org/react/-/react-18.2.0.tgz
|
||||
// LocalHost
|
||||
// dist
|
||||
// .tarball: http://localhost:4000/@ookusanya/package1/-/package1-0.2.0.tgz
|
||||
|
||||
escapedName := mux.Vars(r)["name"]
|
||||
packageName, _ := url.PathUnescape(escapedName)
|
||||
lg.WithFields(logrus.Fields{
|
||||
"function": "get-tar",
|
||||
}).Debugf("Package name => %s\n", packageName)
|
||||
escapedName = mux.Vars(r)["tar"]
|
||||
tarFileNameWithScope, _ := url.PathUnescape(escapedName)
|
||||
lg.WithFields(logrus.Fields{
|
||||
"function": "get-tar",
|
||||
}).Debugf("Tarfile name => %s\n", tarFileNameWithScope)
|
||||
|
||||
fmt.Printf("Tarfile name => %s\n", tarFileNameWithScope)
|
||||
|
||||
fragments := strings.Split(tarFileNameWithScope, "/")
|
||||
tarFileName := fragments[len(fragments)-1]
|
||||
// fragments := strings.Split(urlFileFragment, "-")
|
||||
// versionFragment := fragments[len(fragments)-1]
|
||||
// versionName := strings.Split(versionFragment, ".tgz")[0]
|
||||
// fmt.Printf("Version name => %s\n", versionName)
|
||||
fileAsString, err := stg.GetTarFromStore(packageName, tarFileName, cfg.RepoDir, lg)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Sending the tar as a base64 string
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len([]byte(fileAsString))))
|
||||
io.Copy(w, bytes.NewReader([]byte(fileAsString)))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"gosimplenpm/internal/config"
|
||||
"gosimplenpm/internal/storage"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUnitTar(t *testing.T) {
|
||||
t.Run("return `Bad request` error if tar package cannot be read from the filesystem", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/{name}/-/{tar}", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetTarFromStoreFunc = func(packageName string, tarFileName string, registryPath string, lg *logrus.Logger) (string, error) {
|
||||
return "", fmt.Errorf("Filesystem error")
|
||||
}
|
||||
|
||||
tag := "@test%2Fpackage-1.2.0.tgz"
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "@test%2Fpackage",
|
||||
"tar": tag,
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
PackageTarGet(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Filesystem error\n")
|
||||
})
|
||||
|
||||
t.Run("return 200 OK if tar is found on the filesystem", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/{name}/-/{tar}", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
lg := &logrus.Logger{
|
||||
Out: os.Stdout,
|
||||
// Level: "DEBUG",
|
||||
Formatter: &logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2009-01-02 15:15:15",
|
||||
},
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
}
|
||||
|
||||
mfs := &storage.MockFs{}
|
||||
mfs.GetTarFromStoreFunc = func(packageName string, tarFileName string, registryPath string, lg *logrus.Logger) (string, error) {
|
||||
return "OHnFFeCPAnb7E0jRLSuw4hVrNDVdDmKB4lbye6oZoBVItuRKy6ee43yAMaO6k0yhr2SU9HqWSZ", nil
|
||||
}
|
||||
|
||||
tag := "@test%2Fpackage-1.2.0.tgz"
|
||||
|
||||
//Hack to try to fake gorilla/mux vars
|
||||
vars := map[string]string{
|
||||
"name": "@test%2Fpackage",
|
||||
"tar": tag,
|
||||
}
|
||||
|
||||
req = mux.SetURLVars(req, vars)
|
||||
|
||||
PackageTarGet(lg, cfg, mfs)(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusOK)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "OHnFFeCPAnb7E0jRLSuw4hVrNDVdDmKB4lbye6oZoBVItuRKy6ee43yAMaO6k0yhr2SU9HqWSZ")
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationTar(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping getTar integration test")
|
||||
}
|
||||
|
||||
token := "0N89nr/hmKXoBzG]R{fKH%YE1X"
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
t.Logf("Temp Dir: %s", tmpDir)
|
||||
|
||||
// cpFolders(t, "intestdata/@df", fmt.Sprintf("%s/@df", tmpDir))
|
||||
|
||||
mkDir(t, fmt.Sprintf("%s/@df/simplepackone", tmpDir))
|
||||
mkDir(t, fmt.Sprintf("%s/output", tmpDir))
|
||||
|
||||
indexJsonFp := "intestdata/tar/index.json"
|
||||
tgzFp := "intestdata/tar/simplepackone-1.0.0.tgz"
|
||||
|
||||
cpFile(t, indexJsonFp, fmt.Sprintf("%s/@df/simplepackone/index.json", tmpDir))
|
||||
cpFile(t, tgzFp, fmt.Sprintf("%s/@df/simplepackone/simplepackone-1.0.0.tgz", tmpDir))
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: tmpDir,
|
||||
Token: token,
|
||||
}
|
||||
|
||||
app := newTestApp(t, cfg)
|
||||
app.Routes()
|
||||
ts := newTestServer(t, app.Mux)
|
||||
defer ts.Close()
|
||||
|
||||
tarUrlFragment := url.PathEscape("@df/simplepackone-1.0.0.tgz")
|
||||
nameUrlFragment := url.PathEscape("@df/simplepackone")
|
||||
|
||||
code, _, body := ts.get(t, fmt.Sprintf("/%s/-/%s", nameUrlFragment, tarUrlFragment))
|
||||
assert.Equal(t, code, http.StatusOK)
|
||||
// assert.NotEmpty(t, body)
|
||||
|
||||
expected := readTestFileAsBase64(t, tgzFp)
|
||||
assert.Equal(t, string(body), expected)
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"gosimplenpm/internal/config"
|
||||
"gosimplenpm/internal/storage"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func newTestApp(t *testing.T, cfg config.Config) *Application {
|
||||
return &Application{
|
||||
Conf: cfg,
|
||||
Logger: &logrus.Logger{
|
||||
Out: io.Discard,
|
||||
// Level: logrus.DebugLevel,
|
||||
// Formatter: &logrus.TextFormatter{
|
||||
// FullTimestamp: true,
|
||||
// TimestampFormat: "2009-01-02 15:15:15",
|
||||
// },
|
||||
},
|
||||
FSStorage: &storage.FSStorage{},
|
||||
}
|
||||
}
|
||||
|
||||
type testServer struct {
|
||||
*httptest.Server
|
||||
}
|
||||
|
||||
func newTestServer(t *testing.T, h http.Handler) *testServer {
|
||||
ts := httptest.NewServer(h)
|
||||
return &testServer{ts}
|
||||
}
|
||||
|
||||
func (ts *testServer) get(t *testing.T, urlPath string) (int, http.Header, []byte) {
|
||||
rs, err := ts.Client().Get(ts.URL + urlPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer rs.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
body = bytes.TrimSpace(body)
|
||||
return rs.StatusCode, rs.Header, body
|
||||
}
|
||||
|
||||
func (ts *testServer) put(t *testing.T, urlPath string, token string, data string) (int, http.Header, []byte) {
|
||||
req, err := http.NewRequest(http.MethodPut, ts.URL+urlPath, strings.NewReader(data))
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rs, err := ts.Client().Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer rs.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
body = bytes.TrimSpace(body)
|
||||
return rs.StatusCode, rs.Header, body
|
||||
}
|
||||
|
||||
func (ts *testServer) delete(t *testing.T, urlPath string, token string) (int, http.Header, []byte) {
|
||||
req, err := http.NewRequest(http.MethodDelete, ts.URL+urlPath, nil)
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rs, err := ts.Client().Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer rs.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
body = bytes.TrimSpace(body)
|
||||
return rs.StatusCode, rs.Header, body
|
||||
}
|
||||
|
||||
func cpFile(t *testing.T, src string, dst string) {
|
||||
var srcfd *os.File
|
||||
var dstfd *os.File
|
||||
var err error
|
||||
var srcinfo os.FileInfo
|
||||
srcfd, err = os.Open(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer srcfd.Close()
|
||||
dstfd, err = os.Create(dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer dstfd.Close()
|
||||
_, err = io.Copy(dstfd, srcfd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
srcinfo, err = os.Stat(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = os.Chmod(dst, srcinfo.Mode())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// cpFolders - Copy folders recursively
|
||||
// func cpFolders(t *testing.T, src string, dst string) {
|
||||
// var err error
|
||||
// var fds []fs.DirEntry
|
||||
// var srcinfo os.FileInfo
|
||||
|
||||
// srcinfo, err = os.Stat(src)
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
|
||||
// err = os.MkdirAll(dst, srcinfo.Mode())
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
|
||||
// fds, err = os.ReadDir(src)
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
|
||||
// for _, fd := range fds {
|
||||
// srcfp := path.Join(src, fd.Name())
|
||||
// dstfp := path.Join(dst, fd.Name())
|
||||
// if fd.IsDir() {
|
||||
// cpFolders(t, srcfp, dstfp)
|
||||
// } else {
|
||||
// cpFile(t, srcfp, dstfp)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
func mkDir(t *testing.T, fp string) {
|
||||
err := os.MkdirAll(fp, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func readTestFile(t *testing.T, fp string) []byte {
|
||||
f, err := os.ReadFile(fp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func readTestFileAsBase64(t *testing.T, fp string) string {
|
||||
f, err := os.ReadFile(fp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return base64.StdEncoding.EncodeToString(f)
|
||||
}
|
||||
|
||||
func listDir(t *testing.T, fp string, list bool) []string {
|
||||
var filePaths []string
|
||||
err := filepath.Walk(fp, func(path string, info os.FileInfo, err error) error {
|
||||
if list {
|
||||
t.Logf("File (Directory: %t)=> %s", info.IsDir(), path)
|
||||
} else {
|
||||
if !info.IsDir() {
|
||||
filePaths = append(filePaths, path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return filePaths
|
||||
}
|
||||
|
||||
func IsDirEmpty(t *testing.T, name string) bool {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// read in ONLY one file
|
||||
_, err = f.Readdir(1)
|
||||
|
||||
// and if the file is EOF... well, the dir is empty.
|
||||
return err == io.EOF
|
||||
}
|
||||
|
||||
func containsSub(s []string, str string) bool {
|
||||
for _, v := range s {
|
||||
if strings.Contains(v, str) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package middlewares
|
||||
|
||||
import (
|
||||
"gosimplenpm/internal/config"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func AuthMiddleware(cfg config.Config) func(http.HandlerFunc) http.HandlerFunc {
|
||||
return func(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// get headers
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
authFields := strings.Fields(authHeader)
|
||||
if len(authFields) != 2 || strings.ToLower(authFields[0]) != "bearer" {
|
||||
http.Error(w, "Authentication Error", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
token := authFields[1]
|
||||
if token != cfg.Token {
|
||||
http.Error(w, "Authentication Error", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package middlewares
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"gosimplenpm/internal/config"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUnitMAuthMiddleware(t *testing.T) {
|
||||
|
||||
router := mux.NewRouter()
|
||||
handlerStr := []byte("Logic\n")
|
||||
|
||||
hFunc := func(w http.ResponseWriter, e *http.Request) {
|
||||
_, err := w.Write(handlerStr)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed writing HTTP response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
cfg := config.Config{
|
||||
RepoDir: "",
|
||||
Token: "MyToken",
|
||||
}
|
||||
|
||||
router.HandleFunc("/", AuthMiddleware(cfg)(hFunc))
|
||||
|
||||
t.Run("return `Status Foribben` if there is no token", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
req.Header.Set("Authorization", "")
|
||||
|
||||
router.ServeHTTP(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusForbidden)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Authentication Error\n")
|
||||
})
|
||||
|
||||
t.Run("return `Status Foribben` if the Authorization field is not set properly", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
req.Header.Set("Authorization", "Secret other")
|
||||
|
||||
router.ServeHTTP(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusForbidden)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Authentication Error\n")
|
||||
})
|
||||
|
||||
t.Run("return `Status Foribben` if the token is incorrect", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
wrt := httptest.NewRecorder()
|
||||
|
||||
req.Header.Set("Authorization", "Bearer incorrectToken")
|
||||
|
||||
router.ServeHTTP(wrt, req)
|
||||
|
||||
rs := wrt.Result()
|
||||
|
||||
assert.Equal(t, rs.StatusCode, http.StatusForbidden)
|
||||
|
||||
defer rs.Body.Close()
|
||||
body, err := io.ReadAll(rs.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes.TrimSpace(body)
|
||||
|
||||
assert.Equal(t, string(body), "Authentication Error\n")
|
||||
})
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package middlewares
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func LogMiddleware(lg *logrus.Logger) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
lg.Debugf("%s - %s - %s", r.Method, r.URL, r.Host)
|
||||
|
||||
hasBody := false
|
||||
if r.Method == "PUT" {
|
||||
hasBody = true
|
||||
}
|
||||
|
||||
requestDump, err := httputil.DumpRequest(r, hasBody)
|
||||
if err != nil {
|
||||
lg.Debugln(err)
|
||||
}
|
||||
lg.Debugln("RequestDump: ", string(requestDump))
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package serviceidos
|
||||
|
||||
type IndexJsonAttachments struct {
|
||||
ContentType string `json:"content_type"`
|
||||
Data string `json:"data"`
|
||||
Length int `json:"length"`
|
||||
}
|
||||
|
||||
type IndexJsonDist struct {
|
||||
Integrity string `json:"integrity"`
|
||||
Shasum string `json:"shasum"`
|
||||
Tarball string `json:"tarball"`
|
||||
FileCount int `json:"fileCount"`
|
||||
UnpackedSize int `json:"unpackedSize"`
|
||||
}
|
||||
|
||||
type IndexJsonRepository struct {
|
||||
Type string `json:"type"`
|
||||
Url string `json:"url"`
|
||||
// Only used if the package.json is not in the root folder of the url
|
||||
Directory string `json:"directory"`
|
||||
}
|
||||
|
||||
type IndexJsonEngines struct {
|
||||
NodeVersion string `json:"node,omitempty"`
|
||||
NpmVersion string `json:"npm,omitempty"`
|
||||
}
|
||||
|
||||
type IndexJsonBugs struct {
|
||||
Url string `json:"url,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
}
|
||||
|
||||
type IndexJsonAuthor struct {
|
||||
Url string `json:"url,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
}
|
||||
|
||||
// https://docs.npmjs.com/cli/v10/configuring-npm/package-json
|
||||
// https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md
|
||||
type IndexJsonVersions struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Description string `json:"description"`
|
||||
Main string `json:"main,omitempty"`
|
||||
Scripts map[string]string `json:"scripts,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
License string `json:"license"`
|
||||
Repository IndexJsonRepository `json:"repository,omitempty"`
|
||||
Files []string `json:"files,omitempty"`
|
||||
Homepage string `json:"homepage,omitempty"`
|
||||
Readme string `json:"readme,omitempty"`
|
||||
ReadmeFilename string `json:"readmeFilename,omitempty"`
|
||||
Keywords []string `json:"keywords,omitempty"`
|
||||
ID string `json:"_id"`
|
||||
Contributors []IndexJsonAuthor `json:"contributors,omitempty"`
|
||||
Maintainers []IndexJsonAuthor `json:"maintainers.omitempty"`
|
||||
Bugs IndexJsonBugs `json:"bugs,omitempty"`
|
||||
Bin map[string]string `json:"bin"`
|
||||
OperatingSystem []string `json:"os,omitempty"`
|
||||
Cpu []string `json:"cpu,omitempty"`
|
||||
Engines IndexJsonEngines `json:"engines,omitempty"`
|
||||
NodeVersion string `json:"_nodeVersion"`
|
||||
NpmVersion string `json:"_npmVersion"`
|
||||
Dist IndexJsonDist `json:"dist"`
|
||||
Dependencies map[string]string `json:"dependencies,omitempty"`
|
||||
DevDependencies map[string]string `json:"devDependencies,omitempty"`
|
||||
PeerDependencies map[string]string `json:"peerDependencies,omitempty"`
|
||||
PeerDependenciesMeta map[string]map[string]bool `json:"peerDependenciesMeta,omitempty"`
|
||||
BundleDependencies []string `json:"bundleDependencies,omitempty"`
|
||||
Resolutions map[string]string `json:"resolutions,omitempty"`
|
||||
}
|
||||
|
||||
type IndexJson struct {
|
||||
ID string `json:"_id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Readme string `json:"readme,omitempty"`
|
||||
ReadmeFilename string `json:"readmeFilename,omitempty"`
|
||||
DistTags map[string]string `json:"dist-tags"`
|
||||
TimesPackage map[string]string `json:"time"`
|
||||
Versions map[string]IndexJsonVersions `json:"versions"`
|
||||
Access string `json:"access"`
|
||||
Attachments map[string]IndexJsonAttachments `json:"_attachments"`
|
||||
}
|
||||
|
||||
type IndexJsonAbridgedVersions struct {
|
||||
HasShrinkWrap bool `json:"_hasShrinkwrap"`
|
||||
Dist IndexJsonDist `json:"dist"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md
|
||||
type IndexJsonAbridged struct {
|
||||
DistTags map[string]string `json:"dist-tags"`
|
||||
Modified string `json:"modified"`
|
||||
Name string `json:"name"`
|
||||
Versions map[string]IndexJsonAbridgedVersions `json:"versions"`
|
||||
}
|
||||
|
||||
type TagPutResponse struct {
|
||||
Ok bool `json:"ok"`
|
||||
ID string `json:"id"`
|
||||
DistTags string `json:"dist-tags"`
|
||||
}
|
||||
|
||||
type TagDeleteResponse struct {
|
||||
Ok bool `json:"ok"`
|
||||
ID string `json:"id"`
|
||||
DistTags string `json:"dist-tags"`
|
||||
}
|
||||
|
||||
type PublishPutResponse struct {
|
||||
Ok bool `json:"ok"`
|
||||
Name string `json:"package_name"`
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"gosimplenpm/internal/serviceidos"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type FSStorage struct{}
|
||||
|
||||
func (f *FSStorage) GetIndexJsonFromStore(packageName string, registryPath string, log *logrus.Logger) (string, bool, error) {
|
||||
fileToServe := ""
|
||||
found := false
|
||||
|
||||
err := filepath.WalkDir(registryPath, func(fp string, info fs.DirEntry, e error) error {
|
||||
if strings.Contains(fp, path.Join(packageName, "index.json")) {
|
||||
fileToServe = fp
|
||||
found = true
|
||||
}
|
||||
return e
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"function": "get-index-json-from-store",
|
||||
}).Errorf("List files error: +%v\n", err)
|
||||
return fileToServe, found, err
|
||||
}
|
||||
|
||||
if fileToServe == "" && !found {
|
||||
fileToServe = path.Join(registryPath, packageName, "index.json")
|
||||
}
|
||||
|
||||
return fileToServe, found, nil
|
||||
}
|
||||
|
||||
func (f *FSStorage) GetTarFromStore(packageName string, tarFileName string, registryPath string, log *logrus.Logger) (string, error) {
|
||||
fileToServe := ""
|
||||
|
||||
err := filepath.WalkDir(registryPath, func(fp string, info fs.DirEntry, e error) error {
|
||||
if strings.Contains(fp, path.Join(packageName, tarFileName)) {
|
||||
fileToServe = fp
|
||||
}
|
||||
return e
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"function": "get-tar-from-store",
|
||||
}).Errorf("List files error: +%v\n", err)
|
||||
return fileToServe, err
|
||||
}
|
||||
|
||||
if fileToServe == "" {
|
||||
return fileToServe, fmt.Errorf("file %s is not found for package %s", tarFileName, packageName)
|
||||
}
|
||||
|
||||
file, err := os.Open(fileToServe)
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"function": "get-tar-from-store",
|
||||
}).Errorf("Open error: %s\n", fileToServe)
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
bs, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"function": "get-tar-from-store",
|
||||
}).Errorf("File Read error: %s\n", fileToServe)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return base64.StdEncoding.EncodeToString(bs), err
|
||||
}
|
||||
|
||||
func (f *FSStorage) ReadIndexJson(fPath string, res *serviceidos.IndexJson, log *logrus.Logger) error {
|
||||
jsonFile, err := os.Open(fPath)
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"function": "read-index-json",
|
||||
}).Errorf("File Not found: %s\n", fPath)
|
||||
return err
|
||||
}
|
||||
|
||||
defer jsonFile.Close()
|
||||
|
||||
err = json.NewDecoder(jsonFile).Decode(res)
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"function": "read-index-json",
|
||||
}).Errorf("Unmarshalerror: %+v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FSStorage) WriteIndexJson(fPath string, res *serviceidos.IndexJson, log *logrus.Logger) error {
|
||||
// Need to create the directory first
|
||||
parent := path.Dir(fPath)
|
||||
err := os.MkdirAll(parent, os.ModePerm)
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"function": "write-index-json",
|
||||
}).Errorf("Folder (%s) creation failed.\n", fPath)
|
||||
return err
|
||||
}
|
||||
|
||||
// Create the file
|
||||
jsonFile, err := os.Create(fPath)
|
||||
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"function": "write-index-json",
|
||||
}).Errorf("Creation error for path(%s): %+v\n ", fPath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
defer jsonFile.Close()
|
||||
|
||||
err = json.NewEncoder(jsonFile).Encode(res)
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"function": "write-index-json",
|
||||
}).Debugf("Marshalerror: %+v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FSStorage) WritePackageToStore(fPath string, data string, log *logrus.Logger) error {
|
||||
dec, err := base64.StdEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"function": "write-package-to-store",
|
||||
}).Errorf("Base64 Decode error: %+v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
dataFile, err := os.Create(fPath)
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"function": "write-package-to-store",
|
||||
}).Errorf("Creation error: %s\n", fPath)
|
||||
return err
|
||||
}
|
||||
|
||||
defer dataFile.Close()
|
||||
|
||||
_, err = dataFile.Write(dec)
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"function": "write-package-to-store",
|
||||
}).Errorf("Write error: %s\n", fPath)
|
||||
return err
|
||||
}
|
||||
|
||||
err = dataFile.Sync()
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"function": "write-package-to-store",
|
||||
}).Errorf("Sync error: %s\n", fPath)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"gosimplenpm/internal/serviceidos"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type MockFs struct {
|
||||
calls map[string]int
|
||||
GetIndexJsonFromStoreFunc func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error)
|
||||
GetTarFromStoreFunc func(packageName string, tarFileName string, registryPath string, lg *logrus.Logger) (string, error)
|
||||
ReadIndexJsonFunc func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error
|
||||
WriteIndexJsonFunc func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error
|
||||
WritePackageToStoreFunc func(fPath string, data string, lg *logrus.Logger) error
|
||||
}
|
||||
|
||||
func (m *MockFs) GetIndexJsonFromStore(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
|
||||
if len(m.calls) == 0 {
|
||||
m.calls = make(map[string]int)
|
||||
}
|
||||
m.calls["GetIndexJsonFromStore"] += 1
|
||||
return m.GetIndexJsonFromStoreFunc(packageName, registryPath, lg)
|
||||
}
|
||||
|
||||
func (m *MockFs) GetTarFromStore(packageName string, tarFileName string, registryPath string, lg *logrus.Logger) (string, error) {
|
||||
if len(m.calls) == 0 {
|
||||
m.calls = make(map[string]int)
|
||||
}
|
||||
m.calls["GetTarFromStore"] += 1
|
||||
return m.GetTarFromStoreFunc(packageName, tarFileName, registryPath, lg)
|
||||
}
|
||||
|
||||
func (m *MockFs) ReadIndexJson(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
if len(m.calls) == 0 {
|
||||
m.calls = make(map[string]int)
|
||||
}
|
||||
m.calls["ReadIndexJson"] += 1
|
||||
return m.ReadIndexJsonFunc(fPath, res, lg)
|
||||
}
|
||||
|
||||
func (m *MockFs) WriteIndexJson(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
|
||||
if len(m.calls) == 0 {
|
||||
m.calls = make(map[string]int)
|
||||
}
|
||||
m.calls["WriteIndexJson"] += 1
|
||||
return m.WriteIndexJsonFunc(fPath, res, lg)
|
||||
}
|
||||
|
||||
func (m *MockFs) WritePackageToStore(fPath string, data string, lg *logrus.Logger) error {
|
||||
if len(m.calls) == 0 {
|
||||
m.calls = make(map[string]int)
|
||||
}
|
||||
m.calls["WritePackageToStore"] += 1
|
||||
return m.WritePackageToStoreFunc(fPath, data, lg)
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"gosimplenpm/internal/serviceidos"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Storage interface {
|
||||
GetIndexJsonFromStore(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error)
|
||||
GetTarFromStore(packageName string, tarFileName string, registryPath string, lg *logrus.Logger) (string, error)
|
||||
ReadIndexJson(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error
|
||||
WriteIndexJson(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error
|
||||
WritePackageToStore(fPath string, data string, lg *logrus.Logger) error
|
||||
}
|
34
main.go
34
main.go
|
@ -1,13 +1,35 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"gosimplenpm/cmd/gosimplenpm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := new(application)
|
||||
log.Print("Starting server on port 4000")
|
||||
err := http.ListenAndServe(":4000", app.Routes())
|
||||
log.Fatal(err)
|
||||
gosimplenpm.Execute()
|
||||
}
|
||||
|
||||
// func main() {
|
||||
// log := &logrus.Logger{
|
||||
// Out: os.Stdout,
|
||||
// Level: logrus.DebugLevel,
|
||||
// Formatter: &logrus.TextFormatter{
|
||||
// FullTimestamp: true,
|
||||
// TimestampFormat: "2009-01-02 15:15:15",
|
||||
// },
|
||||
// }
|
||||
|
||||
// var cfg config.Config
|
||||
// err := config.LoadConfiguration("userdata/config.json", &cfg)
|
||||
// if err != nil {
|
||||
// log.Fatalf("Config is not loaded: %+v\n", err)
|
||||
// }
|
||||
|
||||
// app := &gosimplenpm.Application{
|
||||
// Conf: cfg,
|
||||
// Logger: log,
|
||||
// }
|
||||
|
||||
// log.Infoln("Starting server on port 4000")
|
||||
// err = http.ListenAndServe(":4000", app.Routes())
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func GetPackageFromStore(packageName string) (string, error) {
|
||||
fileToServe := ""
|
||||
searchDir, err := filepath.Abs("./examples")
|
||||
if err != nil {
|
||||
fmt.Printf("File repo not found: +%v/n", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = filepath.WalkDir(searchDir, func(fp string, info fs.DirEntry, e error) error {
|
||||
if strings.Contains(fp, path.Join(packageName, "index.json")) {
|
||||
fileToServe = fp
|
||||
}
|
||||
return e
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("List files error: +%v/n", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fileToServe, nil
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
{"_id":"@ookusanya/simplepackone","name":"@ookusanya/simplepackone","description":"This is a very rough implementation of a private npm registry.","dist-tags":{"latest":"1.2.0"},"versions":{"1.0.0":{"name":"@ookusanya/simplepackone","version":"1.0.0","description":"This is a very rough implementation of a private npm registry.","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" \u0026\u0026 exit 1"},"license":"MIT","files":null,"readme":"# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...","_id":"@ookusanya/simplepackone@1.0.0","_nodeVersion":"16.20.0","_npmVersion":"8.19.4","dist":{"integrity":"sha512-x9cAEHDPM0dLhdyd8baWLTNYskr41QXLNv5HS5WNDg1CVzwcMaAU+N9lYVuOvLf/xPHLielWmX+wLXEtRWQvGw==","shasum":"545a7737b59b1a6f064339a4ebe056e8a9b1511d","tarball":"http://localhost:4000/@ookusanya/simplepackone/-/@ookusanya/simplepackone-1.0.0.tgz"},"dependencies":{"eslint":"^7.x"}},"1.2.0":{"name":"@ookusanya/simplepackone","version":"1.2.0","description":"This is a very rough implementation of a private npm registry.","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" \u0026\u0026 exit 1"},"license":"MIT","files":null,"readme":"# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...","_id":"@ookusanya/simplepackone@1.2.0","_nodeVersion":"16.20.0","_npmVersion":"8.19.4","dist":{"integrity":"sha512-coOiU+ywV/do/+HwK91mfuei9491yfQUedLWjspAjDa58RniJvQNTF/cXDp/sooVdpjgEbCqKVyLOju6C1i3pw==","shasum":"1adafdbe8878372c9f4b554bf30d5bfb1b7896c5","tarball":"http://localhost:4000/@ookusanya%2Fsimplepackone/-/@ookusanya%2Fsimplepackone-1.2.0.tgz"},"dependencies":{"eslint":"^7.x","mocha":"^10.x","uuid":"^9.x"}}},"access":"","_attachments":{"@ookusanya/simplepackone-1.2.0.tgz":{"content_type":"application/octet-stream","data":"H4sIAAAAAAAC/+1XbWvjOBDuZ/+KwQtLCyZ10pfl+uncRGnMOnawne0VlgXVVmJtHctIcl849r/fSEmb9nof73LcXoaA0Lw9M6PMSG5pcUeX7Jg3JXvsfVcH/wD5vn9+egp/xbc0OIGDk7Nzv98/P/dRz+/7nwY+rgc7oE5pKjGUvyFJJHhZ/yNUiEaJmvVqsTx0J0wy9+hgT/8fajf9v1lxAohmx/1/ev6u//tnZ/19/++CfncA3IaumHsB7q9C3HWKNk/0WPFVWzPzrxANcz2jdc+k4qIxiv3eoOevuSVTheSt3kjWzBXldvd8q6y5a0WFAoOKDM2UNmqsqAR8dYmUQl5AI8AIQLWs4AvOyq8ufPwI7JFr6Lto+cN6o52uhNxi1rxgjbJ5TMP8ObiWYQhNwdkrWKZq3ljgb596j1bTxCyKilpm399yu46X7oX77RdkGWjnh/NT9n9KgtGU9Fbl7u//waD/7v4/Oxns+38X9AGuxLrXm3blOHnFFeCPAnb7E0jRLSuw4hVrNDVdDmKB4lbye6oZoBVItuRKy6ee43yAMaO6k0yhr2SU9HqWSZqKNoX18VZwLeQdRGj9wt1fyf9K/0fhkMQZ6elHveP+H/hng3f3f9/f9/9OyBmK9knyZaXhsDiCgfkYS+rugZZ0xWtRU0g2TwLHmTG54sq8AcyIqPBj4fYJlpI2mpUeLCRjZjbgLSqXzAMtAM2gxVeDGRq3Gt8EvFk6FAqENJraDBslFvqBSobKJVClRMFxrpRQiqLbzpwFr5mCQ10xx802Fu6RBSkZrYE36I3BswgeOD4OOo2jCQcTL4wPD5WKuitNDM/imq/4BsGY2zoo47RTmIGJ04OVKPnCrMym1Xa3NVeV55Rm5vHbTiNTGaZ9fngmj2MhQbG6Nh7w5bHOdRud1UEUpzUF1ZsSWdyHSqzeZoIlWnSyQUhW2nQFlswifmeFNl6M+kLUtXjA1BCyKbnJSF2YcY51vRX3zOayPuZGaAx1HYI5gHZ7qhuRqijGfsucdcEQF8tLX6UjDTz2TaM51r4V0uL9OU0c8PmEQJaM8+sgJRBmMEuTL+GIjMANMty7HlyH+SSZ54AaaRDnN5CMIYhv4HMYjzyH/DZLSZZBkkI4nUUhGXkQxsNoPgrjK7hEuzjJIQrxwYdO8wQM4MZVSNBu7ExJOpzgNrgMozC/8WAc5rHxOUanAcyCNA+H8yhIYTZPZ0lGEH7kxEkcxuMUUciUxHkPUREKyBfcQDYJoshCBXOMPrXxDZPZTRpeTXKYJNGIIPOSOFEYXEZkDYVJDaMgnHowCqbBFbFWCXpJwahtorueEMtCvAB/wzxMYgdrMkziPMWth1mm+YvpdZgRD4I0zExBxmmC7k050SKxTtAuJmsvptTw5kRQxeznGdnGMiJBhL4yY/xaeX8x72lPe9rTT0V/APlm4rcAGAAA","length":1089}}}
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue