UNPKG

verdaccio

Version:

A lightweight private npm proxy registry

421 lines (420 loc) 15.7 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const require_runtime = require("../_virtual/_rolldown/runtime.js"); const require_lib_constants = require("./constants.js"); const require_lib_logger_index = require("./logger/index.js"); const require_lib_utils = require("./utils.js"); let lodash = require("lodash"); lodash = require_runtime.__toESM(lodash); let _verdaccio_core = require("@verdaccio/core"); let _verdaccio_utils = require("@verdaccio/utils"); let debug = require("debug"); debug = require_runtime.__toESM(debug); let _verdaccio_search_indexer = require("@verdaccio/search-indexer"); //#region src/lib/storage-utils.ts var debug$1 = (0, debug.default)("verdaccio:storage-utils"); /** * Create an empty package manifest with all the internal bookkeeping * sections (`_uplinks`, `_distfiles`, `_attachments`, `_rev`) initialized. * Used as the starting point when a package is seen for the first time. * @param name package name * @return an empty manifest for the given package */ function generatePackageTemplate(name) { return { name, versions: {}, time: {}, [require_lib_constants.USERS]: {}, [require_lib_constants.DIST_TAGS]: {}, _uplinks: {}, _distfiles: {}, _attachments: {}, _rev: "" }; } /** * Normalize a manifest read from storage: ensure the object sections * (`versions`, `dist-tags`, `_distfiles`, `_attachments`, `_uplinks`, `time`) * exist, default the revision and `_id`, and normalize the dist-tags shape. * The manifest is mutated in place. * @param pkg package manifest reference * @return the same manifest, normalized */ function normalizePackage(pkg) { [ "versions", "dist-tags", "_distfiles", "_attachments", "_uplinks", "time" ].forEach((key) => { const pkgProp = pkg[key]; if (lodash.default.isNil(pkgProp) || require_lib_utils.isObject(pkgProp) === false) pkg[key] = {}; }); if (lodash.default.isString(pkg._rev) === false) { debug$1("package %o has no revision, setting default", pkg.name); pkg._rev = require_lib_constants.STORAGE.DEFAULT_REVISION; } if (lodash.default.isString(pkg._id) === false) pkg._id = pkg.name; require_lib_utils.normalizeDistTags(pkg); return pkg; } /** * Produce the next revision id in the `<counter>-<hash>` format used by the * storage layer, incrementing the counter of the previous revision. * @param rev previous revision id, eg. `2-a1b2c3` * @return the next revision id */ function generateRevision(rev) { return (+rev.split("-")[0] || 0) + 1 + "-" + (0, _verdaccio_utils.generateRandomHexString)(); } /** * Pick the most relevant readme for a package: the manifest readme or the * `latest` version readme first, otherwise the first readme found across the * `next`, `beta`, `alpha`, `test`, `dev` and `canary` dist-tags, in that order. * @param pkg package manifest * @return the readme content, or an empty string when none is available */ function getLatestReadme(pkg) { const versions = pkg["versions"] || {}; const distTags = pkg["dist-tags"] || {}; const latestVersion = distTags["latest"] ? versions[distTags["latest"]] || {} : {}; let readme = lodash.default.trim(pkg.readme || latestVersion.readme || ""); if (readme) return readme; [ "next", "beta", "alpha", "test", "dev", "canary" ].map(function(tag) { if (readme) return readme; const version = distTags[tag] ? versions[distTags[tag]] || {} : {}; readme = lodash.default.trim(version.readme || readme); }); return readme; } /** * Remove the readme from a version object: only one readme is kept per * package (see getLatestReadme), never one per version. The version is * mutated in place. * @param version version metadata * @return the same version without its readme */ function cleanUpReadme(version) { if (lodash.default.isNil(version) === false) delete version.readme; return version; } /** * Manifest properties allowed in responses served to clients; everything * else is verdaccio-internal bookkeeping and is stripped by cleanUpLinksRef. */ var WHITELIST = [ "_rev", "name", "versions", "dist-tags", "readme", "time", "_id", "users" ]; /** * Strip verdaccio-internal sections (`_distfiles`, `_attachments`, ...) from * a manifest before serving it to a client. The manifest is mutated in place. * @param keepUpLinkData whether the `_uplinks` section should be preserved * @param result manifest to clean up * @return the same manifest with only whitelisted properties */ function cleanUpLinksRef(keepUpLinkData, result) { const propertyToKeep = [...WHITELIST]; if (keepUpLinkData === true) propertyToKeep.push("_uplinks"); for (const i in result) if (propertyToKeep.indexOf(i) === -1) delete result[i]; return result; } /** * Verify a package does not exist locally yet; part of the publish flow. * @param name package name * @param localStorage local storage instance * @throws conflict error when the package already exists locally */ async function checkPackageLocal(name, localStorage) { debug$1("checking if %o already exists locally", name); try { if (await localStorage.getPackageMetadataAsync(name)) { debug$1("package %o already exists locally", name); throw require_lib_utils.ErrorCode.getConflict(_verdaccio_core.API_ERROR.PACKAGE_EXIST); } } catch (err) { if (err.status === _verdaccio_core.HTTP_STATUS.NOT_FOUND) { debug$1("package %o does not exist locally", name); return; } throw err; } } /** * Create a new package in the local storage and register its latest version * in the in-memory search indexer. * @param name package name * @param metadata package manifest to store * @param localStorage local storage instance */ function publishPackage(name, metadata, localStorage) { debug$1("publishing a new package for %o", name); return new Promise((resolve, reject) => { localStorage.addPackage(name, metadata, (err, latest) => { if (!lodash.default.isNull(err)) return reject(err); else if (!lodash.default.isUndefined(latest)) _verdaccio_search_indexer.SearchMemoryIndexer.add(latest).catch((reason) => { debug$1("indexer has failed on add item %o", reason); require_lib_logger_index.logger.error("indexer has failed on add item"); }); return resolve(); }); }); } /** * Verify a package does not exist on any uplink yet; part of the publish * flow. Uplink failures other than 404 reject the publish, unless * `publish.allow_offline` is enabled in the configuration. * @param name package name * @param isAllowPublishOffline whether publishing with unreachable uplinks is allowed * @param syncMetadata bound Storage._syncUplinksMetadata function * @throws conflict error when the package exists upstream, service unavailable when uplinks are offline */ function checkPackageRemote(name, isAllowPublishOffline, syncMetadata) { debug$1("checking if %o already exists on uplinks", name); return new Promise((resolve, reject) => { syncMetadata(name, null, {}, (err, packageJsonLocal, upLinksErrors) => { if (err && err.status !== _verdaccio_core.HTTP_STATUS.NOT_FOUND) return reject(err); if (lodash.default.isNil(packageJsonLocal) === false) { debug$1("package %o already exists on an uplink", name); return reject(require_lib_utils.ErrorCode.getConflict(_verdaccio_core.API_ERROR.PACKAGE_EXIST)); } for (let errorItem = 0; errorItem < upLinksErrors.length; errorItem++) if (lodash.default.isNil(upLinksErrors[errorItem][0]) === false) { if (upLinksErrors[errorItem][0].status !== _verdaccio_core.HTTP_STATUS.NOT_FOUND) { if (isAllowPublishOffline) { debug$1("uplink offline for %o, publish offline is allowed", name); return resolve(); } debug$1("uplink offline for %o, rejecting publish", name); return reject(require_lib_utils.ErrorCode.getServiceUnavailable(_verdaccio_core.API_ERROR.UPLINK_OFFLINE_PUBLISH)); } } return resolve(); }); }); } /** * Merge the `time` field of a remote manifest into the cached one; remote * entries win on conflicts. Returns a new manifest, the inputs are untouched. * @param cacheManifest local cached manifest * @param remoteManifest manifest fetched from an uplink * @return the cached manifest with the merged time field */ function mergeUplinkTimeIntoLocal(cacheManifest, remoteManifest) { if ("time" in remoteManifest) { debug$1("merging remote time field into local manifest for %o", cacheManifest.name); return { ...cacheManifest, time: { ...cacheManifest.time, ...remoteManifest.time } }; } return cacheManifest; } /** * Build the search item emitted for a local package during a search stream, * based on its latest version: npm-search compatible maintainers * (`{ username, email }`), publisher (from `_npmUser`, falling back to the * first maintainer), license and links. * @param data package manifest * @param time modified time attached to the search item * @return the search item, or undefined when the package has no usable latest version */ function prepareSearchPackage(data, time) { const latest = _verdaccio_core.pkgUtils.getLatest(data); if (!latest || !data.versions[latest]) { debug$1("no latest version found for %o, skipping search item", data.name); return; } debug$1("preparing search item for %o@%o", data.name, latest); if (latest && data.versions[latest]) { const version = data.versions[latest]; const versions = { [latest]: "latest" }; const rawMaintainers = version.maintainers || [version.author].filter(Boolean); const maintainers = Array.isArray(rawMaintainers) ? rawMaintainers.map((maintainer) => typeof maintainer === "string" ? { username: maintainer, email: "" } : { ...maintainer, username: maintainer?.username ?? maintainer?.name ?? "", email: maintainer?.email ?? "" }) : []; const npmUser = version._npmUser; const publisher = npmUser?.name ? { username: npmUser.name, email: npmUser.email ?? "" } : maintainers[0] ?? {}; return { name: version.name, description: version.description, [require_lib_constants.DIST_TAGS]: { latest }, maintainers, publisher, author: version.author, repository: version.repository, readmeFilename: version.readmeFilename || "", homepage: version.homepage, keywords: version.keywords, time: { modified: time }, bugs: version.bugs, license: version.license, versions }; } } /** * Whether the tarball url path ends with the given file name * (any query string or fragment is ignored, like the path parsing * in updateVersions). */ function tarballMatchesFilename(tarball, filename) { return tarball.split(/[?#]/)[0].endsWith(`/${filename}`); } /** * Build a distfile record from a version's dist metadata when its tarball * url matches the given file name. */ function distFileFromVersion(version, filename) { const dist = version?.dist; if (dist?.tarball && tarballMatchesFilename(dist.tarball, filename)) return { url: dist.tarball, sha: dist.shasum }; return null; } /** * Resolve the distfile record for a tarball filename. Prefers the * `_distfiles` bookkeeping, but falls back to the version metadata when the * record is missing (storages written by other verdaccio versions cache * versions without their distfile records, which made those tarballs * permanently unavailable). * @param manifest cached manifest * @param filename tarball file name, eg. pkg-1.0.0.tgz */ function lookupDistFile(manifest, filename) { const distFile = manifest?._distfiles?.[filename]; if (lodash.default.isNil(distFile) === false) return distFile; const versions = manifest?.versions ?? {}; const basename = manifest?.name?.replace(/^.*\//, ""); if (basename && filename.startsWith(`${basename}-`) && filename.endsWith(".tgz")) { const versionId = filename.slice(basename.length + 1, -4); const distFromVersion = distFileFromVersion(versions[versionId], filename); if (distFromVersion) { debug$1("distfile record missing for %o, using version %o dist", filename, versionId); return distFromVersion; } } for (const versionId in versions) { const distFromVersion = distFileFromVersion(versions[versionId], filename); if (distFromVersion) { debug$1("distfile record missing for %o, using version %o dist", filename, versionId); return distFromVersion; } } debug$1("no distfile record or version dist found for %o", filename); return null; } /** * Check whether the package metadata has enough data to be published. * @param pkg package manifest * @return true when the manifest carries a versions section */ function isPublishablePackage(pkg) { return Object.keys(pkg).includes("versions"); } /** * Whether a version declares an install lifecycle script (`install`, * `preinstall` or `postinstall`); surfaced in abbreviated manifests as * `hasInstallScript` so clients can warn about script execution. * @param version version metadata * @return true when an install script is present */ function hasInstallScript(version) { if (version?.scripts) return Object.keys(version.scripts).find((item) => { return [ "install", "preinstall", "postinstall" ].includes(item); }) !== void 0; return false; } /** * Convert a full manifest into the abbreviated format served when the client * requests `application/vnd.npm.install-v1+json` (npm install), keeping only * the fields the npm registry contract defines for installation. * https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md * @param manifest full package manifest * @return the abbreviated manifest */ function convertAbbreviatedManifest(manifest) { if (debug$1.enabled) debug$1("converting %o to abbreviated manifest with %o versions", manifest.name, Object.keys(manifest.versions).length); const abbreviatedVersions = Object.keys(manifest.versions).reduce((acc, version) => { const _version = manifest.versions[version]; acc[version] = { name: _version.name, version: _version.version, description: _version.description, deprecated: _version.deprecated, bin: _version.bin, dist: _version.dist, engines: _version.engines, cpu: _version.cpu, os: _version.os, funding: _version.funding, directories: _version.directories, dependencies: _version.dependencies, devDependencies: _version.devDependencies, peerDependencies: _version.peerDependencies, peerDependenciesMeta: _version.peerDependenciesMeta, optionalDependencies: _version.optionalDependencies, bundleDependencies: _version.bundleDependencies, _hasShrinkwrap: _version._hasShrinkwrap, hasInstallScript: hasInstallScript(_version) }; return acc; }, {}); return { name: manifest["name"], [require_lib_constants.DIST_TAGS]: manifest[require_lib_constants.DIST_TAGS], versions: abbreviatedVersions, modified: manifest?.time?.modified, time: manifest?.time }; } //#endregion exports.WHITELIST = WHITELIST; exports.checkPackageLocal = checkPackageLocal; exports.checkPackageRemote = checkPackageRemote; exports.cleanUpLinksRef = cleanUpLinksRef; exports.cleanUpReadme = cleanUpReadme; exports.convertAbbreviatedManifest = convertAbbreviatedManifest; exports.distFileFromVersion = distFileFromVersion; exports.generatePackageTemplate = generatePackageTemplate; exports.generateRevision = generateRevision; exports.getLatestReadme = getLatestReadme; exports.hasInstallScript = hasInstallScript; exports.isPublishablePackage = isPublishablePackage; exports.lookupDistFile = lookupDistFile; exports.mergeUplinkTimeIntoLocal = mergeUplinkTimeIntoLocal; exports.normalizePackage = normalizePackage; exports.prepareSearchPackage = prepareSearchPackage; exports.publishPackage = publishPackage; exports.tarballMatchesFilename = tarballMatchesFilename; //# sourceMappingURL=storage-utils.js.map