mirror of
https://github.com/drone-plugins/drone-npm.git
synced 2026-07-06 16:02:01 +08:00
236 lines
4.9 KiB
Go
236 lines
4.9 KiB
Go
package main
|
||
|
||
import (
|
||
"encoding/base64"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"io/ioutil"
|
||
"net/http"
|
||
"net/url"
|
||
"os"
|
||
"os/exec"
|
||
"path"
|
||
"strings"
|
||
|
||
log "github.com/Sirupsen/logrus"
|
||
)
|
||
|
||
type (
|
||
Config struct {
|
||
Username string
|
||
Password string
|
||
Email string
|
||
Registry string
|
||
Folder string
|
||
AlwaysAuth bool
|
||
}
|
||
|
||
NpmPackage struct {
|
||
Name string `json:"name"`
|
||
Version string `json:"version"`
|
||
}
|
||
|
||
Plugin struct {
|
||
Config Config
|
||
}
|
||
)
|
||
|
||
const GlobalRegistry = "https://registry.npmjs.org"
|
||
|
||
func (p Plugin) Exec() error {
|
||
// read the package
|
||
packagePath := path.Join(p.Config.Folder, "package.json")
|
||
|
||
npmPackage, err := readPackageFile(packagePath)
|
||
|
||
if err != nil {
|
||
log.WithFields(log.Fields{
|
||
"error": err,
|
||
}).Error("Could not read package.json")
|
||
return err
|
||
}
|
||
|
||
log.WithFields(log.Fields{
|
||
"name": npmPackage.Name,
|
||
"version": npmPackage.Version,
|
||
}).Info("Found package")
|
||
|
||
// see if the package should be published
|
||
publish, err := shouldPublishPackage(p.Config, npmPackage)
|
||
|
||
if publish {
|
||
log.Info("Attempting to publish package")
|
||
|
||
// write the npmrc file
|
||
err := writeNpmrcFile(p.Config)
|
||
|
||
if err != nil {
|
||
log.WithFields(log.Fields{
|
||
"error": err,
|
||
}).Error("Problem creating npmrc file")
|
||
|
||
return err
|
||
}
|
||
|
||
var cmds []*exec.Cmd
|
||
|
||
// write registry command
|
||
if p.Config.Registry != GlobalRegistry {
|
||
cmds = append(cmds, registryCommand(p.Config.Registry))
|
||
}
|
||
|
||
// write auth command
|
||
if p.Config.AlwaysAuth {
|
||
cmds = append(cmds, alwaysAuthCommand())
|
||
}
|
||
|
||
// write the publish command
|
||
cmds = append(cmds, publishCommand())
|
||
|
||
// run the commands
|
||
err = runCommands(cmds)
|
||
|
||
if err != nil {
|
||
log.WithFields(log.Fields{
|
||
"error": err,
|
||
}).Error("Package was not published")
|
||
}
|
||
} else {
|
||
log.WithFields(log.Fields{
|
||
"reason": err,
|
||
}).Info("Package will not be published")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
/// Reads the package file at the given path
|
||
func readPackageFile(path string) (*NpmPackage, error) {
|
||
// read the file
|
||
file, err := ioutil.ReadFile(path)
|
||
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// unmarshal the json data
|
||
npmPackage := NpmPackage{}
|
||
err = json.Unmarshal(file, &npmPackage)
|
||
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// make sure values are present
|
||
if len(npmPackage.Name) == 0 {
|
||
return nil, errors.New("No package name present")
|
||
}
|
||
|
||
if len(npmPackage.Version) == 0 {
|
||
return nil, errors.New("No package version present")
|
||
}
|
||
|
||
return &npmPackage, nil
|
||
}
|
||
|
||
/// Determines if the package should be published
|
||
func shouldPublishPackage(config Config, npmPackage *NpmPackage) (bool, error) {
|
||
// get the url for the package
|
||
//
|
||
// encoding the portion of the url for the package name in case of scopes
|
||
packageUrl := fmt.Sprintf("%s/%s/%s", config.Registry, url.QueryEscape(npmPackage.Name), npmPackage.Version)
|
||
|
||
log.WithFields(log.Fields{
|
||
"url": packageUrl,
|
||
}).Info("Requesting package information")
|
||
|
||
// create a request for the package
|
||
req, err := http.NewRequest("GET", packageUrl, nil)
|
||
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
|
||
// set authentication if needed
|
||
// NPM uses basic http auth
|
||
if config.AlwaysAuth {
|
||
req.SetBasicAuth(config.Username, config.Password)
|
||
}
|
||
|
||
// get the response
|
||
resp, err := http.DefaultClient.Do(req)
|
||
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
|
||
defer resp.Body.Close()
|
||
|
||
statusCode := resp.StatusCode
|
||
|
||
// look for a 404 to see if the package should be published
|
||
if statusCode == http.StatusNotFound {
|
||
return true, nil
|
||
} else if statusCode == http.StatusOK {
|
||
return false, errors.New("Package already published")
|
||
} else {
|
||
contents, err := ioutil.ReadAll(resp.Body)
|
||
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
|
||
return false, errors.New(fmt.Sprintf("Error Occurred. Status %d\nBody:%s", statusCode, contents))
|
||
}
|
||
}
|
||
|
||
/// Writes the npmrc file
|
||
func writeNpmrcFile(config Config) error {
|
||
// get the base64 encoded string
|
||
authString := fmt.Sprintf("%s:%s", config.Username, config.Password)
|
||
encoded := base64.StdEncoding.EncodeToString([]byte(authString))
|
||
|
||
// create the file contents
|
||
contents := fmt.Sprintf("_auth = %s\nemail = %s", encoded, config.Email)
|
||
|
||
// write the file
|
||
return ioutil.WriteFile("/root/.npmrc", []byte(contents), 0644)
|
||
}
|
||
|
||
// Sets the npm registry
|
||
func registryCommand(registry string) *exec.Cmd {
|
||
return exec.Command("npm", "config", "set", "registry", registry)
|
||
}
|
||
|
||
// Sets the always off flag
|
||
func alwaysAuthCommand() *exec.Cmd {
|
||
return exec.Command("npm", "config", "set", "always-auth", "true")
|
||
}
|
||
|
||
// Publishes the package
|
||
func publishCommand() *exec.Cmd {
|
||
return exec.Command("npm", "publish")
|
||
}
|
||
|
||
// Trace writes each command to standard error (preceded by a ‘$ ’) before it
|
||
// is executed. Used for debugging your build.
|
||
func trace(cmd *exec.Cmd) {
|
||
fmt.Fprintf(os.Stdout, "+ %s\n", strings.Join(cmd.Args, " "))
|
||
}
|
||
|
||
func runCommands(cmds []*exec.Cmd) error {
|
||
for _, cmd := range cmds {
|
||
cmd.Stdout = os.Stdout
|
||
cmd.Stderr = os.Stderr
|
||
trace(cmd)
|
||
|
||
err := cmd.Run()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|