socket-patch supports yarn berry at cacheKey 10c0 — yarn 4 with the default
compressionLevel: 0, the one cache-zip checksum recipe it can reproduce
offline — in both modes: hosted (scan --mode hosted rewrites the lock entry
to the hosted ::__archiveUrl=) and vendored (vendor wires the root
package.json resolutions plus the lock's file: entry). yarn 2 and 3
(cacheKeys 7 / 8) are refused by both modes. The node-modules and pnpm
linkers are covered end to end; Plug'n'Play keeps packages inside
.yarn/cache zips, so vendor refuses it (vendor_yarn_berry_unsupported)
and so does apply (yarn_pnp_unsupported), while standalone vex still
attests a hosted lock's checksum: pin.
The yarn-berry-e2e job in .github/workflows/ci.yml runs
scripts/yarn-berry-vex-matrix.sh with SOCKET_PATCH_YARN_E2E_REQUIRED=1
(a toolchain or registry problem fails instead of skipping):
| OS | yarn |
|---|---|
| ubuntu-latest | 4.0.2 (bare-hex checksums), 4.1.0 (first 10c0/ spelling), 4.6.0, 4.12.0, 4.18.0 |
| macos-latest | 4.12.0 |
| windows-latest | 4.12.0 |
Each release drives four real-yarn suites — e2e_redirect_yarn_berry_build,
e2e_vendor_yarn_berry_build, e2e_yarn4_pnpm_linker_build and
e2e_yarn4_workspaces_build — each ending in the manifest-less VEX matrix of
tests/yarn_berry_common, plus e2e_yarn_legacy_cachekey_refusal_build
against yarn 2.4.3 and 3.8.7. mode_migration_npm covers both mode
takeovers against yarn 4.12.0. Hermetic twins of every shape (no toolchain)
live in in_process_redirect, in_process_vendor, e2e_vex_lockfile and the
core unit tests.
yarn berry keeps one line ending per file, chosen by the same function for
the lockfile and every manifest. At tag @yarnpkg/cli/4.12.0 (the same code
ships at 4.0.0 and 4.18.0; the published 3.8.7, 4.0.2, 4.6.0, 4.9.2 and
4.18.0 bundles carry it verbatim, and 2.4.3 applies the same rule through
changeFilePromise):
packages/yarnpkg-fslib/sources/FakeFS.ts(lines 799–812):getEndOfLine(content)returnsos.EOLwhencontenthas no line break — the file is new — and otherwise\r\nonly when CRLF breaks strictly outnumber LF ones (a tie is LF);normalizeLineEndings(original, next)respells every break ofnextthat way.packages/yarnpkg-core/sources/Project.ts:persistLockfile(lines 2014–2033) writes the generated lock throughnormalizeLineEndingsagainst the current file; the--immutablecheck (lines 1789–1858) fails with YN0028 whenevernormalizeLineEndings(initialLockfile, generateLockfile())differs from the file — so a uniformly CRLF lock passes, while a lock with mixed endings (or a BOM, which the re-render never writes) always fails.packages/yarnpkg-core/sources/Workspace.ts(lines 217–229):persistManifestwritesJSON.stringify(data, null, indent) + "\n"throughchangeFilePromise(…, {automaticNewlines: true}), the same rule, after every install (Project.tsline 1881,--immutableincluded).Manifest.loadFromTextstrips a BOM when reading (Manifest.tslines 135–146 and 984–990); the rewrite never writes one back.packages/yarnpkg-parsers/sources/syml.ts:parseSymlreads the lock with js-yaml, which accepts CRLF.
So on Windows every yarn berry project starts CRLF: the first yarn install
writes a CRLF yarn.lock, and a package.json yarn pretty-prints for the
first time (any compact one) comes back CRLF. On macOS and Linux both are LF.
An existing file keeps its majority ending on every OS. Git adds its own
path to CRLF: core.autocrlf=true (the Git for Windows installer's default)
checks LF-committed text out as CRLF, and so does core.autocrlf=true or a
text eol=crlf attribute on macOS and Linux; a lock committed with CRLF stays
CRLF in every checkout.
What socket-patch does with those files:
hosted (yarn.lock) |
vendored (yarn.lock + root package.json) |
|
|---|---|---|
| uniformly LF or CRLF | rewritten in the file's own ending; ledger fragments recorded as on disk | lock entry spliced in the file's ending; package.json re-serialized in its own layout (BOM, indent, ending, trailing newline) |
| leading BOM | kept | kept, both files |
| mixed CRLF / LF, or a bare CR | refused untouched: redirect_yarn_berry_mixed_line_endings |
refused before any write: vendor_yarn_berry_mixed_line_endings |
revert (rollback, remove, takeovers) |
byte-exact; a ledger recorded before a uniform LF ↔ CRLF checkout flip is replayed respelled; a mixed lock refuses as drift | byte-exact; a lock mixed after vendoring gets the restored entry in the terminator of the entry it replaces |
| mode takeover into this mode | the berry gates (line endings, cacheKey, compressionLevel) run BEFORE the vendored wiring is reverted; a refused purl stays vendored, byte-identical |
the backend's project gates (both files' line endings, cacheKey, compressionLevel) run BEFORE the hosted redirect is reverted; a refused purl stays hosted, byte-identical |
setup / setup --remove write package.json in the same layout-keeping
way (BOM, indent, ending, trailing newline), so the pair round-trips
byte-exactly on a CRLF manifest too.
Every reader — manifest-less vex, the lockfile inventory, the npm flavor
sniff, repair — splits CRLF lines like LF ones and skips a leading BOM.
The shared hosted golden fixtures stay LF: their TypeScript twin in the
depscan backend has no CRLF path yet.
SOCKET_PATCH_YARN_E2E_REQUIRED=1 SOCKET_PATCH_YARN_BERRY_VERSION=4.12.0 \
COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
cargo test -p socket-patch-cli \
--test e2e_redirect_yarn_berry_build --test e2e_vendor_yarn_berry_build \
--test e2e_yarn4_pnpm_linker_build --test e2e_yarn4_workspaces_build \
--test e2e_yarn_legacy_cachekey_refusal_build -- --nocaptureSOCKET_PATCH_YARN_BERRY_EOL=crlf runs the same flows on CRLF files on macOS
or Linux: right after each fixture's first yarn install, the files yarn
wrote are respelled CRLF — what yarn itself writes on Windows — and yarn keeps
them CRLF on every later write. Each fixture file prints one
BERRY-EOL|<yarn>|<flow>|<file>|yarn=<ending>|flow=<ending> line: the ending
yarn wrote (lf on macOS and Linux, crlf on Windows) and the one the flow
ran on. The mode_migration_npm berry legs honor the variable too (run them
with CI unset: they do not pin yarn's CI defaults).