Skip to content

Commit

Permalink
Publish multiple npm workspaces packages (#1149)
Browse files Browse the repository at this point in the history
  • Loading branch information
EyalDelarea authored Mar 12, 2024
1 parent 52423cf commit 5979ebb
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 137 deletions.
117 changes: 69 additions & 48 deletions artifactory/commands/npm/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package npm
import (
"archive/tar"
"compress/gzip"
"errors"
"fmt"
"io"
"os"
Expand Down Expand Up @@ -37,7 +38,7 @@ type NpmPublishCommandArgs struct {
executablePath string
workingDirectory string
collectBuildInfo bool
packedFilePath string
packedFilePaths []string
packageInfo *biutils.PackageInfo
publishPath string
tarballProvided bool
Expand Down Expand Up @@ -172,11 +173,11 @@ func (npc *NpmPublishCommand) Run() (err error) {
return err
}
// We should delete the tarball we created
return deleteCreatedTarballAndError(npc.packedFilePath, err)
return errors.Join(err, deleteCreatedTarball(npc.packedFilePaths))
}

if !npc.tarballProvided {
if err := deleteCreatedTarball(npc.packedFilePath); err != nil {
if err := deleteCreatedTarball(npc.packedFilePaths); err != nil {
return err
}
}
Expand Down Expand Up @@ -217,6 +218,7 @@ func (npc *NpmPublishCommand) CommandName() string {
}

func (npc *NpmPublishCommand) preparePrerequisites() error {
npc.packedFilePaths = make([]string, 0)
currentDir, err := os.Getwd()
if err != nil {
return errorutils.CheckError(err)
Expand Down Expand Up @@ -251,7 +253,7 @@ func (npc *NpmPublishCommand) preparePrerequisites() error {

func (npc *NpmPublishCommand) pack() error {
log.Debug("Creating npm package.")
packageFileName, err := npm.Pack(npc.npmArgs, npc.executablePath)
packedFileNames, err := npm.Pack(npc.npmArgs, npc.executablePath)
if err != nil {
return err
}
Expand All @@ -261,8 +263,10 @@ func (npc *NpmPublishCommand) pack() error {
return err
}

npc.packedFilePath = filepath.Join(tarballDir, packageFileName)
log.Debug("Created npm package at", npc.packedFilePath)
for _, packageFileName := range packedFileNames {
npc.packedFilePaths = append(npc.packedFilePaths, filepath.Join(tarballDir, packageFileName))
}

return nil
}

Expand All @@ -279,34 +283,36 @@ func (npc *NpmPublishCommand) getTarballDir() (string, error) {
return dest, nil
}

func (npc *NpmPublishCommand) publish() error {
log.Debug("Deploying npm package.")
if err := npc.readPackageInfoFromTarball(); err != nil {
return err
}
target := fmt.Sprintf("%s/%s", npc.repo, npc.packageInfo.GetDeployPath())

// If requested, perform a Xray binary scan before deployment. If a FailBuildError is returned, skip the deployment.
if npc.xrayScan {
fileSpec := spec.NewBuilder().
Pattern(npc.packedFilePath).
Target(npc.repo + "/").
BuildSpec()
err := commandsutils.ConditionalUploadScanFunc(npc.serverDetails, fileSpec, 1, npc.scanOutputFormat)
if err != nil {
return err
func (npc *NpmPublishCommand) publish() (err error) {
for _, packedFilePath := range npc.packedFilePaths {
log.Debug("Deploying npm package.")
if err = npc.readPackageInfoFromTarball(packedFilePath); err != nil {
return
}
target := fmt.Sprintf("%s/%s", npc.repo, npc.packageInfo.GetDeployPath())

// If requested, perform a Xray binary scan before deployment. If a FailBuildError is returned, skip the deployment.
if npc.xrayScan {
fileSpec := spec.NewBuilder().
Pattern(packedFilePath).
Target(npc.repo + "/").
BuildSpec()
if err = commandsutils.ConditionalUploadScanFunc(npc.serverDetails, fileSpec, 1, npc.scanOutputFormat); err != nil {
return
}
}
err = errors.Join(err, npc.doDeploy(target, npc.serverDetails, packedFilePath))
}
return npc.doDeploy(target, npc.serverDetails)
return
}

func (npc *NpmPublishCommand) doDeploy(target string, artDetails *config.ServerDetails) error {
func (npc *NpmPublishCommand) doDeploy(target string, artDetails *config.ServerDetails, packedFilePath string) error {
servicesManager, err := utils.CreateServiceManager(artDetails, -1, 0, false)
if err != nil {
return err
}
up := services.NewUploadParams()
up.CommonParams = &specutils.CommonParams{Pattern: npc.packedFilePath, Target: target}
up.CommonParams = &specutils.CommonParams{Pattern: packedFilePath, Target: target}
var totalFailed int
if npc.collectBuildInfo || npc.detailedSummary {
if npc.collectBuildInfo {
Expand Down Expand Up @@ -341,12 +347,11 @@ func (npc *NpmPublishCommand) doDeploy(target string, artDetails *config.ServerD
}
}
if npc.detailedSummary {
npc.result.SetReader(summary.TransferDetailsReader)
npc.result.SetFailCount(totalFailed)
npc.result.SetSuccessCount(summary.TotalSucceeded)
if err = npc.setDetailedSummary(summary); err != nil {
return err
}
} else {
err = summary.TransferDetailsReader.Close()
if err != nil {
if err = summary.TransferDetailsReader.Close(); err != nil {
return err
}
}
Expand All @@ -364,6 +369,29 @@ func (npc *NpmPublishCommand) doDeploy(target string, artDetails *config.ServerD
return nil
}

func (npc *NpmPublishCommand) setDetailedSummary(summary *specutils.OperationSummary) (err error) {
npc.result.SetFailCount(npc.result.FailCount() + summary.TotalFailed)
npc.result.SetSuccessCount(npc.result.SuccessCount() + summary.TotalSucceeded)
if npc.result.Reader() == nil {
npc.result.SetReader(summary.TransferDetailsReader)
} else {
if err = npc.appendReader(summary); err != nil {
return
}
}
return
}

func (npc *NpmPublishCommand) appendReader(summary *specutils.OperationSummary) error {
readersSlice := []*content.ContentReader{npc.result.Reader(), summary.TransferDetailsReader}
reader, err := content.MergeReaders(readersSlice, content.DefaultKey)
if err != nil {
return err
}
npc.result.SetReader(reader)
return nil
}

func (npc *NpmPublishCommand) setPublishPath() error {
log.Debug("Reading Package Json.")

Expand Down Expand Up @@ -394,13 +422,12 @@ func (npc *NpmPublishCommand) setPackageInfo() error {
}
log.Debug("The provided path is not a directory, we assume this is a compressed npm package")
npc.tarballProvided = true
npc.packedFilePath = npc.publishPath
return npc.readPackageInfoFromTarball()
return npc.readPackageInfoFromTarball(npc.publishPath)
}

func (npc *NpmPublishCommand) readPackageInfoFromTarball() (err error) {
log.Debug("Extracting info from npm package:", npc.packedFilePath)
tarball, err := os.Open(npc.packedFilePath)
func (npc *NpmPublishCommand) readPackageInfoFromTarball(packedFilePath string) (err error) {
log.Debug("Extracting info from npm package:", npc.packedFilePaths)
tarball, err := os.Open(packedFilePath)
if err != nil {
return errorutils.CheckError(err)
}
Expand All @@ -420,7 +447,7 @@ func (npc *NpmPublishCommand) readPackageInfoFromTarball() (err error) {
hdr, err := tarReader.Next()
if err != nil {
if err == io.EOF {
return errorutils.CheckErrorf("Could not find 'package.json' in the compressed npm package: " + npc.packedFilePath)
return errorutils.CheckErrorf("Could not find 'package.json' in the compressed npm package: " + packedFilePath)
}
return errorutils.CheckError(err)
}
Expand All @@ -436,18 +463,12 @@ func (npc *NpmPublishCommand) readPackageInfoFromTarball() (err error) {
}
}

func deleteCreatedTarballAndError(packedFilePath string, currentError error) error {
if err := deleteCreatedTarball(packedFilePath); err != nil {
errorText := fmt.Sprintf("Two errors occurred: \n%s \n%s", currentError, err)
return errorutils.CheckErrorf(errorText)
}
return currentError
}

func deleteCreatedTarball(packedFilePath string) error {
if err := os.Remove(packedFilePath); err != nil {
return errorutils.CheckError(err)
func deleteCreatedTarball(packedFilesPath []string) error {
for _, packedFilePath := range packedFilesPath {
if err := os.Remove(packedFilePath); err != nil {
return errorutils.CheckError(err)
}
log.Debug("Successfully deleted the created npm package:", packedFilePath)
}
log.Debug("Successfully deleted the created npm package:", packedFilePath)
return nil
}
13 changes: 10 additions & 3 deletions artifactory/commands/npm/publish_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,17 @@ import (

func TestReadPackageInfoFromTarball(t *testing.T) {
npmPublish := NewNpmPublishCommand()
npmPublish.packedFilePath = filepath.Join("..", "testdata", "npm", "npm-example-0.0.3.tgz")
err := npmPublish.readPackageInfoFromTarball()
assert.NoError(t, err)
npmPublish.packedFilePaths = append(npmPublish.packedFilePaths, filepath.Join("..", "testdata", "npm", "npm-example-0.0.3.tgz"))
npmPublish.packedFilePaths = append(npmPublish.packedFilePaths, filepath.Join("..", "testdata", "npm", "npm-example-0.0.4.tgz"))

err := npmPublish.readPackageInfoFromTarball(npmPublish.packedFilePaths[0])
assert.NoError(t, err)
assert.Equal(t, "npm-example", npmPublish.packageInfo.Name)
assert.Equal(t, "0.0.3", npmPublish.packageInfo.Version)

err = npmPublish.readPackageInfoFromTarball(npmPublish.packedFilePaths[1])
assert.NoError(t, err)
assert.Equal(t, "npm-example", npmPublish.packageInfo.Name)
assert.Equal(t, "0.0.4", npmPublish.packageInfo.Version)

}
Binary file not shown.
11 changes: 5 additions & 6 deletions artifactory/utils/npm/pack.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import (
"github.com/jfrog/jfrog-client-go/utils/errorutils"
)

func Pack(npmFlags []string, executablePath string) (string, error) {
func Pack(npmFlags []string, executablePath string) ([]string, error) {
configListCmdConfig := createPackCmdConfig(executablePath, npmFlags)
output, err := gofrogcmd.RunCmdOutput(configListCmdConfig)
if err != nil {
return "", errorutils.CheckError(err)
return []string{}, errorutils.CheckError(err)
}
return getPackageFileNameFromOutput(output)
return getPackageFileNameFromOutput(output), nil
}

func createPackCmdConfig(executablePath string, splitFlags []string) *npmutils.NpmConfig {
Expand All @@ -27,8 +27,7 @@ func createPackCmdConfig(executablePath string, splitFlags []string) *npmutils.N
}
}

func getPackageFileNameFromOutput(output string) (string, error) {
func getPackageFileNameFromOutput(output string) []string {
output = strings.TrimSpace(output)
lines := strings.Split(output, "\n")
return strings.TrimSpace(lines[len(lines)-1]), nil
return strings.Split(output, "\n")
}
78 changes: 54 additions & 24 deletions artifactory/utils/npm/pack_test.go
Original file line number Diff line number Diff line change
@@ -1,36 +1,66 @@
package npm

import (
biutils "github.com/jfrog/build-info-go/build/utils"
"github.com/jfrog/build-info-go/utils"
"github.com/jfrog/jfrog-cli-core/v2/utils/tests"
"github.com/jfrog/jfrog-client-go/utils/log"
testsUtils "github.com/jfrog/jfrog-client-go/utils/tests"
"github.com/stretchr/testify/assert"
"os"
"path/filepath"
"testing"
)

const testdataDir = "../testdata/npm/"
const minimumWorkspacesNpmVersion = "7.24.2"

func TestGetPackageFileNameFromOutput(t *testing.T) {
tests := []struct {
testName string
outputTestDataFile string
expectedPackageFilename string
}{
{"Get package filename for npm 6", "npmPackOutputV6", "npm-example-0.0.3.tgz"},
{"Get package filename for npm 7", "npmPackOutputV7", "npm-example-ver0.0.3.tgz"},
}
for _, test := range tests {
t.Run(test.testName, func(t *testing.T) {
output, err := os.ReadFile(filepath.Join(testdataDir, test.outputTestDataFile))
if err != nil {
assert.NoError(t, err)
return
}
actualFilename, err := getPackageFileNameFromOutput(string(output))
if err != nil {
assert.NoError(t, err)
return
}
assert.Equal(t, test.expectedPackageFilename, actualFilename)
})
func TestNpmPackWorkspaces(t *testing.T) {

npmVersion, executablePath, err := biutils.GetNpmVersionAndExecPath(nil)
assert.NoError(t, err)
// In npm under v7 skip test
if npmVersion.Compare(minimumWorkspacesNpmVersion) > 0 {
log.Info("Test skipped as this function in not supported in npm version " + npmVersion.GetVersion())
return
}

tmpDir, createTempDirCallback := tests.CreateTempDirWithCallbackAndAssert(t)
defer createTempDirCallback()

npmProjectPath := filepath.Join("..", "..", "..", "tests", "testdata", "npm-workspaces")
err = utils.CopyDir(npmProjectPath, tmpDir, true, nil)
assert.NoError(t, err)

cwd, err := os.Getwd()
assert.NoError(t, err)
chdirCallback := testsUtils.ChangeDirWithCallback(t, cwd, tmpDir)
defer chdirCallback()

packedFileNames, err := Pack([]string{"--workspaces", "--verbose"}, executablePath)
assert.NoError(t, err)

expected := []string{"module1-1.0.0.tgz", "module2-1.0.0.tgz"}
assert.Equal(t, expected, packedFileNames)
}

func TestNpmPack(t *testing.T) {

_, executablePath, err := biutils.GetNpmVersionAndExecPath(nil)
assert.NoError(t, err)
tmpDir, createTempDirCallback := tests.CreateTempDirWithCallbackAndAssert(t)
defer createTempDirCallback()
npmProjectPath := filepath.Join("..", "..", "..", "tests", "testdata", "npm-workspaces")
err = utils.CopyDir(npmProjectPath, tmpDir, false, nil)
assert.NoError(t, err)

cwd, err := os.Getwd()
assert.NoError(t, err)
chdirCallback := testsUtils.ChangeDirWithCallback(t, cwd, tmpDir)
defer chdirCallback()

packedFileNames, err := Pack([]string{"--verbose"}, executablePath)
assert.NoError(t, err)

expected := []string{"npm-pack-test-1.0.0.tgz"}
assert.Equal(t, expected, packedFileNames)
}
28 changes: 0 additions & 28 deletions artifactory/utils/testdata/npm/npmPackOutputV6

This file was deleted.

Loading

0 comments on commit 5979ebb

Please sign in to comment.