Поисковый интент: workspaces на Bun плюс легаси на npm и package-lock.json. На удалённом Mac CI за трансграничным каналом чаще ломает двойная резолюция, разные URL registry и «глупый» retry. Ниже — матрица и копируемые env. Без входа: главная MacPull, блог; см. Yarn Berry и Deno/JSR — без углубления в Git, Docker или SwiftPM.

Три типичных отказа гибридного Bun + npm на удалённом Mac

Узкое место — RTT и лимиты registry, не CPU Apple Silicon.

  1. Двойной lockfile: npm install в корне при CI на bun install --frozen-lockfile (или наоборот) даёт «случайный» дрейф.
  2. Разные registry: NPM_CONFIG_REGISTRY / bunfig.toml / .npmrc расходятся — checksum mismatch на общих раннерах.
  3. Нет капа сокетов: волна 429; внешний retry job маскирует integrity.

Исполняемая матрица решений (гибрид + трансграничный registry)

Строки — ворота merge; на мультитенантных Mac важнее лимит job’ов на зеркало, чем dedup tarball’ов.

Решение Вариант A Вариант B Примечание для удалённого Mac CI (2026)
Владелец lockfile Bun.lockb / bun.lock; корень: bun install --frozen-lockfile, без npm install Только package-lock.json; Bun не пишет lock Не перегенерировать оба lock в CI; по Bun.lockb — checksum + лог.
Зеркало registry NPM_CONFIG_REGISTRY + scoped токен в секретах Региональное зеркало + bunfig.toml при политике Один hostname на job для npm ci и bun install.
Параллельная установка Bun по умолчанию; кап одновременных job на Mac (1–2) npm config set maxsockets 12 (8–16, метрики 429) Свыше ~24 сокетов на высоком RTT часто растёт 429, не p50.
Ключ кеша CI bun-<os>-<hash(lock)>-<hash(package.json**)> npm-<os>-<hash(package-lock)>-<node>-<npm> Добавляйте мажор Node и семвер npm/Bun при влиянии на optional.
Повторы Сеть: ≤3, backoff 2/4/8 с Lockfile / integrity: 0 внешних retry Не оборачивать ERESOLVE, frozen-lockfile, checksum.

Пять шагов: назначить владельца lock → один registry URL → NPM_CONFIG_MAXSOCKETS плюс лимит job на узел → ключи кеша по lock и версиям → grep-ворота на checksum/frozen-lockfile без внешнего retry.

Для ссылок в runbook: коридор 8–24 параллельных загрузок до метрик; сеть — до трёх попыток с 2/4/8 с; ключ кеша — хеш lock плюс семвер Bun или npm.

Bun.lockb и package-lock.json в одном репозитории

Bun.lockb vs package-lock.json — разные резолверы. Легаси на npm: отдельный корень + npm ci; корень monorepo на Bun — правило в README. В метаданных job печатайте версии Bun/npm; на arm64 сверяйте optional native.

Если команда случайно запускает обновление lock «чужим» менеджером, восстановление начинается с отката коммита и повторной генерации одним выбранным инструментом на чистом клоне. Не смешивайте артефакты кеша Bun и npm в одном каталоге без префикса job — иначе тёплый раннер подставит чужие tarball’ы и вы получите нестабильные тесты без явной ошибки registry.

Переменные окружения и команды (копируйте в CI)

В начале install; URL — по allowlist security.

# Трансграничный pull: npm-стиль читают и npm, и Bun
export NPM_CONFIG_REGISTRY="https://registry.npmjs.org/"
export NPM_CONFIG_FETCH_RETRIES="3"
export NPM_CONFIG_FETCH_RETRY_MINTIMEOUT="20000"
export NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT="120000"
export NPM_CONFIG_MAXSOCKETS="12"

export BUN_INSTALL_CACHE_DIR="${CI_CACHE_DIR:-$HOME/.cache/bun}/install"
export npm_config_cache="${CI_CACHE_DIR:-$HOME/.cache}/npm"

bun --version
bun install --frozen-lockfile

# Пример npm-only поддерева:
# (cd packages/legacy-npm && npm ci --omit=dev)

bunfig.toml без секретов:

# bunfig.toml — пример
[install]
# registry = "https://your.approved.mirror.example/npm"
frozenLockfile = true
# ignoreScripts = true   # в CI при жёсткой политике скриптов
  1. Один URL в env, .npmrc и при необходимости bunfig.toml.
  2. Приватные scope — токен из CI, read-only PAT.

Фрагменты ключей кеша (GitHub Actions)

Раздельные кеши — смена Bun.lockb не должна сбрасывать npm-кеш без причины.

- uses: actions/cache@v4
  with:
    path: ~/.bun/install/cache
    key: bun-${{ runner.os }}-${{ hashFiles('bun.lockb', 'bun.lock') }}-${{ hashFiles('**/package.json') }}
    restore-keys: |
      bun-${{ runner.os }}-${{ hashFiles('bun.lockb', 'bun.lock') }}-

- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-${{ runner.os }}-node${{ matrix.node }}-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      npm-${{ runner.os }}-node${{ matrix.node }}-

Оболочка с ограниченным повтором (только сеть)

Retry только сеть; по frozen/checksum — выход.

#!/usr/bin/env bash
set -euo pipefail
attempt=1 max=4 delay=2
while [ "$attempt" -le "$max" ]; do
  log="bun-install-${attempt}.log"
  if bun install --frozen-lockfile 2>&1 | tee "$log"; then exit 0; fi
  if grep -qiE 'frozen.?lock|lockfile|checksum|integrity|ERESOLVE' "$log"; then exit 1; fi
  sleep "$delay"; delay=$((delay * 2)); attempt=$((attempt + 1))
done
exit 1

FAQ

Можно ли разработчикам чередовать bun install и npm install в корне? Нет. Один инструмент обновляет lock в повседневной работе; второй — только в документированном окне обслуживания с двойным ревью.

Снимает ли быстрое зеркало необходимость капа параллелизма? Нет. Зеркала всё равно режут по rate limit; кап защищает общий egress пула удалённого Mac и стабилизирует p95 очереди.

Смежные темы? Yarn Berry, Deno/JSR.

Итог

Стабильность: один владелец lock на корень, одно зеркало, кап сокетов и job’ов, ключи кеша по lock и версиям инструментов, без retry на integrity.

CTA: macpull.com — главная без входа; тарифы, покупка, помощь; каталог блога для соседних матриц по JS.