Feature/xray 144147 onboard yarn v4#778
Conversation
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>
bd9988b to
0e05c1e
Compare
0e05c1e to
fd5de28
Compare
c5097ad to
ef1823e
Compare
ef1823e to
e29283f
Compare
ab03730 to
d07e8b3
Compare
d07e8b3 to
d615404
Compare
d615404 to
9fd40e0
Compare
9fd40e0 to
8981284
Compare
8981284 to
79d1ae5
Compare
79d1ae5 to
d9e1352
Compare
…param refactor Co-authored-by: Cursor <cursoragent@cursor.com>
| // 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") |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
devbranch.go vet ./....go fmt ./....What
Onboards Yarn V4 (Berry, native
.yarnrc.ymlmode) tojf curation-audit(jf ca),and unifies Yarn V3 + V4 curation on a metadata-only lockfile resolution path.
Why / Design
jf camust build a completeyarn.lockto enumerate the dependency graph, withoutdownloading 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 stillfetches uncached tarballs to compute checksums, so a curation 403 on a blocked uncached
package aborts before
yarn.lockis written.yarn plugin import <url>: rejected — adds a runtime networkdependency, breaks air-gapped CI.
.yarnrc.ymlflags (enableNetwork: false, etc.): rejected — doesn't produce acomplete lockfile for uncached packages.
jfrog-yarn-resolve-lockfile.cjs,//go:embed).It calls
project.resolveEverything()+project.persistLockfile()— resolving the fullgraph 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
.yarnrc.yml(nojf yarn-configstep).is bumping
yarn.lockmtime so the next run can skip re-resolution.attachWorkspaceMembersToRootaudits every workspace member'sdependencies from the root, matching npm/pnpm.
--run-nativeis a no-op for Yarn V4 (always native) with a deferred warning.Tests
TestRegisterYarnPluginInYarnrc,TestAttachWorkspaceMembersToRoot(yarn package)TestPromoteYarnWorkspaceMemberincl. the$HOME-stop case (curation package)