package npm import ( "encoding/json" "fmt" "path/filepath" "regexp" "github.com/pkg/errors" "github.com/SAP/jenkins-library/pkg/log" CredentialUtils "github.com/SAP/jenkins-library/pkg/piperutils" ) type npmMinimalPackageDescriptor struct { Name string `json:version` Version string `json:version` } func (pd *npmMinimalPackageDescriptor) Scope() string { r := regexp.MustCompile(`^(?:(?P@[^\/]+)\/)?(?P.+)$`) matches := r.FindStringSubmatch(pd.Name) if len(matches) == 0 { return "" } return matches[1] } // PublishAllPackages executes npm publish for all package.json files defined in packageJSONFiles list func (exec *Execute) PublishAllPackages(packageJSONFiles []string, registry, username, password string, packBeforePublish bool) error { for _, packageJSON := range packageJSONFiles { log.Entry().Infof("triggering publish for %s", packageJSON) fileExists, err := exec.Utils.FileExists(packageJSON) if err != nil { return fmt.Errorf("cannot check if '%s' exists: %w", packageJSON, err) } if !fileExists { return fmt.Errorf("package.json file '%s' not found: %w", packageJSON, err) } err = exec.publish(packageJSON, registry, username, password, packBeforePublish) if err != nil { return err } } return nil } // publish executes npm publish for package.json func (exec *Execute) publish(packageJSON, registry, username, password string, packBeforePublish bool) error { execRunner := exec.Utils.GetExecRunner() scope, err := exec.readPackageScope(packageJSON) if err != nil { return errors.Wrapf(err, "error reading package scope from %s", packageJSON) } npmignore := NewNPMIgnore(filepath.Dir(packageJSON)) if exists, err := exec.Utils.FileExists(npmignore.filepath); exists { if err != nil { return errors.Wrapf(err, "failed to check for existing %s file", npmignore.filepath) } log.Entry().Debugf("loading existing %s file", npmignore.filepath) if err = npmignore.Load(); err != nil { return errors.Wrapf(err, "failed to read existing %s file", npmignore.filepath) } } else { log.Entry().Debug("creating .npmignore file") } log.Entry().Debug("adding **/piper") npmignore.Add("**/piper") log.Entry().Debug("adding **/sap-piper") npmignore.Add("**/sap-piper") npmrc := NewNPMRC(filepath.Dir(packageJSON)) log.Entry().Debugf("adding piper npmrc file %v", npmrc.filepath) npmignore.Add(npmrc.filepath) if err := npmignore.Write(); err != nil { return errors.Wrapf(err, "failed to update %s file", npmignore.filepath) } // update .piperNpmrc if len(registry) > 0 { // check existing .npmrc file if exists, err := exec.Utils.FileExists(npmrc.filepath); exists { if err != nil { return errors.Wrapf(err, "failed to check for existing %s file", npmrc.filepath) } log.Entry().Debugf("loading existing %s file", npmrc.filepath) if err = npmrc.Load(); err != nil { return errors.Wrapf(err, "failed to read existing %s file", npmrc.filepath) } } else { log.Entry().Debugf("creating new npmrc file at %s", npmrc.filepath) } // set registry log.Entry().Debugf("adding registry %s", registry) npmrc.Set("registry", registry) if len(scope) > 0 { npmrc.Set(fmt.Sprintf("%s:registry", scope), registry) } // set registry auth if len(username) > 0 && len(password) > 0 { log.Entry().Debug("adding registry credentials") npmrc.Set("_auth", CredentialUtils.EncodeUsernamePassword(username, password)) npmrc.Set("always-auth", "true") } // update .npmrc if err := npmrc.Write(); err != nil { return errors.Wrapf(err, "failed to update %s file", npmrc.filepath) } } else { log.Entry().Debug("no registry provided") } if packBeforePublish { tmpDirectory, err := exec.Utils.TempDir(".", "temp-") if err != nil { return errors.Wrap(err, "creating temp directory failed") } defer exec.Utils.RemoveAll(tmpDirectory) err = execRunner.RunExecutable("npm", "pack", "--pack-destination", tmpDirectory) if err != nil { return err } _, err = exec.Utils.Copy(npmrc.filepath, filepath.Join(tmpDirectory, ".piperNpmrc")) if err != nil { return fmt.Errorf("error copying piperNpmrc file from %v to %v with error: %w", npmrc.filepath, filepath.Join(tmpDirectory, ".piperNpmrc"), err) } tarballs, err := exec.Utils.Glob(filepath.Join(tmpDirectory, "*.tgz")) if err != nil { return err } if len(tarballs) != 1 { return fmt.Errorf("found more tarballs than expected: %v", tarballs) } tarballFilePath, err := exec.Utils.Abs(tarballs[0]) if err != nil { return err } projectNpmrc := filepath.Join(filepath.Dir(packageJSON), ".npmrc") projectNpmrcExists, _ := exec.Utils.FileExists(projectNpmrc) if projectNpmrcExists { // rename the .npmrc file since it interferes with publish err = exec.Utils.FileRename(projectNpmrc, projectNpmrc+".tmp") if err != nil { return fmt.Errorf("error when renaming current .npmrc file : %w", err) } } err = execRunner.RunExecutable("npm", "publish", "--tarball", tarballFilePath, "--userconfig", filepath.Join(tmpDirectory, ".piperNpmrc"), "--registry", registry) if err != nil { return errors.Wrap(err, "failed publishing artifact") } if projectNpmrcExists { // undo the renaming ot the .npmrc to keep the workspace like before err = exec.Utils.FileRename(projectNpmrc+".tmp", projectNpmrc) if err != nil { log.Entry().Warnf("unable to rename the .npmrc file : %v", err) } } } else { err := execRunner.RunExecutable("npm", "publish", "--userconfig", npmrc.filepath, "--registry", registry) if err != nil { return errors.Wrap(err, "failed publishing artifact") } } return nil } func (exec *Execute) readPackageScope(packageJSON string) (string, error) { b, err := exec.Utils.FileRead(packageJSON) if err != nil { return "", err } var pd npmMinimalPackageDescriptor json.Unmarshal(b, &pd) return pd.Scope(), nil }