Conversation
…p-level jf commands Wire up the four native PowerShell PSResourceGet cmdlets as separate top-level jf commands (deliberately not a single `jf psresource <verb>` wrapper), mirroring the shipped `jf choco` FlexPack pattern from the unmerged RTECO-2003 branch: - Pin jfrog-cli-artifactory, jfrog-cli-core and build-info-go to the bhanurp fork's RTECO-2247 branch (go.mod replace directives) to pick up PSResourceFlexPackCommand, project.PSResource and the PSResource collectors. - buildtools/cli.go: add Install-PSResource, Save-PSResource, Update-PSResource and Publish-PSResource commands, backed by a shared psResourceCmd(cmdlet) helper, plus a jf setup psresource platform gate (ValidatePSResourcePlatform). - docs/buildtools/psresource: per-cmdlet usage/description/AI-description text. - utils/cliutils/commandsflags.go: PSResource flag set (mirrors Choco's). - docs/buildtools/setup/help.go: mention psresource's prerequisites/gotchas. - utils/tests: test.psresource flag and repo/build-name scaffolding. - psresource_test.go: integration tests that skip gracefully when pwsh + Microsoft.PowerShell.PSResourceGet aren't available, since PSResourceGet is cross-platform (unlike choco, which is Windows-only). - .github/workflows/psresourceTests.yml: cross-platform (ubuntu/macos/windows) CI job, wired into build-gate.yml. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace the four near-identical Install-/Save-/Update-/Publish-PSResource cli.Command literals in buildtools/cli.go with a table-driven psResourceCommandEntries() loop over the four native cmdlet names, and replace the 16 hand-duplicated per-verb functions/vars in docs/buildtools/psresource/help.go with a single templated Usage/GetDescription/GetArguments/GetAIDescription set parameterized by cmdlet name and a small per-cmdlet cmdletMeta map for the AI description's varying prose. Also avoids computing ResolveDescription twice per PSResource command (reused for both Usage and HelpName). This is a pure cleanup/dedup refactor - no behavior change. Verified all four commands' --help output (Name/Usage/Arguments/Options sections) is byte-identical before and after via a temporary before/after snapshot against the pre-refactor files, plus go build/vet, golangci-lint, and the PSResource-scoped tests in psresource_test.go all pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…review
- buildtools/cli.go: WrapCmdWithCurationPostFailureRun was called with
each cmdlet's own PascalCase name ("Install-PSResource" etc.) as
cmdName, but jfrog-cli-security's post-failure curation audit gates
on a fixed, generic verb allowlist ({install, build, i, add, ci,
get, mod}) shared across every package manager - none of our names
was ever in it, so the audit was a silent no-op for all four
commands. Install/Save/Update now pass the matching "install" verb;
Publish-PSResource (which uploads rather than resolves a package,
so curation cannot block it the way this audit checks for) now
runs directly, without a curation cmdName that would never apply.
- Re-pin the three in-flight fork dependencies (jfrog-cli-artifactory,
jfrog-cli-core, build-info-go) to their latest commits, and add an
explicit release-blocking comment in go.mod: these replace
directives point at a personal fork and must be removed once the
corresponding upstream PRs land - not something to "fix" by ripping
them out now, since this repo cannot build the in-flight PSResource
support without them yet.
- docs/buildtools/psresource/help_test.go: GetAIDescription's
per-cmdlet map lookup had zero test coverage across any of the four
cmdlets.
- psresource_test.go: the install build-info test made no assertion
about build-info actually being collected on success, and only
Install-PSResource's real dispatch logic (past the shared --help
early-return) was ever exercised by any test. Added the same
ValidateGeneratedBuildInfoModule assertion the equivalent NuGet test
uses, and added matching tests for Save-/Update-/Publish-PSResource.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
All three in-flight dependencies (jfrog-cli-artifactory, jfrog-cli-core, build-info-go) are now pushed directly to their jfrog org repos (RTECO-2247, or RTECO-2247-psresource for jfrog-cli-artifactory, which has a branch-naming rule requiring a suffix), so these replace directives no longer need to point at the bhanurp personal fork. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
# Conflicts: # go.mod # go.sum
shell := "" was always overwritten by one of the two LookPath branches before ever being read, tripping wastedassign in CI's Static Check. Uses exec.LookPath's own error return to choose the fallback instead of a pre-initialized variable. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
View full scan results in JFrog Platform📗 Scan Summary
|
at 🎯 Static Application Security Testing (SAST) VulnerabilityFull descriptionVulnerability Details
OverviewHardcoded credentials are usernames, passwords, API keys, or other secrets Vulnerable exampleIn this example, the database username and password for the frog pond are package main
import (
"database/sql"
"fmt"
"log"
_ "[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)"
)
func main() {
// VULNERABLE: Hardcoded database credentials for the frog pond.
frogUser := "pond_admin"
frogPassword := "LeapFlog123!"
pondName := "lilypad_db"
connStr := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
frogUser, frogPassword, pondName)
lilypadDB, err := sql.Open("mysql", connStr)
if err != nil {
log.Fatalf("Error opening database: %v", err)
}
defer lilypadDB.Close()
err = lilypadDB.Ping()
if err != nil {
log.Fatalf("Error pinging database: %v", err)
}
fmt.Println("Successfully connected to the frog pond.")
}RemediationThe remediated code retrieves the database credentials from environment package main
import (
"database/sql"
"fmt"
"log"
"os"
_ "[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)"
)
func main() {
// SECURE: Retrieve credentials from environment variables.
frogUser := os.Getenv("FROG_DB_USER")
frogPassword := os.Getenv("FROG_DB_PASS")
pondName := os.Getenv("FROG_DB_NAME")
if frogUser == "" || frogPassword == "" || pondName == "" {
log.Fatal("DB credentials are not set in environment variables.")
}
connStr := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
frogUser, frogPassword, pondName)
lilypadDB, err := sql.Open("mysql", connStr)
if err != nil {
log.Fatalf("Error opening database: %v", err)
}
defer lilypadDB.Close()
err = lilypadDB.Ping()
if err != nil {
log.Fatalf("Error pinging database: %v", err)
}
fmt.Println("Successfully connected to the frog pond.")
} |
at 🎯 Static Application Security Testing (SAST) VulnerabilityFull descriptionVulnerability Details
OverviewHardcoded credentials are usernames, passwords, API keys, or other secrets Vulnerable exampleIn this example, the database username and password for the frog pond are package main
import (
"database/sql"
"fmt"
"log"
_ "[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)"
)
func main() {
// VULNERABLE: Hardcoded database credentials for the frog pond.
frogUser := "pond_admin"
frogPassword := "LeapFlog123!"
pondName := "lilypad_db"
connStr := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
frogUser, frogPassword, pondName)
lilypadDB, err := sql.Open("mysql", connStr)
if err != nil {
log.Fatalf("Error opening database: %v", err)
}
defer lilypadDB.Close()
err = lilypadDB.Ping()
if err != nil {
log.Fatalf("Error pinging database: %v", err)
}
fmt.Println("Successfully connected to the frog pond.")
}RemediationThe remediated code retrieves the database credentials from environment package main
import (
"database/sql"
"fmt"
"log"
"os"
_ "[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)"
)
func main() {
// SECURE: Retrieve credentials from environment variables.
frogUser := os.Getenv("FROG_DB_USER")
frogPassword := os.Getenv("FROG_DB_PASS")
pondName := os.Getenv("FROG_DB_NAME")
if frogUser == "" || frogPassword == "" || pondName == "" {
log.Fatal("DB credentials are not set in environment variables.")
}
connStr := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
frogUser, frogPassword, pondName)
lilypadDB, err := sql.Open("mysql", connStr)
if err != nil {
log.Fatalf("Error opening database: %v", err)
}
defer lilypadDB.Close()
err = lilypadDB.Ping()
if err != nil {
log.Fatalf("Error pinging database: %v", err)
}
fmt.Println("Successfully connected to the frog pond.")
} |
at 🎯 Static Application Security Testing (SAST) VulnerabilityFull descriptionVulnerability Details
OverviewHardcoded credentials are usernames, passwords, API keys, or other secrets Vulnerable exampleIn this example, the database username and password for the frog pond are package main
import (
"database/sql"
"fmt"
"log"
_ "[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)"
)
func main() {
// VULNERABLE: Hardcoded database credentials for the frog pond.
frogUser := "pond_admin"
frogPassword := "LeapFlog123!"
pondName := "lilypad_db"
connStr := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
frogUser, frogPassword, pondName)
lilypadDB, err := sql.Open("mysql", connStr)
if err != nil {
log.Fatalf("Error opening database: %v", err)
}
defer lilypadDB.Close()
err = lilypadDB.Ping()
if err != nil {
log.Fatalf("Error pinging database: %v", err)
}
fmt.Println("Successfully connected to the frog pond.")
}RemediationThe remediated code retrieves the database credentials from environment package main
import (
"database/sql"
"fmt"
"log"
"os"
_ "[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)"
)
func main() {
// SECURE: Retrieve credentials from environment variables.
frogUser := os.Getenv("FROG_DB_USER")
frogPassword := os.Getenv("FROG_DB_PASS")
pondName := os.Getenv("FROG_DB_NAME")
if frogUser == "" || frogPassword == "" || pondName == "" {
log.Fatal("DB credentials are not set in environment variables.")
}
connStr := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
frogUser, frogPassword, pondName)
lilypadDB, err := sql.Open("mysql", connStr)
if err != nil {
log.Fatalf("Error opening database: %v", err)
}
defer lilypadDB.Close()
err = lilypadDB.Ping()
if err != nil {
log.Fatalf("Error pinging database: %v", err)
}
fmt.Println("Successfully connected to the frog pond.")
} |
Address PR review feedback: GitHub-hosted runners (ubuntu-latest, macos-latest, windows-latest) already ship PowerShell 7 as pwsh out of the box, so installing it via apt/curl/brew is unnecessary. Replace the install step with a simple verification step (pwsh -v) as suggested by the reviewer. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>


Summary
Part of RTECO-2247 — registers four top-level commands wrapping PowerShell's PSResourceGet module:
jf Install-PSResource,jf Save-PSResource,jf Update-PSResource,jf Publish-PSResource. Deliberately four separate commands (matching native PowerShell cmdlet names) rather than onejf psresource <verb>wrapper — unlikejf choco, which uses a single-command, first-positional-argument dispatch.Depends on (must merge first, or this repo's
go.modneeds re-pinning to released versions once they land):Note on
go.mod: this branch carriesreplacedirectives pinning all three dependencies above to their ownRTECO-2247(-psresource) branches (same canonical repos, not a fork) so this repo can build against the unreleased changes. Marked with an explicit release-blocking comment ingo.mod— these need to come out once the sibling PRs merge and real released versions can be pinned.What's here
buildtools/cli.go— the fourcli.Commandentries, built from a shared table (psResourceCommandEntries) rather than four hand-duplicated literals, all backed by onepsResourceCmd(cmdletName)helper.docs/buildtools/psresource/help.go— per-cmdlet help text, rendered from one shared template parameterized by a smallcmdletMetastruct (the four cmdlets' help is ~90% identical prose).utils/cliutils/commandsflags.go— the shared flag set (--build-name,--build-number,--module,--project,--repo,--repo-resolve,--server-id).psresource_test.go) — help text on every platform, CLI-level build-flag-pair validation, and (skipping gracefully without a localpwsh+ PSResourceGet install) end-to-end dependency/artifact build-info collection for all four commands.psresourceTests.yml) — cross-platform (Linux/macOS/Windows), unlikejf choco's Windows-only requirement, since PSResourceGet viapwshis cross-platform.Notable bug fix included (found via an adversarial multi-agent code review after the initial implementation)
WrapCmdWithCurationPostFailureRunwas called with each cmdlet's own PascalCase name ("Install-PSResource", etc.) ascmdName, but jfrog-cli-security's post-failure curation audit gates on a fixed, generic verb allowlist ({install, build, i, add, ci, get, mod}) shared across every package manager — none of our names was ever in it, so the audit was a silent no-op for all four commands. Install/Save/Update now pass the matching"install"verb; Publish (which uploads rather than resolves a package, so curation can't block it the way this audit checks for) runs directly without a curationcmdNamethat would never apply.Test plan
go test . -run PSResource -v -args -test.psresource=trueandgo test ./buildtools/... ./docs/buildtools/psresource/...— all pass (end-to-end tests skip gracefully on this machine, which has nopwshinstalled).New regression tests for the curation-cmdName fix,
GetAIDescription's per-cmdlet map lookup, and dispatch-reaching coverage for Save/Update/Publish (previously only Install-PSResource's real dispatch logic was ever exercised past the shared--helpearly-return).golangci-lintclean,gofmtclean.All tests passed. New tests added for the bug found during review.
All static analysis checks passed.
This pull request is on the master branch.
I used gofmt for formatting the code before submitting the pull request.
Full flow was locally tested (help text on every platform; end-to-end paths verified with
pwshunavailable, which is the graceful-skip path).🤖 Generated with Claude Code
Marked as draft: this repo's required
No-Replacecheck hard-fails while anyreplace github.com/jfrog/*directive is active ingo.mod(regardless of target) — and this branch needs one for each of build-info-go#430, jfrog-cli-core#1620, and jfrog-cli-artifactory#563 until they merge and this branch can pin real released versions. Will mark ready for review once those merge and the replace directives come out.