diff --git a/artifactory/commands/npm/artifactoryinstall.go b/artifactory/commands/npm/artifactoryinstall.go new file mode 100644 index 00000000..fb6daa99 --- /dev/null +++ b/artifactory/commands/npm/artifactoryinstall.go @@ -0,0 +1,36 @@ +package npm + +import "github.com/jfrog/jfrog-client-go/utils/log" + +type npmRtInstall struct { + *NpmCommand +} + +func (nri *npmRtInstall) PrepareInstallPrerequisites(repo string) (err error) { + log.Debug("Executing npm install command using jfrog RT on repository: ", repo) + if err = nri.setArtifactoryAuth(); err != nil { + return err + } + + if err = nri.setNpmAuthRegistry(repo); err != nil { + return err + } + + return nri.setRestoreNpmrcFunc() +} + +func (nri *npmRtInstall) Run() (err error) { + if err = nri.CreateTempNpmrc(); err != nil { + return + } + if err = nri.prepareBuildInfoModule(); err != nil { + return + } + err = nri.collectDependencies() + return +} + +func (nri *npmRtInstall) RestoreNpmrc() (err error) { + // Restore the npmrc file, since we are using our own npmrc + return nri.restoreNpmrcFunc() +} diff --git a/artifactory/commands/npm/artifactoryupload.go b/artifactory/commands/npm/artifactoryupload.go new file mode 100644 index 00000000..9fa760a7 --- /dev/null +++ b/artifactory/commands/npm/artifactoryupload.go @@ -0,0 +1,129 @@ +package npm + +import ( + "errors" + "fmt" + buildinfo "github.com/jfrog/build-info-go/entities" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-client-go/artifactory" + "github.com/jfrog/jfrog-client-go/artifactory/services" + specutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/io/content" +) + +type npmRtUpload struct { + *NpmPublishCommand +} + +func (nru *npmRtUpload) upload() (err error) { + for _, packedFilePath := range nru.packedFilePaths { + if err = nru.readPackageInfoFromTarball(packedFilePath); err != nil { + return + } + target := fmt.Sprintf("%s/%s", nru.repo, nru.packageInfo.GetDeployPath()) + + // If requested, perform a Xray binary scan before deployment. If a FailBuildError is returned, skip the deployment. + if nru.xrayScan { + if err = performXrayScan(packedFilePath, nru.repo, nru.serverDetails, nru.scanOutputFormat); err != nil { + return + } + } + err = errors.Join(err, nru.doDeploy(target, nru.serverDetails, packedFilePath)) + } + return +} + +func (nru *npmRtUpload) getBuildArtifacts() ([]buildinfo.Artifact, error) { + return specutils.ConvertArtifactsDetailsToBuildInfoArtifacts(nru.artifactsDetailsReader) +} + +func (nru *npmRtUpload) 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: packedFilePath, Target: target} + if err = nru.addDistTagIfSet(up.CommonParams); err != nil { + return err + } + var totalFailed int + if nru.collectBuildInfo || nru.detailedSummary { + if nru.collectBuildInfo { + up.BuildProps, err = nru.getBuildPropsForArtifact() + if err != nil { + return err + } + } + summary, err := servicesManager.UploadFilesWithSummary(artifactory.UploadServiceOptions{}, up) + if err != nil { + return err + } + totalFailed = summary.TotalFailed + if nru.collectBuildInfo { + nru.artifactsDetailsReader = summary.ArtifactsDetailsReader + } else { + err = summary.ArtifactsDetailsReader.Close() + if err != nil { + return err + } + } + if nru.detailedSummary { + if err = nru.setDetailedSummary(summary); err != nil { + return err + } + } else { + if err = summary.TransferDetailsReader.Close(); err != nil { + return err + } + } + } else { + _, totalFailed, err = servicesManager.UploadFiles(artifactory.UploadServiceOptions{}, up) + if err != nil { + return err + } + } + + // We are deploying only one Artifact which have to be deployed, in case of failure we should fail + if totalFailed > 0 { + return errorutils.CheckErrorf("Failed to upload the npm package to Artifactory. See Artifactory logs for more details.") + } + return nil +} + +func (nru *npmRtUpload) addDistTagIfSet(params *specutils.CommonParams) error { + if nru.distTag == "" { + return nil + } + props, err := specutils.ParseProperties(DistTagPropKey + "=" + nru.distTag) + if err != nil { + return err + } + params.TargetProps = props + return nil +} + +func (nru *npmRtUpload) appendReader(summary *specutils.OperationSummary) error { + readersSlice := []*content.ContentReader{nru.result.Reader(), summary.TransferDetailsReader} + reader, err := content.MergeReaders(readersSlice, content.DefaultKey) + if err != nil { + return err + } + nru.result.SetReader(reader) + return nil +} + +func (nru *npmRtUpload) setDetailedSummary(summary *specutils.OperationSummary) (err error) { + nru.result.SetFailCount(nru.result.FailCount() + summary.TotalFailed) + nru.result.SetSuccessCount(nru.result.SuccessCount() + summary.TotalSucceeded) + if nru.result.Reader() == nil { + nru.result.SetReader(summary.TransferDetailsReader) + } else { + if err = nru.appendReader(summary); err != nil { + return + } + } + return +} diff --git a/artifactory/commands/npm/common.go b/artifactory/commands/npm/common.go index 1cbc2bc2..68c1688f 100644 --- a/artifactory/commands/npm/common.go +++ b/artifactory/commands/npm/common.go @@ -10,6 +10,7 @@ type CommonArgs struct { buildConfiguration *build.BuildConfiguration npmArgs []string serverDetails *config.ServerDetails + useNative bool } func (ca *CommonArgs) SetServerDetails(serverDetails *config.ServerDetails) *CommonArgs { @@ -31,3 +32,12 @@ func (ca *CommonArgs) SetRepo(repo string) *CommonArgs { ca.repo = repo return ca } + +func (ca *CommonArgs) UseNative() bool { + return ca.useNative +} + +func (ca *CommonArgs) SetUseNative(useNpmRc bool) *CommonArgs { + ca.useNative = useNpmRc + return ca +} diff --git a/artifactory/commands/npm/installstrategy.go b/artifactory/commands/npm/installstrategy.go new file mode 100644 index 00000000..e12c9174 --- /dev/null +++ b/artifactory/commands/npm/installstrategy.go @@ -0,0 +1,41 @@ +package npm + +import "github.com/jfrog/jfrog-client-go/utils/log" + +type Installer interface { + PrepareInstallPrerequisites(repo string) error + Run() error + RestoreNpmrc() error +} + +type NpmInstallStrategy struct { + strategy Installer + strategyName string +} + +// Get npm implementation +func NewNpmInstallStrategy(useNativeClient bool, npmCommand *NpmCommand) *NpmInstallStrategy { + npi := NpmInstallStrategy{} + if useNativeClient { + npi.strategy = &npmInstall{npmCommand} + npi.strategyName = "native" + } else { + npi.strategy = &npmRtInstall{npmCommand} + npi.strategyName = "artifactory" + } + return &npi +} + +func (npi *NpmInstallStrategy) PrepareInstallPrerequisites(repo string) error { + log.Debug("Using strategy for preparing install prerequisites: ", npi.strategyName) + return npi.strategy.PrepareInstallPrerequisites(repo) +} + +func (npi *NpmInstallStrategy) Install() error { + log.Debug("Using strategy for npm install: ", npi.strategyName) + return npi.strategy.Run() +} + +func (npi *NpmInstallStrategy) RestoreNpmrc() error { + return npi.strategy.RestoreNpmrc() +} diff --git a/artifactory/commands/npm/npmcommand.go b/artifactory/commands/npm/npmcommand.go index 079bc171..853352d2 100644 --- a/artifactory/commands/npm/npmcommand.go +++ b/artifactory/commands/npm/npmcommand.go @@ -55,6 +55,7 @@ type NpmCommand struct { configFilePath string collectBuildInfo bool buildInfoModule *build.NpmModule + installHandler *NpmInstallStrategy } func NewNpmCommand(cmdName string, collectBuildInfo bool) *NpmCommand { @@ -168,15 +169,15 @@ func (nc *NpmCommand) PreparePrerequisites(repo string) error { return err } log.Debug("Working directory set to:", nc.workingDirectory) - if err = nc.setArtifactoryAuth(); err != nil { - return err - } - if err = nc.setNpmAuthRegistry(repo); err != nil { + _, useNative, err := coreutils.ExtractUseNativeFromArgs(nc.npmArgs) + if err != nil { return err } + nc.SetUseNative(useNative) + nc.installHandler = NewNpmInstallStrategy(nc.UseNative(), nc) - return nc.setRestoreNpmrcFunc() + return nc.installHandler.PrepareInstallPrerequisites(repo) } func (nc *NpmCommand) setNpmAuthRegistry(repo string) (err error) { @@ -309,17 +310,9 @@ func (nc *NpmCommand) Run() (err error) { return } defer func() { - err = errors.Join(err, nc.restoreNpmrcFunc()) + err = errors.Join(err, nc.installHandler.RestoreNpmrc()) }() - if err = nc.CreateTempNpmrc(); err != nil { - return - } - - if err = nc.prepareBuildInfoModule(); err != nil { - return - } - - err = nc.collectDependencies() + err = nc.installHandler.Install() return } diff --git a/artifactory/commands/npm/npminstall.go b/artifactory/commands/npm/npminstall.go new file mode 100644 index 00000000..9406b598 --- /dev/null +++ b/artifactory/commands/npm/npminstall.go @@ -0,0 +1,25 @@ +package npm + +import "github.com/jfrog/jfrog-client-go/utils/log" + +type npmInstall struct { + *NpmCommand +} + +func (ni *npmInstall) PrepareInstallPrerequisites(repo string) error { + log.Debug("Skipping npm install preparation on repository: ", repo) + return nil +} + +func (ni *npmInstall) Run() (err error) { + if err = ni.prepareBuildInfoModule(); err != nil { + return + } + err = ni.collectDependencies() + return +} + +func (ni *npmInstall) RestoreNpmrc() error { + // No need to restore the npmrc file, since we are using user's npmrc + return nil +} diff --git a/artifactory/commands/npm/npmpublish.go b/artifactory/commands/npm/npmpublish.go new file mode 100644 index 00000000..dc632d10 --- /dev/null +++ b/artifactory/commands/npm/npmpublish.go @@ -0,0 +1,160 @@ +package npm + +import ( + "errors" + "fmt" + buildinfo "github.com/jfrog/build-info-go/entities" + gofrogcmd "github.com/jfrog/gofrog/io" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-client-go/artifactory/services" + specutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + "github.com/jfrog/jfrog-client-go/utils/io/content" + "github.com/jfrog/jfrog-client-go/utils/log" + "strings" +) + +type npmPublish struct { + *NpmPublishCommand +} + +func (npu *npmPublish) upload() (err error) { + for _, packedFilePath := range npu.packedFilePaths { + if err = npu.readPackageInfoFromTarball(packedFilePath); err != nil { + return err + } + var repoConfig, targetRepo string + var targetServer *config.ServerDetails + repoConfig, err = npu.getRepoConfig() + if err != nil { + return err + } + targetRepo, err = extractRepoName(repoConfig) + if err != nil { + return err + } + targetServer, err = extractConfigServer(repoConfig) + if err != nil { + return err + } + target := fmt.Sprintf("%s/%s", targetRepo, npu.packageInfo.GetDeployPath()) + + // If requested, perform a Xray binary scan before deployment. If a FailBuildError is returned, skip the deployment. + if npu.xrayScan { + if err = performXrayScan(packedFilePath, npu.repo, targetServer, npu.scanOutputFormat); err != nil { + return + } + } + err = errors.Join(err, npu.publishPackage(npu.executablePath, packedFilePath, targetServer, target)) + } + return +} + +func (npu *npmPublish) getBuildArtifacts() ([]buildinfo.Artifact, error) { + return utils.ConvertArtifactsSearchDetailsToBuildInfoArtifacts(npu.artifactsDetailsReader) +} + +func (npu *npmPublish) publishPackage(executablePath, filePath string, serverDetails *config.ServerDetails, target string) error { + npmCommand := gofrogcmd.NewCommand(executablePath, "publish", []string{filePath}) + output, cmdError, _, err := gofrogcmd.RunCmdWithOutputParser(npmCommand, true) + if err != nil { + log.Error("Error occurred while running npm publish: ", output, cmdError, err) + npu.result.SetFailCount(npu.result.FailCount() + 1) + return err + } + npu.result.SetSuccessCount(npu.result.SuccessCount() + 1) + servicesManager, err := utils.CreateServiceManager(serverDetails, -1, 0, false) + if err != nil { + return err + } + + if npu.collectBuildInfo { + var buildProps string + var searchReader *content.ContentReader + + buildProps, err = npu.getBuildPropsForArtifact() + if err != nil { + return err + } + searchParams := services.SearchParams{ + CommonParams: &specutils.CommonParams{ + Pattern: target, + }, + } + searchReader, err = servicesManager.SearchFiles(searchParams) + if err != nil { + log.Error("Failed to get uploaded npm package: ", err.Error()) + return err + } + + propsParams := services.PropsParams{ + Reader: searchReader, + Props: buildProps, + } + _, err = servicesManager.SetProps(propsParams) + if err != nil { + log.Warn("Unable to set build properties: ", err, "\nThis may cause build to not properly link with artifact, please add build name and build number properties on the tarball artifact manually") + } + npu.artifactsDetailsReader = searchReader + } + return nil +} + +func (npu *NpmPublishCommand) getRepoConfig() (string, error) { + var registryString string + scope := npu.packageInfo.Scope + if scope == "" { + registryString = "registry" + } else { + registryString = scope + ":registry" + } + configCommand := gofrogcmd.Command{ + Executable: npu.executablePath, + CmdName: "config", + CmdArgs: []string{"get", registryString}, + } + data, err := configCommand.RunWithOutput() + repoConfig := string(data) + if err != nil { + log.Error("Error occurred while running npm config get: ", err) + npu.result.SetFailCount(npu.result.FailCount() + 1) + return "", err + } + return repoConfig, nil +} + +func extractRepoName(configUrl string) (string, error) { + url := strings.TrimSpace(configUrl) + url = strings.TrimPrefix(url, "https://") + url = strings.TrimPrefix(url, "http://") + url = strings.TrimSuffix(url, "/") + if url == "" { + return "", errors.New("npm config URL is empty") + } + urlParts := strings.Split(url, "/") + if len(urlParts) < 2 { + return "", errors.New("npm config URL is not valid") + } + return urlParts[len(urlParts)-1], nil +} + +func extractConfigServer(configUrl string) (*config.ServerDetails, error) { + var requiredServerDetails = &config.ServerDetails{} + url := strings.TrimSpace(configUrl) + allAvailableConfigs, err := config.GetAllServersConfigs() + if err != nil { + return requiredServerDetails, err + } + + for _, availableConfig := range allAvailableConfigs { + if strings.HasPrefix(url, availableConfig.ArtifactoryUrl) { + requiredServerDetails = availableConfig + } + } + + if requiredServerDetails == nil { + return requiredServerDetails, fmt.Errorf("no server details found for the URL: %s to create build info", url) + } + + return requiredServerDetails, nil +} diff --git a/artifactory/commands/npm/npmpublish_test.go b/artifactory/commands/npm/npmpublish_test.go new file mode 100644 index 00000000..df31db25 --- /dev/null +++ b/artifactory/commands/npm/npmpublish_test.go @@ -0,0 +1,59 @@ +// Unit test for extractRepoName function +package npm + +import ( + "testing" +) + +func TestExtractRepoName(t *testing.T) { + tests := []struct { + name string + input string + expected string + expectError bool + }{ + { + name: "Valid URL", + input: "https://example.com/artifactory/repo-name", + expected: "repo-name", + expectError: false, + }, + { + name: "Valid URL", + input: "example.com/artifactory/repo-name", + expected: "repo-name", + expectError: false, + }, + { + name: "Empty URL", + input: "", + expected: "", + expectError: true, + }, + { + name: "Invalid URL with no parts", + input: "https://", + expected: "", + expectError: true, + }, + { + name: "URL with trailing slash", + input: "https://example.com/artifactory/repo-name/", + expected: "repo-name", + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := extractRepoName(tt.input) + if (err != nil) != tt.expectError { + t.Errorf("extractRepoName(%q) error = %v, expectError %v", tt.input, err, tt.expectError) + return + } + if !tt.expectError && result != tt.expected { + t.Errorf("extractRepoName(%q) = %q; want %q", tt.input, result, tt.expected) + } + }) + } +} diff --git a/artifactory/commands/npm/publish.go b/artifactory/commands/npm/publish.go index 8da35cbd..c738fe13 100644 --- a/artifactory/commands/npm/publish.go +++ b/artifactory/commands/npm/publish.go @@ -4,10 +4,9 @@ import ( "archive/tar" "compress/gzip" "errors" - "fmt" "github.com/jfrog/build-info-go/build" biutils "github.com/jfrog/build-info-go/build/utils" - ioutils "github.com/jfrog/gofrog/io" + gofrogcmd "github.com/jfrog/gofrog/io" "github.com/jfrog/gofrog/version" commandsutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" @@ -15,12 +14,8 @@ import ( buildUtils "github.com/jfrog/jfrog-cli-core/v2/common/build" "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/common/project" - "github.com/jfrog/jfrog-cli-core/v2/common/spec" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-client-go/artifactory" - "github.com/jfrog/jfrog-client-go/artifactory/services" - specutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" clientutils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/io/content" @@ -125,6 +120,10 @@ func (npc *NpmPublishCommand) Init() error { if err != nil { return err } + filteredNpmArgs, useNative, err := coreutils.ExtractUseNativeFromArgs(filteredNpmArgs) + if err != nil { + return err + } filteredNpmArgs, tag, err := coreutils.ExtractTagFromArgs(filteredNpmArgs) if err != nil { return err @@ -146,7 +145,7 @@ func (npc *NpmPublishCommand) Init() error { } npc.SetBuildConfiguration(buildConfiguration).SetRepo(deployerParams.TargetRepo()).SetNpmArgs(filteredNpmArgs).SetServerDetails(rtDetails) } - npc.SetDetailedSummary(detailedSummary).SetXrayScan(xrayScan).SetScanOutputFormat(scanOutputFormat).SetDistTag(tag) + npc.SetDetailedSummary(detailedSummary).SetXrayScan(xrayScan).SetScanOutputFormat(scanOutputFormat).SetDistTag(tag).SetUseNative(useNative) return nil } @@ -182,7 +181,10 @@ func (npc *NpmPublishCommand) Run() (err error) { } } - if err = npc.publish(); err != nil { + publishStrategy := NewNpmPublishStrategy(npc.UseNative(), npc) + + err = publishStrategy.Publish() + if err != nil { if npc.tarballProvided { return err } @@ -208,11 +210,12 @@ func (npc *NpmPublishCommand) Run() (err error) { if npc.buildConfiguration.GetModule() != "" { npmModule.SetName(npc.buildConfiguration.GetModule()) } - buildArtifacts, err := specutils.ConvertArtifactsDetailsToBuildInfoArtifacts(npc.artifactsDetailsReader) + + buildArtifacts, err := publishStrategy.GetBuildArtifacts() if err != nil { return err } - defer ioutils.Close(npc.artifactsDetailsReader, &err) + defer gofrogcmd.Close(npc.artifactsDetailsReader, &err) err = npmModule.AddArtifacts(buildArtifacts...) if err != nil { return errorutils.CheckError(err) @@ -292,131 +295,6 @@ func (npc *NpmPublishCommand) getTarballDir() (string, error) { return dest, nil } -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 -} - -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: packedFilePath, Target: target} - if err = npc.addDistTagIfSet(up.CommonParams); err != nil { - return err - } - var totalFailed int - if npc.collectBuildInfo || npc.detailedSummary { - if npc.collectBuildInfo { - buildName, err := npc.buildConfiguration.GetBuildName() - if err != nil { - return err - } - buildNumber, err := npc.buildConfiguration.GetBuildNumber() - if err != nil { - return err - } - err = buildUtils.SaveBuildGeneralDetails(buildName, buildNumber, npc.buildConfiguration.GetProject()) - if err != nil { - return err - } - up.BuildProps, err = buildUtils.CreateBuildProperties(buildName, buildNumber, npc.buildConfiguration.GetProject()) - if err != nil { - return err - } - } - summary, err := servicesManager.UploadFilesWithSummary(artifactory.UploadServiceOptions{}, up) - if err != nil { - return err - } - totalFailed = summary.TotalFailed - if npc.collectBuildInfo { - npc.artifactsDetailsReader = summary.ArtifactsDetailsReader - } else { - err = summary.ArtifactsDetailsReader.Close() - if err != nil { - return err - } - } - if npc.detailedSummary { - if err = npc.setDetailedSummary(summary); err != nil { - return err - } - } else { - if err = summary.TransferDetailsReader.Close(); err != nil { - return err - } - } - } else { - _, totalFailed, err = servicesManager.UploadFiles(artifactory.UploadServiceOptions{}, up) - if err != nil { - return err - } - } - - // We are deploying only one Artifact which have to be deployed, in case of failure we should fail - if totalFailed > 0 { - return errorutils.CheckErrorf("Failed to upload the npm package to Artifactory. See Artifactory logs for more details.") - } - return nil -} - -// Set the dist tag property to the package if required by the --tag option. -func (npc *NpmPublishCommand) addDistTagIfSet(params *specutils.CommonParams) error { - if npc.distTag == "" { - return nil - } - props, err := specutils.ParseProperties(DistTagPropKey + "=" + npc.distTag) - if err != nil { - return err - } - params.TargetProps = props - 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.") @@ -495,3 +373,19 @@ func deleteCreatedTarball(packedFilesPath []string) error { } return nil } + +func (npc *NpmPublishCommand) getBuildPropsForArtifact() (string, error) { + buildName, err := npc.buildConfiguration.GetBuildName() + if err != nil { + return "", err + } + buildNumber, err := npc.buildConfiguration.GetBuildNumber() + if err != nil { + return "", err + } + err = buildUtils.SaveBuildGeneralDetails(buildName, buildNumber, npc.buildConfiguration.GetProject()) + if err != nil { + return "", err + } + return buildUtils.CreateBuildProperties(buildName, buildNumber, npc.buildConfiguration.GetProject()) +} diff --git a/artifactory/commands/npm/publishstrategy.go b/artifactory/commands/npm/publishstrategy.go new file mode 100644 index 00000000..9f02cb34 --- /dev/null +++ b/artifactory/commands/npm/publishstrategy.go @@ -0,0 +1,54 @@ +package npm + +import ( + buildinfo "github.com/jfrog/build-info-go/entities" + commandsutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" + "github.com/jfrog/jfrog-cli-core/v2/common/format" + "github.com/jfrog/jfrog-cli-core/v2/common/spec" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-client-go/utils/log" +) + +type Publisher interface { + upload() error + getBuildArtifacts() ([]buildinfo.Artifact, error) +} + +type NpmPublishStrategy struct { + strategy Publisher + strategyName string +} + +// Get npm implementation +func NewNpmPublishStrategy(shouldUseNpmRc bool, npmPublishCommand *NpmPublishCommand) *NpmPublishStrategy { + nps := NpmPublishStrategy{} + if shouldUseNpmRc { + nps.strategy = &npmPublish{npmPublishCommand} + nps.strategyName = "native" + } else { + nps.strategy = &npmRtUpload{npmPublishCommand} + nps.strategyName = "artifactory" + } + return &nps +} + +func (nps *NpmPublishStrategy) Publish() error { + log.Debug("Using strategy for publish: ", nps.strategyName) + return nps.strategy.upload() +} + +func (nps *NpmPublishStrategy) GetBuildArtifacts() ([]buildinfo.Artifact, error) { + log.Debug("Using strategy for build info: ", nps.strategyName) + return nps.strategy.getBuildArtifacts() +} + +func performXrayScan(filePath string, repo string, serverDetails *config.ServerDetails, scanOutputFormat format.OutputFormat) error { + fileSpec := spec.NewBuilder(). + Pattern(filePath). + Target(repo + "/"). + BuildSpec() + if err := commandsutils.ConditionalUploadScanFunc(serverDetails, fileSpec, 1, scanOutputFormat); err != nil { + return err + } + return nil +} diff --git a/cliutils/flagkit/flags.go b/cliutils/flagkit/flags.go index b9809e99..7b3c1bcd 100644 --- a/cliutils/flagkit/flags.go +++ b/cliutils/flagkit/flags.go @@ -341,6 +341,7 @@ const ( // Unique npm flags npmPrefix = "npm-" npmDetailedSummary = npmPrefix + detailedSummary + runNative = "run-native" // Unique nuget/dotnet config flags nugetV2 = "nuget-v2" @@ -663,10 +664,10 @@ var commandFlags = map[string][]string{ global, serverIdResolve, serverIdDeploy, repoResolve, repoDeploy, }, NpmInstallCi: { - BuildName, BuildNumber, module, Project, + BuildName, BuildNumber, module, Project, runNative, }, NpmPublish: { - BuildName, BuildNumber, module, Project, npmDetailedSummary, xrayScan, xrOutput, + BuildName, BuildNumber, module, Project, npmDetailedSummary, xrayScan, xrOutput, runNative, }, PnpmConfig: { global, serverIdResolve, repoResolve, @@ -807,6 +808,7 @@ var flagsMap = map[string]components.Flag{ bundle: components.NewStringFlag(bundle, "[Optional] If specified, only artifacts of the specified bundle are matched. The value format is bundle-name/bundle-version.", components.SetMandatoryFalse()), imageFile: components.NewStringFlag(imageFile, "[Mandatory] Path to a file which includes one line in the following format: @sha256:.", components.SetMandatoryTrue()), ocStartBuildRepo: components.NewStringFlag(repo, "[Mandatory] The name of the repository to which the image was pushed.", components.SetMandatoryTrue()), + runNative: components.NewBoolFlag(runNative, "[Default: false] Set to true if you'd like to use the native client configurations. Note: This flag would invoke native client behind the scenes, has performance implications and does not support deployment view and detailed summary.", components.WithBoolDefaultValueFalse()), // Config specific commands flags interactive: components.NewBoolFlag(interactive, "[Default: true, unless $CI is true] Set to false if you do not want the config command to be interactive. If true, the --url option becomes optional.", components.WithBoolDefaultValueFalse()), diff --git a/go.mod b/go.mod index 320ace09..394a310f 100644 --- a/go.mod +++ b/go.mod @@ -132,7 +132,7 @@ require ( sigs.k8s.io/yaml v1.4.0 // indirect ) -replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20250508140039-bfb5e9a50dfe +replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20250514055103-d3d0d25f7c85 replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20250508130334-f159cff9b11a diff --git a/go.sum b/go.sum index 411ff952..c831c581 100644 --- a/go.sum +++ b/go.sum @@ -137,8 +137,8 @@ github.com/jfrog/froggit-go v1.17.0 h1:20Ie787WO27SwB2MOHDvsR6yN7fA5WfRnuAbmUqz1 github.com/jfrog/froggit-go v1.17.0/go.mod h1:HvDkfFfJwIdsXFdqaB+utvD2cLDRmaC3kF8otYb6Chw= github.com/jfrog/gofrog v1.7.6 h1:QmfAiRzVyaI7JYGsB7cxfAJePAZTzFz0gRWZSE27c6s= github.com/jfrog/gofrog v1.7.6/go.mod h1:ntr1txqNOZtHplmaNd7rS4f8jpA5Apx8em70oYEe7+4= -github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20250508140039-bfb5e9a50dfe h1:17ZlsX/X4orRHqFoyGnEBHIfKFu49AVvyVoRqk7635E= -github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20250508140039-bfb5e9a50dfe/go.mod h1:8ODu50AZkrP5xXvUGLizFTL1+qJknSShHNGhGl7OtFQ= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20250514055103-d3d0d25f7c85 h1:8MP9xgYm9bT6LeznzhyrecjpfTZQr2pBa4hxXEV36CI= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20250514055103-d3d0d25f7c85/go.mod h1:8ODu50AZkrP5xXvUGLizFTL1+qJknSShHNGhGl7OtFQ= github.com/jfrog/jfrog-client-go v1.28.1-0.20250508130334-f159cff9b11a h1:QkebmsUWuurSgp4Lx7k6GtQ0sX3RmJ/d2n+NQ2l2g/E= github.com/jfrog/jfrog-client-go v1.28.1-0.20250508130334-f159cff9b11a/go.mod h1:uRmT8Q1SJymIzId01v0W1o8mGqrRfrwUF53CgEMsH0U= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=