verdaccio
Version:
A lightweight private npm proxy registry
609 lines (608 loc) • 23.2 kB
JavaScript
Object.defineProperties(exports, {
__esModule: { value: true },
[Symbol.toStringTag]: { value: "Module" }
});
const require_runtime = require("../_virtual/_rolldown/runtime.js");
const require_lib_constants = require("./constants.js");
const require_lib_utils = require("./utils.js");
const require_lib_storage_utils = require("./storage-utils.js");
let lodash = require("lodash");
lodash = require_runtime.__toESM(lodash);
let _verdaccio_utils = require("@verdaccio/utils");
let debug = require("debug");
debug = require_runtime.__toESM(debug);
let assert = require("assert");
assert = require_runtime.__toESM(assert);
let _verdaccio_streams = require("@verdaccio/streams");
let _verdaccio_core = require("@verdaccio/core");
//#region src/lib/local-storage.ts
var debug$1 = (0, debug.default)("verdaccio:local-storage");
/**
* Implements Storage interface (same for storage.js, local-storage.js, up-storage.js).
*/
var LocalStorage = class {
config;
storagePlugin;
logger;
constructor(config, logger, localStorage) {
this.logger = logger;
this.config = config;
this.storagePlugin = localStorage;
}
addPackage(name, pkg, callback) {
const storage = this._getLocalStorage(name);
if (lodash.default.isNil(storage)) return callback(require_lib_utils.ErrorCode.getNotFound("this package cannot be added"));
storage.createPackage(name, require_lib_storage_utils.generatePackageTemplate(name), (err) => {
if (lodash.default.isNull(err) === false && (err.code === require_lib_constants.STORAGE.FILE_EXIST_ERROR || err.code === _verdaccio_core.HTTP_STATUS.CONFLICT)) return callback(require_lib_utils.ErrorCode.getConflict());
const latest = (0, _verdaccio_utils.getLatestVersion)(pkg);
if (lodash.default.isNil(latest) === false && pkg.versions[latest]) return callback(null, pkg.versions[latest]);
return callback();
});
}
/**
* Remove package.
* @param {*} name
* @param {*} callback
* @return {Function}
*/
removePackage(name, callback) {
const storage = this._getLocalStorage(name);
debug$1("[storage] removing package %o", name);
if (lodash.default.isNil(storage)) return callback(require_lib_utils.ErrorCode.getNotFound());
storage.readPackage(name, (err, data) => {
if (lodash.default.isNil(err) === false) {
if (err.code === require_lib_constants.STORAGE.NO_SUCH_FILE_ERROR || err.code === _verdaccio_core.HTTP_STATUS.NOT_FOUND) return callback(require_lib_utils.ErrorCode.getNotFound());
return callback(err);
}
data = require_lib_storage_utils.normalizePackage(data);
this.storagePlugin.remove(name, (removeFailed) => {
if (removeFailed) {
this.logger.error({ name }, `[storage/removePackage] the database is locked, removed has failed for @{name}`);
return callback(require_lib_utils.ErrorCode.getBadData(removeFailed.message));
}
storage.deletePackage(require_lib_constants.STORAGE.PACKAGE_FILE_NAME, (err) => {
if (err) return callback(err);
const attachments = Object.keys(data._attachments);
this._deleteAttachments(storage, attachments, callback);
});
});
});
}
/**
* Synchronize remote package info with the local one
* @param {*} name
* @param {*} packageInfo
* @param {*} callback
*/
updateVersions(name, packageInfo, callback) {
this._readCreatePackage(name, (err, packageLocalJson) => {
if (err) return callback(err);
let change = false;
packageLocalJson.readme = require_lib_storage_utils.getLatestReadme(packageInfo);
if (packageInfo.readme !== packageLocalJson.readme) change = true;
for (const versionId in packageInfo.versions) if (lodash.default.isNil(packageLocalJson.versions[versionId])) {
let version = packageInfo.versions[versionId];
version = require_lib_storage_utils.cleanUpReadme(version);
version.contributors = (0, _verdaccio_utils.normalizeContributors)(version.contributors);
change = true;
packageLocalJson.versions[versionId] = version;
if (version.dist && version.dist.tarball) {
let urlObject;
try {
urlObject = new URL(version.dist.tarball, "http://localhost");
} catch (err) {
this.logger.warn({
name,
version: versionId,
tarball: version.dist.tarball,
err: err.message
}, "skipping distfile for @{name}@@{version}: invalid tarball url @{tarball} (@{err})");
continue;
}
const filename = urlObject.pathname.replace(/^.*\//, "");
if (lodash.default.isNil(packageLocalJson._distfiles[filename])) {
const hash = packageLocalJson._distfiles[filename] = {
url: version.dist.tarball,
sha: version.dist.shasum
};
const upLink = version[Symbol.for("__verdaccio_uplink")];
if (lodash.default.isNil(upLink) === false) this._updateUplinkToRemoteProtocol(hash, upLink);
}
}
}
for (const tag in packageInfo[require_lib_constants.DIST_TAGS]) if (!packageLocalJson["dist-tags"][tag] || packageLocalJson["dist-tags"][tag] !== packageInfo["dist-tags"][tag]) {
change = true;
packageLocalJson[require_lib_constants.DIST_TAGS][tag] = packageInfo[require_lib_constants.DIST_TAGS][tag];
}
for (const up in packageInfo._uplinks) if (Object.prototype.hasOwnProperty.call(packageInfo._uplinks, up)) {
if (!require_lib_utils.isObject(packageLocalJson._uplinks[up]) || packageInfo._uplinks[up].etag !== packageLocalJson._uplinks[up].etag || packageInfo._uplinks[up].fetched !== packageLocalJson._uplinks[up].fetched) {
change = true;
packageLocalJson._uplinks[up] = packageInfo._uplinks[up];
}
}
if ("time" in packageInfo && !lodash.default.isEqual(packageLocalJson.time, packageInfo.time)) {
packageLocalJson.time = packageInfo.time;
change = true;
}
if (change) {
debug$1("updating package %o info", name);
this._writePackage(name, packageLocalJson, function(err) {
callback(err, packageLocalJson);
});
} else callback(null, packageLocalJson);
});
}
/**
* Add a new version to a previous local package.
* @param {*} name
* @param {*} version
* @param {*} metadata
* @param {*} tag
* @param {*} callback
*/
addVersion(name, version, metadata, tag, callback) {
this._updatePackage(name, (data, cb) => {
data.readme = metadata.readme;
metadata = require_lib_storage_utils.cleanUpReadme(metadata);
metadata.contributors = (0, _verdaccio_utils.normalizeContributors)(metadata.contributors);
if (data.versions[version] != null) return cb(require_lib_utils.ErrorCode.getConflict());
if (require_lib_utils.isObject(metadata.dist) && lodash.default.isString(metadata.dist.tarball)) {
const tarball = metadata.dist.tarball.replace(/.*\//, "");
if (require_lib_utils.isObject(data._attachments[tarball])) {
if (lodash.default.isNil(data._attachments[tarball].shasum) === false && lodash.default.isNil(metadata.dist.shasum) === false) {
if (data._attachments[tarball].shasum != metadata.dist.shasum) {
const errorMessage = `shasum error, ${data._attachments[tarball].shasum} != ${metadata.dist.shasum}`;
return cb(require_lib_utils.ErrorCode.getBadRequest(errorMessage));
}
}
const currentDate = (/* @__PURE__ */ new Date()).toISOString();
if (lodash.default.isNil(data.time)) data.time = {};
data.time["modified"] = currentDate;
if ("created" in data.time === false) data.time.created = currentDate;
data.time[version] = currentDate;
data._attachments[tarball].version = version;
}
}
data.versions[version] = metadata;
require_lib_utils.tagVersion(data, version, tag);
this.storagePlugin.add(name, (addFailed) => {
if (addFailed) return cb(require_lib_utils.ErrorCode.getBadData(addFailed.message));
cb();
});
}, callback);
}
/**
* Merge a new list of tags for a local packages with the existing one.
* @param {*} pkgName
* @param {*} tags
* @param {*} callback
*/
mergeTags(pkgName, tags, callback) {
this._updatePackage(pkgName, (data, cb) => {
for (const tag in tags) {
if (lodash.default.isNull(tags[tag])) {
delete data[require_lib_constants.DIST_TAGS][tag];
continue;
}
if (lodash.default.isNil(data.versions[tags[tag]])) return cb(this._getVersionNotFound());
const version = tags[tag];
require_lib_utils.tagVersion(data, version, tag);
}
cb(null);
}, callback);
}
/**
* Return version not found
* @return {String}
* @private
*/
_getVersionNotFound() {
return require_lib_utils.ErrorCode.getNotFound(_verdaccio_core.API_ERROR.VERSION_NOT_EXIST);
}
/**
* Return file no available
* @return {String}
* @private
*/
_getFileNotAvailable() {
return require_lib_utils.ErrorCode.getNotFound("no such file available");
}
/**
* Update the package metadata, tags and attachments (tarballs).
* Note: Currently supports unpublishing and deprecation.
* @param {*} name
* @param {*} incomingPkg
* @param {*} revision
* @param {*} callback
* @return {Function}
*/
changePackage(name, incomingPkg, revision, callback) {
if (!require_lib_utils.isObject(incomingPkg.versions) || !require_lib_utils.isObject(incomingPkg["dist-tags"])) {
this.logger.error({ name }, `changePackage bad data for @{name}`);
return callback(require_lib_utils.ErrorCode.getBadData());
}
debug$1("changePackage udapting package for %o", name);
this._updatePackage(name, (localData, cb) => {
for (const version in localData.versions) {
const incomingVersion = incomingPkg.versions[version];
if (lodash.default.isNil(incomingVersion)) {
this.logger.info({
name,
version
}, "unpublishing @{name}@@{version}");
delete localData.versions[version];
delete localData.time[version];
for (const file in localData._attachments) if (localData._attachments[file].version === version) delete localData._attachments[file].version;
} else if (Object.prototype.hasOwnProperty.call(incomingVersion, "deprecated")) {
const incomingDeprecated = incomingVersion.deprecated;
if (incomingDeprecated != localData.versions[version].deprecated) {
if (!incomingDeprecated) {
this.logger.info({
name,
version
}, "undeprecating @{name}@@{version}");
delete localData.versions[version].deprecated;
} else {
this.logger.info({
name,
version
}, "deprecating @{name}@@{version}");
localData.versions[version].deprecated = incomingDeprecated;
}
localData.time.modified = (/* @__PURE__ */ new Date()).toISOString();
}
}
}
localData[require_lib_constants.USERS] = incomingPkg[require_lib_constants.USERS];
localData[require_lib_constants.DIST_TAGS] = incomingPkg[require_lib_constants.DIST_TAGS];
cb(null);
}, function(err) {
if (err) return callback(err);
callback();
});
}
/**
* Remove a tarball.
* @param {*} name
* @param {*} filename
* @param {*} revision
* @param {*} callback
*/
removeTarball(name, filename, revision, callback) {
(0, assert.default)((0, _verdaccio_utils.validateName)(filename));
this._updatePackage(name, (data, cb) => {
if (data._attachments[filename]) {
delete data._attachments[filename];
cb(null);
} else cb(this._getFileNotAvailable());
}, (err) => {
if (err) return callback(err);
const storage = this._getLocalStorage(name);
if (storage) storage.deletePackage(filename, callback);
});
}
/**
* Add a tarball.
* @param {String} name
* @param {String} filename
* @param {DistFile} distFile distfile record to restore when the manifest
* lost it (storages written by other verdaccio versions); persisted
* together with the attachment registration once the upload succeeds
* @return {Stream}
*/
addTarball(name, filename, distFile) {
(0, assert.default)((0, _verdaccio_utils.validateName)(filename));
let length = 0;
const shaOneHash = (0, _verdaccio_utils.createTarballHash)();
const uploadStream = new _verdaccio_streams.UploadTarball({});
const _transform = uploadStream._transform;
const storage = this._getLocalStorage(name);
uploadStream.abort = function() {};
uploadStream.done = function() {};
uploadStream._transform = function(data, ...args) {
shaOneHash.update(data);
length += data.length;
const appliedData = [data, ...args];
_transform.apply(uploadStream, appliedData);
};
if (name === "__proto__") {
process.nextTick(() => {
uploadStream.emit("error", require_lib_utils.ErrorCode.getForbidden());
});
return uploadStream;
}
if (!storage) {
process.nextTick(() => {
uploadStream.emit("error", "can't upload this package");
});
return uploadStream;
}
const writeStream = storage.writeTarball(filename);
writeStream.on("error", (err) => {
if (err.code === require_lib_constants.STORAGE.FILE_EXIST_ERROR || err.code === _verdaccio_core.HTTP_STATUS.CONFLICT) {
uploadStream.emit("error", require_lib_utils.ErrorCode.getConflict());
uploadStream.abort();
} else if (err.code === require_lib_constants.STORAGE.NO_SUCH_FILE_ERROR || err.code === _verdaccio_core.HTTP_STATUS.NOT_FOUND) this.getPackageMetadata(name, function(_err) {
if (_err) uploadStream.emit("error", _err);
else uploadStream.emit("error", err);
});
else uploadStream.emit("error", err);
});
writeStream.on("open", function() {
uploadStream.emit("open");
});
writeStream.on("success", () => {
this._updatePackage(name, function updater(data, cb) {
data._attachments[filename] = { shasum: shaOneHash.digest("hex") };
if (lodash.default.isNil(distFile) === false && lodash.default.isNil(data._distfiles[filename])) {
debug$1("restoring missing distfile record for %o served by %o", filename, distFile.registry);
data._distfiles[filename] = distFile;
}
cb(null);
}, function(err) {
if (err) uploadStream.emit("error", err);
else uploadStream.emit("success");
});
});
uploadStream.abort = function() {
writeStream.abort();
};
uploadStream.done = function() {
if (!length) {
uploadStream.emit("error", require_lib_utils.ErrorCode.getBadData("refusing to accept zero-length file"));
writeStream.abort();
} else writeStream.done();
};
uploadStream.pipe(writeStream);
return uploadStream;
}
/**
* Get a tarball.
* @param {*} name
* @param {*} filename
* @return {ReadTarball}
*/
getTarball(name, filename) {
(0, assert.default)((0, _verdaccio_utils.validateName)(filename));
const storage = this._getLocalStorage(name);
if (lodash.default.isNil(storage)) return this._createFailureStreamResponse();
return this._streamSuccessReadTarBall(storage, filename);
}
/**
* Return a stream that emits a read failure.
* @private
* @return {ReadTarball}
*/
_createFailureStreamResponse() {
const stream = new _verdaccio_streams.ReadTarball({});
process.nextTick(() => {
stream.emit("error", this._getFileNotAvailable());
});
return stream;
}
/**
* Return a stream that emits the tarball data
* @param {Object} storage
* @param {String} filename
* @private
* @return {ReadTarball}
*/
_streamSuccessReadTarBall(storage, filename) {
const stream = new _verdaccio_streams.ReadTarball({});
const readTarballStream = storage.readTarball(filename);
const e404 = require_lib_utils.ErrorCode.getNotFound;
stream.abort = function() {
if (lodash.default.isNil(readTarballStream) === false) readTarballStream.abort();
};
readTarballStream.on("error", function(err) {
if (err.code === require_lib_constants.STORAGE.NO_SUCH_FILE_ERROR || err.code === _verdaccio_core.HTTP_STATUS.NOT_FOUND) stream.emit("error", e404("no such file available"));
else stream.emit("error", err);
});
readTarballStream.on("content-length", function(content) {
stream.emit("content-length", content);
});
readTarballStream.on("open", function() {
stream.emit("open");
readTarballStream.pipe(stream);
});
return stream;
}
/**
* Retrieve a package by name.
* @param {*} name
* @param {*} callback
* @return {Function}
*/
getPackageMetadata(name, callback = () => {}) {
const storage = this._getLocalStorage(name);
if (lodash.default.isNil(storage)) return callback(require_lib_utils.ErrorCode.getNotFound());
this._readPackage(name, storage, callback);
}
getPackageMetadataAsync(name) {
return new Promise((resolve, reject) => {
this.getPackageMetadata(name, (err, metadata) => {
if (err) return reject(err);
resolve(metadata);
});
});
}
updateVersionsAsync(name, packageInfo) {
return new Promise((resolve, reject) => {
this.updateVersions(name, packageInfo, (err, result) => {
if (err) return reject(err);
resolve(result);
});
});
}
/**
* Search a local package.
* @param {*} startKey
* @param {*} options
* @return {Function}
*/
search(startKey, options) {
debug$1("options for search %o", options);
const stream = new _verdaccio_streams.ReadTarball({ objectMode: true });
this._searchEachPackage((item, cb) => {
if (item.time > parseInt(startKey, 10)) this.getPackageMetadata(item.name, (err, data) => {
if (err) return cb(err);
const result = require_lib_storage_utils.prepareSearchPackage(data, new Date(item.time).toISOString());
if (lodash.default.isNil(result) === false) stream.push(result);
cb(null);
});
else cb(null);
}, function onEnd(err) {
if (err) {
stream.emit("error", err);
return;
}
stream.end();
});
return stream;
}
/**
* Retrieve a wrapper that provide access to the package location.
* @param {Object} pkgName package name.
* @return {Object}
*/
_getLocalStorage(pkgName) {
return this.storagePlugin.getPackageStorage(pkgName);
}
/**
* Read a json file from storage.
* @param {Object} storage
* @param {Function} callback
*/
_readPackage(name, storage, callback) {
storage.readPackage(name, (err, result) => {
if (err) {
if (err.code === require_lib_constants.STORAGE.NO_SUCH_FILE_ERROR || err.code === _verdaccio_core.HTTP_STATUS.NOT_FOUND) return callback(require_lib_utils.ErrorCode.getNotFound());
return callback(this._internalError(err, require_lib_constants.STORAGE.PACKAGE_FILE_NAME, "error reading"));
}
callback(err, require_lib_storage_utils.normalizePackage(result));
});
}
/**
* Walks through each package and calls `on_package` on them.
* @param {*} onPackage
* @param {*} onEnd
*/
_searchEachPackage(onPackage, onEnd) {
if (lodash.default.isNil(this.storagePlugin.search)) {
this.logger.warn("plugin search not implemented yet");
onEnd();
} else this.storagePlugin.search(onPackage, onEnd, _verdaccio_utils.validateName);
}
/**
* Retrieve either a previous created local package or a boilerplate.
* @param {*} pkgName
* @param {*} callback
* @return {Function}
*/
_readCreatePackage(pkgName, callback) {
const storage = this._getLocalStorage(pkgName);
if (lodash.default.isNil(storage)) {
this._createNewPackage(pkgName, callback);
return;
}
storage.readPackage(pkgName, (err, data) => {
if (lodash.default.isNil(err) === false) if (err.code === require_lib_constants.STORAGE.NO_SUCH_FILE_ERROR || err.code === _verdaccio_core.HTTP_STATUS.NOT_FOUND) data = require_lib_storage_utils.generatePackageTemplate(pkgName);
else return callback(this._internalError(err, require_lib_constants.STORAGE.PACKAGE_FILE_NAME, "error reading"));
callback(null, require_lib_storage_utils.normalizePackage(data));
});
}
_createNewPackage(name, callback) {
return callback(null, require_lib_storage_utils.normalizePackage(require_lib_storage_utils.generatePackageTemplate(name)));
}
/**
* Handle internal error
* @param {*} err
* @param {*} file
* @param {*} message
* @return {Object} Error instance
*/
_internalError(err, file, message) {
this.logger.error({
err,
file
}, `${message} @{file}: @{!err.message}`);
return require_lib_utils.ErrorCode.getInternalError();
}
/**
* @param {*} name package name
* @param {*} updateHandler function(package, cb) - update function
* @param {*} callback callback that gets invoked after it's all updated
* @return {Function}
*/
_updatePackage(name, updateHandler, callback) {
const storage = this._getLocalStorage(name);
if (!storage) return callback(require_lib_utils.ErrorCode.getNotFound());
storage.updatePackage(name, updateHandler, this._writePackage.bind(this), require_lib_storage_utils.normalizePackage, callback);
}
/**
* Update the revision (_rev) string for a package.
* @param {*} name
* @param {*} json
* @param {*} callback
* @return {Function}
*/
_writePackage(name, json, callback) {
const storage = this._getLocalStorage(name);
if (lodash.default.isNil(storage)) return callback();
storage.savePackage(name, this._setDefaultRevision(json), callback);
}
_setDefaultRevision(json) {
if (lodash.default.isString(json._rev) === false) json._rev = require_lib_constants.STORAGE.DEFAULT_REVISION;
if (lodash.default.isNil(this.config._debug)) json._rev = require_lib_storage_utils.generateRevision(json._rev);
return json;
}
_deleteAttachments(storage, attachments, callback) {
debug$1("[storage/_deleteAttachments] delete attachments total: %o", attachments?.length);
const unlinkNext = function(cb) {
if (lodash.default.isEmpty(attachments)) return cb();
const attachment = attachments.shift();
storage.deletePackage(attachment, function() {
unlinkNext(cb);
});
};
unlinkNext(function() {
storage.removePackage(function(err) {
callback(err);
});
});
}
/**
* Ensure the dist file remains as the same protocol
* @param {Object} hash metadata
* @param {String} upLinkKey registry key
* @private
*/
_updateUplinkToRemoteProtocol(hash, upLinkKey) {
const tarballUrl = URL.parse(hash.url);
const uplinkUrl = URL.parse(this.config.uplinks[upLinkKey].url);
if (tarballUrl !== null && uplinkUrl !== null && uplinkUrl.host === tarballUrl.host) {
tarballUrl.protocol = uplinkUrl.protocol;
hash.registry = upLinkKey;
hash.url = tarballUrl.href;
}
}
async getSecret(config) {
const secretKey = await this.storagePlugin.getSecret();
return this.storagePlugin.setSecret(config.checkSecretKey(secretKey));
}
saveToken(token) {
if (lodash.default.isFunction(this.storagePlugin.saveToken) === false) return Promise.reject(require_lib_utils.ErrorCode.getCode(_verdaccio_core.HTTP_STATUS.SERVICE_UNAVAILABLE, _verdaccio_core.SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE));
return this.storagePlugin.saveToken(token);
}
deleteToken(user, tokenKey) {
if (lodash.default.isFunction(this.storagePlugin.deleteToken) === false) return Promise.reject(require_lib_utils.ErrorCode.getCode(_verdaccio_core.HTTP_STATUS.SERVICE_UNAVAILABLE, _verdaccio_core.SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE));
return this.storagePlugin.deleteToken(user, tokenKey);
}
readTokens(filter) {
if (lodash.default.isFunction(this.storagePlugin.readTokens) === false) return Promise.reject(require_lib_utils.ErrorCode.getCode(_verdaccio_core.HTTP_STATUS.SERVICE_UNAVAILABLE, _verdaccio_core.SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE));
return this.storagePlugin.readTokens(filter);
}
};
//#endregion
exports.default = LocalStorage;
//# sourceMappingURL=local-storage.js.map