Перейти к содержимому

npm

NORA предоставляет полностью совместимый npm-реестр. Публикуйте приватные пакеты и прозрачно проксируйте публичные пакеты из официального реестра npm.

Направьте npm (или yarn/pnpm) на NORA:

Окно терминала
# npm
npm config set registry http://nora.example.com:4000/npm/
# yarn
yarn config set registry http://nora.example.com:4000/npm/
# pnpm
pnpm config set registry http://nora.example.com:4000/npm/

Публикация пакета:

Окно терминала
npm publish --registry http://nora.example.com:4000/npm/

npm публикует только с токеном для реестра: при выключенной аутентификации подойдёт любое значение, при включённой — API-токен:

Окно терминала
npm config set //nora.example.com:4000/npm/:_authToken=<token>

Настройка .npmrc для конкретного проекта:

registry=http://nora.example.com:4000/npm/

Если пакет не найден локально, NORA загружает его из upstream-реестра. URL тарболов в метаданных перезаписываются для загрузки через NORA.

config.toml:

[npm]
enabled = true
proxy = "https://registry.npmjs.org"
proxy_timeout = 30
metadata_ttl = 300 # секунды

Переменные окружения:

ПеременнаяОписаниеПо умолчанию
NORA_NPM_ENABLEDВключить реестр npmtrue
NORA_NPM_PROXYURL upstream-реестраhttps://registry.npmjs.org
NORA_NPM_PROXY_AUTHАутентификация upstream (user:pass)—
NORA_NPM_PROXY_TIMEOUTТаймаут upstream в секундах30
NORA_NPM_METADATA_TTLTTL кеша метаданных в секундах300

С 1.4.0 пакет, опубликованный в NORA, владеет своим именем: его packument, манифесты версий и dist-tags (/<name>, /<name>/<version>, /<name>/<dist-tag>, -/package/<name>/dist-tags) отдаются из его ключей при любом metadata_ttl и никогда не обращаются к upstream. То, что upstream отдаёт для пакета, который в NORA не опубликован, кэшируется отдельно, в npm/<name>/upstream/. npm search и другие пути метаданных отвечает upstream, без кэша.

Используйте proxies вместо proxy, если часть scope живёт в приватном реестре (GitLab, GitHub Packages, Verdaccio, реестр поставщика), а всё остальное берётся из публичного:

[npm]
proxies = [
"https://registry.npmjs.org",
{ url = "https://npm.vendor.example", scopes = ["@vendor"], auth_bearer_env = "VENDOR_NPM_TOKEN" },
{ url = "https://gitlab.example.com/api/v4/packages/npm", scopes = ["@team", "@infra"], auth = "deploy:secret" },
]
  • Пакет из перечисленного scope (@vendor/a) идёт только к записи, которой этот scope принадлежит: за метаданными, тарболами и при ревалидации. Если этот upstream недоступен, клиент получает 502: другой upstream NORA не спрашивает. Так приватное имя не попадает в публичный реестр.
  • Все остальные пакеты идут к записи без scopes. Такая запись может быть только одна. Если её нет, пакеты вне перечисленных scope отдаются только из локального хранилища.
  • У каждой записи свои учётные данные: auth = "user:pass" (Basic) или auth_bearer_env = "VAR" (Bearer-токен из этой переменной окружения, читается при старте; в конфиг и логи токен не попадает). Задаётся одно из двух.
  • Если задан proxies, параметры proxy и proxy_auth не используются (на proxy_auth при старте будет предупреждение).

NORA не запускается (код выхода 78), если у записи URL без http:///https://, scope не вида @name, один scope принадлежит двум записям, заданы одновременно auth и auth_bearer_env или переменная из auth_bearer_env не задана либо содержит значение, недопустимое в HTTP-заголовке (например, с переводом строки в конце).

Происхождение кэша. Для каждого закэшированного объекта NORA записывает, какой upstream его отдал, и SHA-256 этих байтов. В scope с владельцем закэшированная копия отдаётся, только если её отдал владелец и байты не менялись, или если пакет опубликован в NORA. Копия, закэшированная раньше из другого upstream (например, публичный пакет с тем же именем, попавший в кэш до того, как у scope появилась своя запись), заново запрашивается у владельца и не отдаётся ни из кэша, ни как устаревшая копия. Записи хранятся в .nora-origins/ в корне хранилища; nora backup, nora restore и nora migrate их переносят, а nora gc удаляет записи, у которых нет объекта.

Своей базы уязвимостей у NORA нет. npm audit, yarn npm audit и pnpm audit пересылаются в upstream, который отдаёт пакеты без scope, и его ответ возвращается как есть.

Если у proxies есть scope с владельцами или в курации задано внутреннее пространство имён, NORA сначала удаляет эти имена из запроса. Это возможно только для формата npm 7+ (/-/npm/v1/security/advisories/bulk, обычный или gzip). Запрос, который проверить нельзя, отклоняется с кодом 4xx и JSON-полем error с причиной; ответа «уязвимостей нет» в этом случае не бывает:

ЗапросКод
npm 6 audits/quick или pnpm audits (всё дерево зависимостей)422
Тело больше 8 МБ, сжатое или после распаковки413
Кодирование, отличное от gzip415
gzip, который не распаковывается, или тело не того JSON-формата422

Реестр без upstream для пакетов без scope отвечает на аудит 200 {}, и клиенты сообщают, что уязвимостей нет.

ФункцияСтатусПримечание
Метаданные пакета (GET)ПолнаяJSON со всеми версиями
Scoped-пакеты (@scope/name)ПолнаяURL-кодированный путь
Скачивание тарболовПолнаяПроверка SHA256
Перезапись URL тарболовПолнаяУказывает на NORA, а не на upstream
Публикация (npm publish)ПолнаяИммутабельные версии
Upstream-проксиПолнаяНастраиваемый TTL; несколько upstream по scope
Unpublish—Не реализовано
Dist-теги (latest, next)ЧастичнаяЧитаются из метаданных, без явного управления
Поиск (/-/v1/search)—Не реализовано
Аудит безопасностиПроксиПересылается в upstream; имена scope с владельцами вырезаются, иначе запрос отклоняется
  • Unpublish не поддерживается — опубликованные версии являются иммутабельными.
  • Dist-теги считываются из upstream-метаданных, но не могут быть явно управляемы через NORA.
  • Поиск пакетов не реализован. На аудит отвечает upstream: своей базы уязвимостей у NORA нет.
  • dist.tarball, указывающий на другой хост, а не на URL своего upstream, не перезаписывается: клиент скачивает тарбол с этого хоста напрямую.
  • Если upstream недоступен при первом запросе пакета, NORA отвечает 502 (до 1.3.4 — 404).