Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Publish multiple npm workspaces packages #1149

Merged
merged 12 commits into from
Mar 12, 2024
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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will work on windows?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like from the CLI test that it is working on windows 👍

}
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
Loading