Skip to content

Commit c219c88

Browse files
authored
Merge branch 'master' into feature/tharing/server-id-for-login-command
2 parents ef34170 + e701744 commit c219c88

23 files changed

Lines changed: 1660 additions & 295 deletions

File tree

.github/workflows/pnpmTests.yml

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
name: pnpm Tests
2+
on:
3+
workflow_dispatch:
4+
push:
5+
branches:
6+
- "master"
7+
pull_request_target:
8+
types: [labeled]
9+
branches:
10+
- "master"
11+
12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}-${{ github.ref }}
14+
cancel-in-progress: true
15+
16+
jobs:
17+
pnpm-Tests:
18+
name: "pnpm 10 tests (${{ matrix.os.name }})"
19+
if: github.event_name == 'workflow_dispatch' || github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'safe to test')
20+
strategy:
21+
fail-fast: false
22+
matrix:
23+
os:
24+
- name: ubuntu
25+
version: 24.04
26+
- name: windows
27+
version: 2022
28+
- name: macos
29+
version: 14
30+
runs-on: ${{ matrix.os.name }}-${{ matrix.os.version }}
31+
steps:
32+
- name: Skip macOS - JGC-413
33+
if: matrix.os.name == 'macos'
34+
run: |
35+
echo "::warning::JGC-413 - Skip until artifactory bootstrap in osx is fixed"
36+
exit 0
37+
38+
- name: Checkout code
39+
if: matrix.os.name != 'macos'
40+
uses: actions/checkout@v5
41+
with:
42+
ref: ${{ github.event.pull_request.head.sha || github.ref }}
43+
44+
- name: Setup FastCI
45+
if: matrix.os.name != 'macos'
46+
uses: jfrog-fastci/fastci@v0
47+
with:
48+
github_token: ${{ secrets.GITHUB_TOKEN }}
49+
fastci_otel_token: ${{ secrets.FASTCI_TOKEN }}
50+
51+
- name: Install Node.js
52+
if: matrix.os.name != 'macos'
53+
uses: actions/setup-node@v5
54+
with:
55+
node-version: "20"
56+
57+
- name: Install pnpm 10
58+
if: matrix.os.name != 'macos'
59+
run: npm install -g pnpm@10
60+
61+
- name: Validate pnpm version is 10
62+
if: matrix.os.name != 'macos'
63+
shell: bash
64+
run: |
65+
PNPM_VER=$(pnpm --version)
66+
PNPM_MAJOR=$(echo "$PNPM_VER" | cut -d. -f1)
67+
echo "pnpm version: ${PNPM_VER}"
68+
if [ "$PNPM_MAJOR" -ne 10 ]; then
69+
echo "::error::Expected pnpm 10.x but got ${PNPM_VER}"
70+
exit 1
71+
fi
72+
echo "pnpm ${PNPM_VER} OK"
73+
74+
- name: Setup Go with cache
75+
if: matrix.os.name != 'macos'
76+
uses: jfrog/.github/actions/install-go-with-cache@main
77+
78+
- name: Install local Artifactory
79+
if: matrix.os.name != 'macos'
80+
uses: jfrog/.github/actions/install-local-artifactory@main
81+
with:
82+
RTLIC: ${{ secrets.RTLIC }}
83+
RT_CONNECTION_TIMEOUT_SECONDS: ${{ env.RT_CONNECTION_TIMEOUT_SECONDS || '1200' }}
84+
85+
- name: Run pnpm tests
86+
if: matrix.os.name != 'macos'
87+
env:
88+
YARN_IGNORE_NODE: 1
89+
run: go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.pnpm

.github/workflows/pythonTests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ jobs:
5959

6060
- name: Setup Pipenv
6161
if: ${{ matrix.suite == 'pipenv' && matrix.os.name != 'macos' }}
62-
run: python -m pip install pipenv
62+
run: python -m pip install pipenv==2026.2.2
6363

6464
- name: Setup Twine
6565
if: ${{ matrix.suite == 'pip' && matrix.os.name != 'macos' }}

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,8 @@ node_modules
3535

3636
# Class files
3737
*.class
38-
**/testdata/**/bin
38+
**/testdata/**/bin
39+
testdata/**/.gradle
40+
41+
# AI agents
42+
.cursor

buildtools/cli.go

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
huggingfaceCommands "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/huggingface"
3333
"github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/mvn"
3434
"github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/npm"
35+
"github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/pnpm"
3536
containerutils "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/ocicontainer"
3637
"github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/terraform"
3738
"github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/yarn"
@@ -67,6 +68,7 @@ import (
6768
"github.com/jfrog/jfrog-cli/docs/buildtools/mvnconfig"
6869
"github.com/jfrog/jfrog-cli/docs/buildtools/npmcommand"
6970
"github.com/jfrog/jfrog-cli/docs/buildtools/npmconfig"
71+
"github.com/jfrog/jfrog-cli/docs/buildtools/pnpmcommand"
7072
nugetdocs "github.com/jfrog/jfrog-cli/docs/buildtools/nuget"
7173
"github.com/jfrog/jfrog-cli/docs/buildtools/nugetconfig"
7274
"github.com/jfrog/jfrog-cli/docs/buildtools/pipconfig"
@@ -436,6 +438,17 @@ func GetCommands() []cli.Command {
436438
return cliutils.CreateConfigCmd(c, project.Pnpm)
437439
},
438440
},
441+
{
442+
Name: "pnpm",
443+
Flags: cliutils.GetCommandFlags(cliutils.Pnpm),
444+
Usage: pnpmcommand.GetDescription(),
445+
HelpName: corecommon.CreateUsage("pnpm", pnpmcommand.GetDescription(), pnpmcommand.Usage),
446+
UsageText: pnpmcommand.GetArguments(),
447+
SkipFlagParsing: true,
448+
BashComplete: corecommon.CreateBashCompletionFunc("install", "i", "publish", "p"),
449+
Category: buildToolsCategory,
450+
Action: pnpmCmd,
451+
},
439452
{
440453
Name: "docker",
441454
Flags: cliutils.GetCommandFlags(cliutils.Docker),
@@ -490,7 +503,7 @@ func GetCommands() []cli.Command {
490503
Aliases: []string{"hf"},
491504
HelpName: corecommon.CreateUsage("hugging-face", huggingface.GetDescription(), huggingface.Usage),
492505
Description: huggingface.GetDescription(),
493-
Hidden: true,
506+
Hidden: false,
494507
Category: buildToolsCategory,
495508
Action: func(c *cli.Context) error {
496509
if c.Args().Present() {
@@ -799,6 +812,64 @@ func YarnCmd(c *cli.Context) error {
799812
return commands.Exec(yarnCmd)
800813
}
801814

815+
func pnpmCmd(c *cli.Context) error {
816+
if show, err := cliutils.ShowCmdHelpIfNeeded(c, c.Args()); show || err != nil {
817+
return err
818+
}
819+
if c.NArg() < 1 {
820+
return cliutils.WrongNumberOfArgumentsHandler(c)
821+
}
822+
args := cliutils.ExtractCommand(c)
823+
cmdName, filteredArgs := getCommandName(args)
824+
825+
// Extract JFrog-specific flags (--build-name, --build-number, --project, --module, --server-id)
826+
// once, so both supported commands and native pass-through use cleaned args.
827+
serverDetails, cleanArgs, buildConfiguration, err := extractPnpmOptionsFromArgs(filteredArgs)
828+
if err != nil {
829+
return err
830+
}
831+
832+
switch cmdName {
833+
case "install", "i", "publish":
834+
pnpmCommand, err := pnpm.NewCommand(cmdName, cleanArgs, buildConfiguration, serverDetails)
835+
if err != nil {
836+
return err
837+
}
838+
return commands.Exec(pnpmCommand)
839+
default:
840+
return runNativePackageManagerCmd("pnpm", append([]string{cmdName}, cleanArgs...))
841+
}
842+
}
843+
844+
// runNativePackageManagerCmd runs a package manager command directly, passing through stdio.
845+
func runNativePackageManagerCmd(binary string, args []string) error {
846+
cmd := exec.Command(binary, args...)
847+
cmd.Stdin = os.Stdin
848+
cmd.Stdout = os.Stdout
849+
cmd.Stderr = os.Stderr
850+
return cmd.Run()
851+
}
852+
853+
// extractPnpmOptionsFromArgs extracts all JFrog CLI options from pnpm command args.
854+
// Returns server details, cleaned args (with JFrog flags removed), and build configuration.
855+
func extractPnpmOptionsFromArgs(args []string) (serverDetails *coreConfig.ServerDetails, cleanArgs []string, buildConfig *build.BuildConfiguration, err error) {
856+
cleanArgs = append([]string(nil), args...)
857+
var serverID string
858+
cleanArgs, serverID, err = coreutils.ExtractServerIdFromCommand(cleanArgs)
859+
if err != nil {
860+
return nil, nil, nil, fmt.Errorf("failed to extract server ID: %w", err)
861+
}
862+
serverDetails, err = coreConfig.GetSpecificConfig(serverID, true, true)
863+
if err != nil {
864+
return nil, nil, nil, fmt.Errorf("failed to get server configuration for ID '%s': %w", serverID, err)
865+
}
866+
cleanArgs, buildConfig, err = build.ExtractBuildDetailsFromArgs(cleanArgs)
867+
if err != nil {
868+
return nil, nil, nil, err
869+
}
870+
return serverDetails, cleanArgs, buildConfig, nil
871+
}
872+
802873
func NugetCmd(c *cli.Context) error {
803874
if show, err := cliutils.ShowCmdHelpIfNeeded(c, c.Args()); show || err != nil {
804875
return err
@@ -1070,7 +1141,7 @@ func buildCmd(c *cli.Context) error {
10701141
}
10711142

10721143
// Extract build configuration and arguments
1073-
_, rtDetails, _, _, _, cleanArgs, buildConfiguration, err := extractDockerOptionsFromArgs(c.Args())
1144+
_, rtDetails, _, skipLogin, _, cleanArgs, buildConfiguration, err := extractDockerOptionsFromArgs(c.Args())
10741145
if err != nil {
10751146
return err
10761147
}
@@ -1079,10 +1150,11 @@ func buildCmd(c *cli.Context) error {
10791150
return err
10801151
}
10811152

1082-
// Login to the docker registry
1083-
err = loginCmd(c)
1084-
if err != nil {
1085-
return err
1153+
if !skipLogin {
1154+
err = loginCmd(c)
1155+
if err != nil {
1156+
return err
1157+
}
10861158
}
10871159

10881160
dockerOptions := strategies.DockerBuildOptions{
@@ -1852,12 +1924,8 @@ func pythonCmd(c *cli.Context, projectType project.ProjectType) error {
18521924
log.Info(fmt.Sprintf("Publishing to repository: %s (from --repository flag)", deployerRepo))
18531925
}
18541926

1855-
// Execute native poetry command directly (similar to Maven FlexPack)
18561927
log.Info(fmt.Sprintf("Running Poetry %s.", cmdName))
1857-
poetryCmd := exec.Command("poetry", append([]string{cmdName}, poetryArgs...)...)
1858-
poetryCmd.Stdout = os.Stdout
1859-
poetryCmd.Stderr = os.Stderr
1860-
if err := poetryCmd.Run(); err != nil {
1928+
if err := runNativePackageManagerCmd("poetry", append([]string{cmdName}, poetryArgs...)); err != nil {
18611929
return fmt.Errorf("poetry %s failed: %w", cmdName, err)
18621930
}
18631931

docker_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"testing"
1515
"time"
1616

17+
urfavecli "github.com/urfave/cli"
1718
"github.com/jfrog/jfrog-client-go/utils/log"
1819

1920
tests2 "github.com/jfrog/jfrog-cli-artifactory/utils/tests"
@@ -1278,6 +1279,48 @@ CMD ["echo", "Hello from buildx"]`, baseImage)
12781279
inttestutils.ContainerTestCleanup(t, serverDetails, artHttpDetails, imageNameOnly, buildName, tests.OciLocalRepo)
12791280
}
12801281

1282+
// TestDockerBuildxSkipLoginFails verifies that --skip-login actually skips login.
1283+
// After logging out, buildx --push with --skip-login should fail because no auth is available.
1284+
func TestDockerBuildxSkipLoginFails(t *testing.T) {
1285+
cleanup := initDockerBuildTest(t)
1286+
defer cleanup()
1287+
1288+
// Prevent urfave/cli from calling os.Exit on command errors
1289+
origOsExiter := urfavecli.OsExiter
1290+
urfavecli.OsExiter = func(code int) {}
1291+
defer func() { urfavecli.OsExiter = origOsExiter }()
1292+
1293+
registryHost := *tests.ContainerRegistry
1294+
if parsedURL, err := url.Parse(registryHost); err == nil && parsedURL.Host != "" {
1295+
registryHost = parsedURL.Host
1296+
}
1297+
imageName := path.Join(registryHost, tests.OciLocalRepo, "test-skip-login")
1298+
imageTag := imageName + ":v1"
1299+
1300+
workspace, err := filepath.Abs(tests.Out)
1301+
assert.NoError(t, err)
1302+
assert.NoError(t, fileutils.CreateDirIfNotExist(workspace))
1303+
1304+
baseImage := path.Join(registryHost, tests.OciRemoteRepo, "busybox:latest")
1305+
dockerfileContent := fmt.Sprintf(`FROM %s
1306+
CMD ["echo", "skip-login test"]`, baseImage)
1307+
dockerfilePath := filepath.Join(workspace, "Dockerfile")
1308+
assert.NoError(t, os.WriteFile(dockerfilePath, []byte(dockerfileContent), 0644)) //#nosec G703 -- test code
1309+
1310+
// Logout from registry so push requires fresh login
1311+
logoutCmd := exec.Command("docker", "logout", registryHost)
1312+
assert.NoError(t, logoutCmd.Run())
1313+
1314+
// With --skip-login, jf should NOT re-login, so push should fail
1315+
err = runJfrogCliWithoutAssertion("docker", "buildx", "build",
1316+
"--platform", "linux/amd64",
1317+
"-t", imageTag, "-f", dockerfilePath, "--push", "--skip-login", workspace)
1318+
assert.Error(t, err, "Expected failure: --skip-login should prevent auto-login, causing push to fail without auth")
1319+
1320+
// Re-login for subsequent tests
1321+
runJfrogCli(t, "docker", "login", registryHost)
1322+
}
1323+
12811324
// TestDockerBuildWithVirtualRepo tests docker build with virtual repository
12821325
func TestDockerBuildWithVirtualRepo(t *testing.T) {
12831326
cleanup := initDockerBuildTest(t)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package pnpmcommand
2+
3+
var Usage = []string{"pnpm <pnpm arguments> [command options]"}
4+
5+
func GetDescription() string {
6+
return "Run pnpm command."
7+
}
8+
9+
func GetArguments() string {
10+
return ` install, i Run pnpm install.
11+
publish Packs and deploys the pnpm package to the designated npm repository.
12+
help, h`
13+
}

0 commit comments

Comments
 (0)