Skip to content

Feature/xray 144147 onboard yarn v4#778

Open
gauriy-tech wants to merge 6 commits into
jfrog:devfrom
gauriy-tech:feature/XRAY-144147-onboard-yarn-v4
Open

Feature/xray 144147 onboard yarn v4#778
gauriy-tech wants to merge 6 commits into
jfrog:devfrom
gauriy-tech:feature/XRAY-144147-onboard-yarn-v4

Conversation

@gauriy-tech

@gauriy-tech gauriy-tech commented Jun 16, 2026

Copy link
Copy Markdown
Contributor
  • The pull request is targeting the dev branch.
  • The code has been validated to compile successfully by running go vet ./....
  • The code has been formatted properly using go fmt ./....
  • All static analysis checks passed.
  • All tests have passed. If this feature is not already covered by the tests, new tests have been added.
  • Updated the Contributing page / ReadMe page / CI Workflow files if needed.
  • All changes are detailed at the description. if not already covered at JFrog Documentation, new documentation have been added.

What

Onboards Yarn V4 (Berry, native .yarnrc.yml mode) to jf curation-audit (jf ca),
and unifies Yarn V3 + V4 curation on a metadata-only lockfile resolution path.

Why / Design

jf ca must build a complete yarn.lock to enumerate the dependency graph, without
downloading tarballs (curation blocks blocked tarballs with a 403, which aborts a normal
install before the lockfile is written).
Approaches considered:

  • yarn install --mode=update-lockfile (previous V3 behavior): rejected — it still
    fetches uncached tarballs to compute checksums, so a curation 403 on a blocked uncached
    package aborts before yarn.lock is written.
  • Hosted plugin via yarn plugin import <url>: rejected — adds a runtime network
    dependency, breaks air-gapped CI.
  • .yarnrc.yml flags (enableNetwork: false, etc.): rejected — doesn't produce a
    complete lockfile for uncached packages.
  • Chosen: embedded resolution-only plugin (jfrog-yarn-resolve-lockfile.cjs, //go:embed).
    It calls project.resolveEverything() + project.persistLockfile() — resolving the full
    graph from registry metadata only (no tarball fetch), so blocked packages never abort
    the lockfile. The plugin is written into the per-run temp dir, never the customer's project.
    V3 behavior change (intentional): V3 curation previously used --mode=update-lockfile;
    it now uses the same embedded plugin as V4. V3 had the identical tarball-fetch abort problem,
    so this gives V3 strictly better behavior and keeps V3/V4 on one code path.

Highlights

  • Yarn V4 native mode: registry + auth read from .yarnrc.yml (no jf yarn-config step).
  • Non-invasive: all resolution happens in a temp copy; the only touch to the original project
    is bumping yarn.lock mtime so the next run can skip re-resolution.
  • Full workspace coverage: attachWorkspaceMembersToRoot audits every workspace member's
    dependencies from the root, matching npm/pnpm.
  • --run-native is a no-op for Yarn V4 (always native) with a deferred warning.
  • V1 is rejected up front with an actionable message; V2 falls back to direct-dependency probing.

Tests

  • TestRegisterYarnPluginInYarnrc, TestAttachWorkspaceMembersToRoot (yarn package)
  • TestPromoteYarnWorkspaceMember incl. the $HOME-stop case (curation package)


V4 operates in native mode: registry URL and auth token come from
.yarnrc.yml (local or ~/.yarnrc.yml written by yarn config set --home)
instead of requiring jf yarn-config / yarn.yaml.

Key changes:

yarn.go
- Remove V4 rejection from verifyYarnVersionSupportedForCuration and
  configureYarnResolutionServerAndRunInstall (only V1 is now rejected).
- resolveCurationLockfileDir: copy project to temp dir before running
  install (mirrors pnpm), so the customer's checkout is never modified.
- For V4 curation: yarnCurationRegistry() rewrites api/npm/ →
  api/curation/audit/, then runYarnConfigSet sets a global npmAuthToken
  in the temp .yarnrc.yml so the rewritten URL authenticates correctly
  (the original token is scoped to api/npm/ and does not match the
  curation endpoint).
- GetNativeYarnV4RegistryConfig: reads npmRegistryServer via
  yarn config get; reads npmAuthToken via direct YAML parsing of
  .yarnrc.yml and ~/.yarnrc.yml (yarn config get is unreliable for
  nested keys).
- runYarnCommandQuiet: capture stdout+stderr on failure and emit as
  Debug log so failed-install diagnosis is visible.

curationaudit.go
- setRepoFromYarnrcForYarnV4: calls SetDepsRepo(repoName) in addition
  to setPackageManagerConfig. Both are required — PackageManagerConfig
  drives auth; SetDepsRepo populates params.DependenciesRepository so
  the curation endpoint URL is constructed in
  configureYarnResolutionServerAndRunInstall (was always "" before,
  causing the V4 branch to be skipped and the install to hit api/npm/
  instead of api/curation/audit/).
- resolveNpmYarnTech: detect V4 projects that have .yarnrc.yml (created
  by yarn set version 4) without a pre-existing yarn.lock, and projects
  using a global ~/.yarnrc.yml (--home setup); guard with
  package-lock.json absence to avoid npm misidentification.
- validateRunNativeForTech: accept --run-native for Yarn as a no-op
  (V4 is always native; flag has no effect but should not error).
- SetRepo: detect V4 via version check and route to
  setRepoFromYarnrcForYarnV4; make version-detection failures explicit
  errors instead of silent fallbacks.

Co-authored-by: Cursor <cursoragent@cursor.com>
@gauriy-tech gauriy-tech force-pushed the feature/XRAY-144147-onboard-yarn-v4 branch from ef1823e to e29283f Compare June 18, 2026 10:49
@gauriy-tech gauriy-tech force-pushed the feature/XRAY-144147-onboard-yarn-v4 branch from ab03730 to d07e8b3 Compare June 19, 2026 06:13
@gauriy-tech gauriy-tech force-pushed the feature/XRAY-144147-onboard-yarn-v4 branch from d07e8b3 to d615404 Compare June 19, 2026 07:07
@gauriy-tech gauriy-tech force-pushed the feature/XRAY-144147-onboard-yarn-v4 branch from d615404 to 9fd40e0 Compare June 19, 2026 11:53
@gauriy-tech gauriy-tech force-pushed the feature/XRAY-144147-onboard-yarn-v4 branch from 9fd40e0 to 8981284 Compare June 19, 2026 18:08
@gauriy-tech gauriy-tech force-pushed the feature/XRAY-144147-onboard-yarn-v4 branch from 8981284 to 79d1ae5 Compare June 19, 2026 18:45
@gauriy-tech gauriy-tech force-pushed the feature/XRAY-144147-onboard-yarn-v4 branch from 79d1ae5 to d9e1352 Compare June 19, 2026 19:31
// Yarn V4 always resolves natively from .yarnrc.yml — --run-native is redundant and has no effect.
// Deferred: emitted after the spinner stops so the message is not overwritten.
if ca.RunNative() && tech == techutils.Yarn {
ca.pendingWarnings = append(ca.pendingWarnings, "--run-native has no effect for yarn V4; yarn V4 always resolves natively from .yarnrc.yml")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The warning text is V4-specific ("yarn V4 always resolves natively from .yarnrc.yml") but the guard tech == techutils.Yarn fires for V2 and V3 too.

Before this PR: --run-native on any Yarn project was a hard error from validateRunNativeForTech — clear signal to the user.
After this PR: V2/V3 users passing --run-native get a silent no-op with a message that references V4 and .yarnrc.yml, neither of which applies to their project.

Consider a version-agnostic message so users on V2/V3 aren't confused:

"--run-native has no effect for yarn; yarn curation always resolves natively from the Artifactory repository"

Or gate the V4-specific message on the detected version — the version is available in SetRepo at this call path.

// os.UserHomeDir(); point it at an empty dir so a real one on the
// developer's machine can't leak in and flip "neither yaml" to yarn.
// HOME (unix) and USERPROFILE (windows) cover os.UserHomeDir on all OSes.
dummyHome := t.TempDir()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The six test cases here only exercise the GetProjectConfFilePath (yarn.yaml / npm.yaml) branches. The three new V4 native-mode code paths added in resolveNpmYarnTech have no positive test cases:

Path Code Missing test
Local .yarnrc.yml indicator curationaudit.go:590 npm + .yarnrc.yml in working dir → promoted
Global ~/.yarnrc.yml curationaudit.go:598-600 npm + .yarnrc.yml written to dummyHome → promoted
package-lock.json guard curationaudit.go:586-588 npm + yarn indicator + package-lock.json present → NOT promoted

The test infrastructure already sets up dummyHome and tempProjectDir — all three cases fit naturally into the existing table. For example, the global ~/.yarnrc.yml case just needs:

{
    name: "npm, global ~/.yarnrc.yml only — promoted to yarn (V4 native mode)",
    tech: techutils.Npm.String(),
    // write .yarnrc.yml to dummyHome in the test body
    want: techutils.Yarn.String(),
},

// curation endpoint returns 403 HTML the plugin can't parse) and enforce
// curation afterwards via the HEAD-walker.
func shouldRouteThroughCurationEndpoint(yarnVersion *version.Version, isCurationCmd bool) bool {
return isCurationCmd && yarnVersion.Compare(yarnV3Version) > 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This function has zero unit test coverage. It determines whether V2 Yarn routes through /api/curation/audit/ vs the plain repo — getting it wrong would either misroute V3/V4 through the curation endpoint (which returns 403 HTML the plugin can't parse) or leave V2 off the endpoint silently.

The function is pure and the gofrog Compare inversion (>0 means receiver is less) makes the direction non-obvious. A small table-driven test would lock in the contract:

func TestShouldRouteThroughCurationEndpoint(t *testing.T) {
    assert.True(t,  shouldRouteThroughCurationEndpoint(version.NewVersion("2.5.0"), true),  "V2 + curation → route through endpoint")
    assert.False(t, shouldRouteThroughCurationEndpoint(version.NewVersion("3.0.0"), true),  "V3 → skip endpoint")
    assert.False(t, shouldRouteThroughCurationEndpoint(version.NewVersion("4.0.0"), true),  "V4 → skip endpoint")
    assert.False(t, shouldRouteThroughCurationEndpoint(version.NewVersion("2.5.0"), false), "non-curation → skip regardless of version")
}

// resolveCurationLockfileDir prepares the directory from which the curation
// audit reads yarn.lock. When install is needed it copies the project to a
// temp dir, configures the curation registry there, and runs
// 'yarn install --mode=update-lockfile' — so the customer's project content is

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: the docstring says 'yarn install --mode=update-lockfile' but after this PR that's no longer what runs. V3/V4 curation now uses yarn jfrog-yarn-resolve-lockfile (the embedded plugin); V2 runs a standard yarn install that fails at 403.

Consider updating to reflect the current behavior:

// ...and runs 'yarn jfrog-yarn-resolve-lockfile' (V3/V4) or 'yarn install' (V2) —
// so the customer's project content is never modified and read-only CI checkouts still work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants