@shockpkg/core
Version:
shockpkg core
2,278 lines (1,798 loc) • 49.8 kB
JavaScript
import _initializerDefineProperty from "@babel/runtime/helpers/initializerDefineProperty.js";
import _applyDecoratedDescriptor from "@babel/runtime/helpers/applyDecoratedDescriptor.js";
import _initializerWarningHelper from "@babel/runtime/helpers/initializerWarningHelper.js";
var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _class, _descriptor, _descriptor2, _descriptor3, _descriptor4, _descriptor5, _descriptor6, _descriptor7, _descriptor8, _descriptor9, _descriptor10, _descriptor11, _descriptor12, _descriptor13, _descriptor14, _descriptor15, _temp;
import { join as pathJoin } from 'path';
import fse from 'fs-extra';
import { MAIN_DIR, META_DIR, PACKAGE_FILE, PACKAGES_FILE, PACKAGES_URL, PACKAGES_URL_ENV, PATH_ENV, TEMP_DIR } from "./constants.mjs";
import { property } from "./decorators.mjs";
import { Dispatcher } from "./dispatcher.mjs";
import { Lock } from "./lock.mjs";
import { Packages } from "./packages.mjs";
import { Request } from "./request.mjs";
import { arrayFilterAsync, arrayMapAsync, dependSort, fileHashVerify, fileSizeVerify, lstatExists, promiseCatch, readDir, streamRequest, streamRequestDownload, zipEntryExtract } from "./util.mjs";
import { Zip } from "./zip.mjs";
/**
* Manager constructor.
*
* @param path The path, defaults to environment variable or relative.
*/
export let Manager = (_dec = property(false), _dec2 = property(false), _dec3 = property(false), _dec4 = property(false), _dec5 = property(false), _dec6 = property(false), _dec7 = property(false), _dec8 = property(false), _dec9 = property(false), _dec10 = property(false), _dec11 = property(false), _dec12 = property(false), _dec13 = property(false), _dec14 = property(false), _dec15 = property(false), (_class = (_temp = class Manager extends Object {
/**
* Package install before events.
*/
/**
* Package install after events.
*/
/**
* Package install current events.
*/
/**
* Package download before events.
*/
/**
* Package download after events.
*/
/**
* Package download progress events.
*/
/**
* Package stream before events.
*/
/**
* Package stream after events.
*/
/**
* Package stream progress events.
*/
/**
* Package extract before events.
*/
/**
* Package extract after events.
*/
/**
* Package extract progress events.
*/
/**
* Package cleanup before events.
*/
/**
* Package cleanup after events.
*/
/**
* Packages URL.
*/
/**
* Packages file.
*/
/**
* Package file.
*/
/**
* Main directory.
*/
/**
* Meta directory.
*/
/**
* Temp directory.
*/
/**
* Path environment variable name.
*/
/**
* Packages URL environment variable name.
*/
/**
* Inited flag.
*/
/**
* Destroyed flag.
*/
/**
* Exclusive access flag.
*/
/**
* Root path.
*/
/**
* Lock file instance.
*/
/**
* Packages instance.
*/
/**
* Request instance.
*/
constructor(path = null) {
super();
this.eventPackageInstallBefore = // eslint-disable-next-line no-invalid-this
new Dispatcher(this);
this.eventPackageInstallAfter = // eslint-disable-next-line no-invalid-this
new Dispatcher(this);
this.eventPackageInstallCurrent = // eslint-disable-next-line no-invalid-this
new Dispatcher(this);
this.eventPackageDownloadBefore = // eslint-disable-next-line no-invalid-this
new Dispatcher(this);
this.eventPackageDownloadAfter = // eslint-disable-next-line no-invalid-this
new Dispatcher(this);
this.eventPackageDownloadProgress = // eslint-disable-next-line no-invalid-this
new Dispatcher(this);
this.eventPackageStreamBefore = // eslint-disable-next-line no-invalid-this
new Dispatcher(this);
this.eventPackageStreamAfter = // eslint-disable-next-line no-invalid-this
new Dispatcher(this);
this.eventPackageStreamProgress = // eslint-disable-next-line no-invalid-this
new Dispatcher(this);
this.eventPackageExtractBefore = // eslint-disable-next-line no-invalid-this
new Dispatcher(this);
this.eventPackageExtractAfter = // eslint-disable-next-line no-invalid-this
new Dispatcher(this);
this.eventPackageExtractProgress = // eslint-disable-next-line no-invalid-this
new Dispatcher(this);
this.eventPackageCleanupBefore = // eslint-disable-next-line no-invalid-this
new Dispatcher(this);
this.eventPackageCleanupAfter = // eslint-disable-next-line no-invalid-this
new Dispatcher(this);
_initializerDefineProperty(this, "_packagesUrl", _descriptor, this);
_initializerDefineProperty(this, "_packagesFile", _descriptor2, this);
_initializerDefineProperty(this, "_packageFile", _descriptor3, this);
_initializerDefineProperty(this, "_mainDir", _descriptor4, this);
_initializerDefineProperty(this, "_metaDir", _descriptor5, this);
_initializerDefineProperty(this, "_tempDir", _descriptor6, this);
_initializerDefineProperty(this, "_pathEnv", _descriptor7, this);
_initializerDefineProperty(this, "_packagesUrlEnv", _descriptor8, this);
_initializerDefineProperty(this, "_inited", _descriptor9, this);
_initializerDefineProperty(this, "_destroyed", _descriptor10, this);
_initializerDefineProperty(this, "_exclusive", _descriptor11, this);
_initializerDefineProperty(this, "_path", _descriptor12, this);
_initializerDefineProperty(this, "_lock", _descriptor13, this);
_initializerDefineProperty(this, "_packages", _descriptor14, this);
_initializerDefineProperty(this, "_request", _descriptor15, this);
this._path = this._createPath(path);
const lock = this._createLock();
lock.eventCompromised.on(() => {// Do nothing, instead fail on next assert call.
});
this._lock = lock;
this._packagesUrl = this._createPackagesUrl(this._packagesUrl);
this._packages = this._createPackages();
this._request = this._createRequest();
}
/**
* Root path.
*
* @returns The path.
*/
get path() {
return this._path;
}
/**
* Packages URL.
*
* @returns The URL.
*/
get packagesUrl() {
return this._packagesUrl;
}
/**
* Packages file.
*
* @returns The file.
*/
get packagesFile() {
return this._packagesFile;
}
/**
* Package file.
*
* @returns The path.
*/
get packageFile() {
return this._packageFile;
}
/**
* Packages file path.
*
* @returns The path.
*/
get pathMetaPackages() {
return this.pathToMeta(this.packagesFile);
}
/**
* Meta directory.
*
* @returns The directory.
*/
get metaDir() {
return this._metaDir;
}
/**
* Temp directory.
*
* @returns The directory.
*/
get tempDir() {
return this._tempDir;
}
/**
* Meta directory path for root path.
*
* @returns The path.
*/
get pathMeta() {
return this.pathToMeta();
}
/**
* Instance inited and not yet destroyed.
*
* @returns Is active.
*/
get active() {
return !this._destroyed && this._inited;
}
/**
* Packages loaded.
*
* @returns Is loaded.
*/
get loaded() {
return this._packages.loaded;
}
/**
* The lock file compromised.
*
* @returns Is compromised.
*/
get lockCompromised() {
return this._lock.compromised;
}
/**
* Assert instance not inited.
*/
assertNotInited() {
// eslint-disable-next-line no-sync
this._exclusiveSync(() => {
this._assertNotInited();
});
}
/**
* Assert instance is active.
* Implies inited, not-destroyed, and lock-not-compromised assertions.
*/
assertActive() {
// eslint-disable-next-line no-sync
this._exclusiveSync(() => {
this._assertActive();
});
}
/**
* Assert instance all loaded, including the packages list.
* Implies all active assertions.
*/
assertLoaded() {
// eslint-disable-next-line no-sync
this._exclusiveSync(() => {
this._assertLoaded();
});
}
/**
* Initialize instance.
*/
async init() {
await this._exclusiveAsync(async () => this._init());
}
/**
* Destroy instance.
*/
async destroy() {
await this._exclusiveAsync(async () => this._destroy());
}
/**
* Run asyncronous function with automatic init and destroy.
*
* @param func Async function.
* @returns Return value of the async function.
*/
async with(func) {
await this.init();
let r;
try {
r = await func.call(this, this);
} finally {
await this.destroy();
}
return r;
}
/**
* Itterate over the packages.
*
* @returns Package itterator.
*/
packageItter() {
// eslint-disable-next-line no-sync
return this._exclusiveSync(() => this._packageItter());
}
/**
* Get package by the unique name.
*
* @param name Package name.
* @returns The package or null.
*/
packageByName(name) {
// eslint-disable-next-line no-sync
return this._exclusiveSync(() => this._packageByName(name));
}
/**
* Get package by the sha256 hash.
*
* @param sha256 Package sha256.
* @returns The package or null.
*/
packageBySha256(sha256) {
// eslint-disable-next-line no-sync
return this._exclusiveSync(() => this._packageBySha256(sha256));
}
/**
* Get package by the unique value.
*
* @param unique Package unique.
* @returns The package or null.
*/
packageByUnique(unique) {
// eslint-disable-next-line no-sync
return this._exclusiveSync(() => this._packageByUnique(unique));
}
/**
* Check if package is in packages collection.
*
* @param pkg Package instance.
* @returns If the package instance is present.
*/
packageIsMember(pkg) {
// eslint-disable-next-line no-sync
return this._exclusiveSync(() => this._packageIsMember(pkg));
}
/**
* Read package install receipt.
*
* @param pkg The package.
* @returns Install receipt.
*/
async packageInstallReceipt(pkg) {
return this._exclusiveAsync(async () => this._packageMetaReceiptRead(pkg));
}
/**
* Get package install file.
*
* @param pkg The package.
* @returns Path to install file.
*/
async packageInstallFile(pkg) {
return this._exclusiveAsync(async () => this._packageInstallFile(pkg));
}
/**
* Verify package install file, using size and hash.
*
* @param pkg The package.
*/
async packageInstallVerify(pkg) {
await this._exclusiveAsync(async () => this._packageInstallVerify(pkg));
}
/**
* Packages ordered by dependencies.
*
* @param pkgs Packages list.
* @returns Packages list, sorted order.
*/
packagesDependOrdered(pkgs) {
// eslint-disable-next-line no-sync
return this._exclusiveSync(() => this._packagesDependOrdered(pkgs));
}
/**
* Update the package manager installed data.
* Updates the packages list.
*
* @returns Update report.
*/
async update() {
return this._exclusiveAsync(async () => this._update());
}
/**
* Check if a package is installed.
*
* @param pkg The package.
* @returns True if already installed, else false.
*/
async isInstalled(pkg) {
return this._exclusiveAsync(async () => this._isInstalled(pkg));
}
/**
* Check if a package is installed and up-to-date.
*
* @param pkg The package.
* @returns True if already up-to-date, else false.
*/
async isCurrent(pkg) {
return this._exclusiveAsync(async () => this._isCurrent(pkg));
}
/**
* List all installed packages.
*
* @returns A list of installed package objects.
*/
async installed() {
return this._exclusiveAsync(async () => this._installed());
}
/**
* List all outdated packages.
*
* @returns The list of outdated package objects.
*/
async outdated() {
return this._exclusiveAsync(async () => this._outdated());
}
/**
* An alias for upgradeSlim.
*
* @returns List of packages upgraded.
*/
async upgrade() {
return this._exclusiveAsync(async () => this._upgrade());
}
/**
* Upgrade any outdated packages.
*
* @returns List of packages upgraded.
*/
async upgradeFull() {
return this._exclusiveAsync(async () => this._upgradeFull());
}
/**
* Upgrade any outdated packages, using slim install method.
*
* @returns List of packages upgraded.
*/
async upgradeSlim() {
return this._exclusiveAsync(async () => this._upgradeSlim());
}
/**
* An alias for installSlim.
*
* @param pkg The package.
* @returns True if was installed, false if already installed.
*/
async install(pkg) {
return this._exclusiveAsync(async () => this._install(pkg));
}
/**
* Install package, with parents.
* Returns the list of packages installed to install.
* Returns empty array if current version is already installed.
*
* @param pkg The package.
* @returns List of packages processed.
*/
async installFull(pkg) {
return this._exclusiveAsync(async () => this._installFull(pkg));
}
/**
* Install multiple package with parents, higher dependencies first.
*
* @param pkgs Packages list.
* @returns Installed list.
*/
async installFullMulti(pkgs) {
return this._exclusiveAsync(async () => this._installFullMulti(pkgs));
}
/**
* Install package, without parents.
* Returns the list of packages downloaded or extracted to install.
* Returns empty array if current version is already installed.
*
* @param pkg The package.
* @returns List of packages processed.
*/
async installSlim(pkg) {
return this._exclusiveAsync(async () => this._installSlim(pkg));
}
/**
* Install multiple package without parents, higher dependencies first.
*
* @param pkgs Packages list.
* @returns Installed list.
*/
async installSlimMulti(pkgs) {
return this._exclusiveAsync(async () => this._installSlimMulti(pkgs));
}
/**
* Remove package.
*
* @param pkg The package.
* @returns True if removed, false if nothing to remove.
*/
async remove(pkg) {
return this._exclusiveAsync(async () => this._remove(pkg));
}
/**
* Check if package name is obsolete.
*
* @param pkg The package.
* @returns True if package obslete, else false.
*/
async isObsolete(pkg) {
return this._exclusiveAsync(async () => this._isObsolete(pkg));
}
/**
* List obsolete package names.
*
* @returns A list of obsolete package names.
*/
async obsolete() {
return this._exclusiveAsync(async () => this._obsolete());
}
/**
* Cleanup all obsolete and outdated packages.
*
* @returns Lists of removed packages.
*/
async cleanup() {
return this._exclusiveAsync(async () => this._cleanup());
}
/**
* Join path on the base path.
*
* @param parts Path parts.
* @returns Joined path.
*/
pathTo(...parts) {
return pathJoin(this.path, ...parts);
}
/**
* Join path on the meta path.
*
* @param parts Path parts.
* @returns Joined path.
*/
pathToMeta(...parts) {
return this.pathTo(this.metaDir, ...parts);
}
/**
* Join path on the temp folder path.
*
* @param parts Path parts.
* @returns Joined path.
*/
pathToTemp(...parts) {
return this.pathToMeta(this.tempDir, ...parts);
}
/**
* Join path on package base path.
*
* @param pkg The package.
* @param parts Path parts.
* @returns Joined path.
*/
pathToPackage(pkg, ...parts) {
// eslint-disable-next-line no-sync
return this._exclusiveSync(() => this._pathToPackage(pkg, ...parts));
}
/**
* Join path on package meta path.
*
* @param pkg The package.
* @param parts Path parts.
* @returns Joined path.
*/
pathToPackageMeta(pkg, ...parts) {
// eslint-disable-next-line no-sync
return this._exclusiveSync(() => this._pathToPackageMeta(pkg, ...parts));
}
/**
* Join path on package base path.
*
* @param pkg The package.
* @param parts Path parts.
* @returns Joined path.
*/
_pathToPackage(pkg, ...parts) {
this._assertActive();
const name = this._packageToName(pkg, false);
return this.pathTo(name, ...parts);
}
/**
* Join path on package meta path.
*
* @param pkg The package.
* @param parts Path parts.
* @returns Joined path.
*/
_pathToPackageMeta(pkg, ...parts) {
this._assertActive();
const name = this._packageToName(pkg, false);
return this.pathTo(name, this.metaDir, ...parts);
}
/**
* Obtain exclusive access for the duration of a syncronous callback.
*
* @param func Syncronous function.
* @returns Return value of the syncronous callback.
*/
_exclusiveSync(func) {
this._assertNotExclusive();
this._exclusive = true;
let r;
try {
r = func.call(this, this);
} finally {
this._exclusive = false;
}
return r;
}
/**
* Obtain exclusive access for the duration of a asyncronous callback.
*
* @param func Asyncronous function.
* @returns Return value of the asyncronous callback.
*/
async _exclusiveAsync(func) {
this._assertNotExclusive();
this._exclusive = true;
let r;
try {
r = await func.call(this, this);
} finally {
this._exclusive = false;
}
return r;
}
/**
* Itterate over the packages.
*/
*_packageItter() {
this._assertActive();
for (const entry of this._packages.itter()) {
this._assertActive();
yield entry;
}
}
/**
* Get package by the unique name.
*
* @param name Package name.
* @returns The package or null.
*/
_packageByName(name) {
this._assertLoaded();
return this._packages.byName(name);
}
/**
* Get package by the sha256 hash.
*
* @param sha256 Package sha256.
* @returns The package or null.
*/
_packageBySha256(sha256) {
this._assertLoaded();
return this._packages.bySha256(sha256);
}
/**
* Get package by the unique value.
*
* @param unique Package unique.
* @returns The package or null.
*/
_packageByUnique(unique) {
this._assertLoaded();
return this._packages.byUnique(unique);
}
/**
* Check if package is in packages collection.
*
* @param pkg Package instance.
* @returns If the package instance is present.
*/
_packageIsMember(pkg) {
this._assertLoaded();
return this._packages.has(pkg);
}
/**
* Get package install file.
*
* @param pkg The package.
* @returns Path to install file.
*/
async _packageInstallFile(pkg) {
this._assertLoaded();
pkg = this._packageToPackage(pkg);
const data = await this._packageMetaReceiptRead(pkg);
return this._pathToPackage(pkg, data.file);
}
/**
* Verify package install file, using size and hash.
*
* @param pkg The package.
*/
async _packageInstallVerify(pkg) {
this._assertLoaded();
pkg = this._packageToPackage(pkg);
const data = await this._packageMetaReceiptRead(pkg);
const {
sha256,
file,
size
} = data;
const filePath = this._pathToPackage(pkg, file);
await fileSizeVerify(filePath, size);
await fileHashVerify(filePath, [{
algorithm: 'sha256',
encoding: 'hex',
digest: sha256
}]);
}
/**
* Get package object by object, name, or hash.
* If package object is passed, check that object is known.
* Throw error if package is unknown.
*
* @param pkg The package.
* @returns Package object.
*/
_packageToPackage(pkg) {
this._assertLoaded();
let r;
if (typeof pkg === 'string') {
const p = this._packageByUnique(pkg);
if (!p) {
throw new Error(`Unknown package: ${pkg}`);
}
r = p;
} else {
this._assertpackageIsMember(pkg);
r = pkg;
}
return r;
}
/**
* Get package name by object, name, or hash.
* If package object is passed, check that object is known.
* If string is passed and unknown, returns string.
*
* @param pkg The package.
* @param mustExist Must exist.
* @returns Package object.
*/
_packageToName(pkg, mustExist = true) {
this._assertLoaded();
let r;
if (typeof pkg === 'string') {
const pkgObj = this._packageByUnique(pkg);
if (!pkgObj && mustExist) {
throw new Error(`Unknown package: ${pkg}`);
}
r = pkgObj ? pkgObj.name : pkg;
} else {
this._assertpackageIsMember(pkg);
r = pkg.name;
}
return r;
}
/**
* List package parent packages.
*
* @param pkg The package.
* @returns Packages list.
*/
_packageParents(pkg) {
this._assertLoaded();
pkg = this._packageToPackage(pkg);
const r = [];
for (let p = pkg.parent; p; p = p.parent) {
r.push(p);
}
return r;
}
/**
* List package parent packages not updated.
*
* @param pkg The package.
* @returns Packages list.
*/
async _packageParentsNotUpdated(pkg) {
this._assertLoaded();
pkg = this._packageToPackage(pkg);
const r = [];
for (let p = pkg.parent; p; p = p.parent) {
// eslint-disable-next-line no-await-in-loop
if (await this._isCurrent(p)) {
break;
}
r.push(p);
}
return r;
}
/**
* List the packages that need to be installed.
*
* @param pkg The package.
* @returns Package root or null and the children list.
*/
async _packageInstallList(pkg) {
this._assertLoaded();
pkg = this._packageToPackage(pkg);
const r = await this._packageParentsNotUpdated(pkg);
r.reverse().push(pkg);
return r;
}
/**
* Packages ordered by dependencies.
*
* @param pkgs Packages list.
* @returns Packages list, sorted order.
*/
_packagesDependOrdered(pkgs) {
this._assertLoaded();
const list = pkgs.map(pkg => this._packageToPackage(pkg));
return dependSort(list, pkg => this._packageParents(pkg));
}
/**
* Read package installed receipt.
*
* @param pkg The package.
* @returns Package object.
*/
async _packageMetaReceiptRead(pkg) {
this._assertLoaded();
const name = this._packageToName(pkg, false);
const pkgf = this._pathToPackageMeta(name, this.packageFile);
const r = await promiseCatch(fse.readJson(pkgf), null);
if (!r) {
throw new Error('Package is not installed');
}
return r;
}
/**
* Write package installed receipt.
*
* @param pkg The package.
*/
async _packageMetaReceiptWrite(pkg) {
this._assertLoaded();
pkg = this._packageToPackage(pkg);
const name = this._packageToName(pkg);
const pkgf = this._pathToPackageMeta(name, this.packageFile);
const receipt = await this._packageMetaReceiptFromPackage(pkg);
await fse.outputJson(pkgf, receipt, {
spaces: '\t'
});
}
/**
* CReate package installed receipt object from a package.
*
* @param pkg The package.
* @returns Receipt object.
*/
// eslint-disable-next-line @typescript-eslint/require-await
async _packageMetaReceiptFromPackage(pkg) {
this._assertLoaded();
pkg = this._packageToPackage(pkg);
const r = {
name: pkg.name,
file: pkg.file,
size: pkg.size,
sha256: pkg.sha256,
source: pkg.source
};
return r;
}
/**
* Check if package meta directory exists.
*
* @param pkg The package.
* @returns True if the meta directory path, else false.
*/
async _packageMetaDirExists(pkg) {
this._assertLoaded();
const dir = this._pathToPackageMeta(pkg);
return fse.pathExists(dir);
}
/**
* Ensure package directory exists.
*
* @param pkg The package.
*/
async _packageDirsEnsure(pkg) {
this._assertLoaded();
pkg = this._packageToPackage(pkg);
const dir = this._pathToPackage(pkg);
const dirMeta = this._pathToPackageMeta(pkg);
await fse.ensureDir(dir);
await fse.ensureDir(dirMeta);
}
/**
* Assert instance not inited.
*/
_assertNotInited() {
if (this._inited) {
throw new Error('Instance initialized');
}
}
/**
* Assert instance is inited.
*/
_assertInited() {
if (!this._inited) {
throw new Error('Instance uninitialized');
}
}
/**
* Assert instance not destroyed.
*/
_assertNotDestroyed() {
if (this._destroyed) {
throw new Error('Instance destroyed');
}
}
/**
* Assert instance lock was not compromised.
*/
_assertLockNotCompromised() {
if (this.lockCompromised) {
throw new Error('Instance lock file compromised');
}
}
/**
* Assert instance is active.
* Implies inited, not-destroyed, and lock-not-compromised assertions.
*/
_assertActive() {
// Check everything is active in order they should report failure.
this._assertInited();
this._assertNotDestroyed();
this._assertLockNotCompromised();
}
/**
* Assert instance all loaded, including the packages list.
* Implies all active assertions.
*/
_assertLoaded() {
this._assertActive();
if (!this.loaded) {
throw new Error('Packages list not loaded');
}
}
/**
* Assert not current running exclusive method.
*/
_assertNotExclusive() {
if (this._exclusive) {
throw new Error('Already running exclusive method');
}
}
/**
* Assert the package is in packages collection.
*
* @param pkg Package instance.
*/
_assertpackageIsMember(pkg) {
this._assertLoaded();
this._packages.assertHas(pkg);
}
/**
* Assert correct status code.
*
* @param expected Expected status code.
* @param statusCode The actual status code.
*/
_assertStatusCode(expected, statusCode) {
if (statusCode === expected) {
return;
}
throw new Error(`Unexpected status code: ${statusCode} expected: ${expected}`);
}
/**
* Assert correct content length.
*
* @param expected Expected content length, as a number.
* @param contentLength The actual content length as string.
*/
_assertContentLength(expected, contentLength) {
const size = +contentLength;
if (size === expected) {
return;
}
throw new Error(`Unexpected content-length: ${contentLength} expected: ${expected}`);
}
/**
* Initialize instance.
*/
async _init() {
this._assertNotInited();
await this._ensureDirs();
await this._lock.aquire();
await this._packages.readIfExists();
this._inited = true;
}
/**
* Destroy instance.
*/
async _destroy() {
// Destroy should always work only once if instance was inited.
this._assertInited();
this._assertNotDestroyed(); // Lock may have been compromised, only release if currently held.
if (this._lock.held) {
await this._lock.release();
}
this._destroyed = true;
}
/**
* Update the package manager.
* Updates the packages list.
*
* @returns Update report.
*/
async _update() {
this._assertActive();
return this._updatePackages();
}
/**
* Check if a package is installed.
*
* @param pkg The package.
* @returns True if already installed, else false.
*/
async _isInstalled(pkg) {
this._assertLoaded();
pkg = this._packageToPackage(pkg);
try {
await this._packageMetaReceiptRead(pkg);
} catch (err) {
return false;
}
return true;
}
/**
* Check if a package is installed and up-to-date.
*
* @param pkg The package.
* @returns True if already up-to-date, else false.
*/
async _isCurrent(pkg) {
this._assertLoaded();
pkg = this._packageToPackage(pkg);
let data = null;
try {
data = await this._packageMetaReceiptRead(pkg);
} catch (err) {
return false;
}
return !!(data.sha256 === pkg.sha256 && data.size === pkg.size && data.file === pkg.file && data.name === pkg.name);
}
/**
* List all installed packages.
*
* @returns A list of installed package objects.
*/
async _installed() {
this._assertLoaded();
const dirList = await this._packagesDirList();
const filtered = await arrayFilterAsync(dirList, async entry => {
const pkg = this._packageByName(entry);
const installed = pkg && (await this._isInstalled(pkg));
return installed;
});
return this._packagesDependOrdered(filtered);
}
/**
* List all outdated packages.
*
* @returns The list of outdated package objects.
*/
async _outdated() {
this._assertLoaded();
const dirList = await this._packagesDirList();
const filtered = await arrayFilterAsync(dirList, async entry => {
const pkg = this._packageByName(entry);
return pkg && !(await this._isCurrent(pkg));
});
return this._packagesDependOrdered(filtered);
}
/**
* An alias for upgradeSlim.
*
* @returns List of packages upgraded.
*/
async _upgrade() {
return this._upgradeSlim();
}
/**
* Upgrade any outdated packages.
*
* @returns List of packages upgraded.
*/
async _upgradeFull() {
this._assertLoaded();
const outdated = await this._outdated();
return this._installFullMulti(outdated);
}
/**
* Upgrade any outdated packages, using slim install method.
*
* @returns List of packages upgraded.
*/
async _upgradeSlim() {
this._assertLoaded();
const outdated = await this._outdated();
return this._installSlimMulti(outdated);
}
/**
* An alias for installSlim.
*
* @param pkg The package.
* @returns True if was installed, false if already installed.
*/
async _install(pkg) {
return this._installSlim(pkg);
}
/**
* Install package, with parents.
* Returns the list of packages installed to install.
* Returns empty array if current version is already installed.
*
* @param pkg The package.
* @returns List of packages processed.
*/
async _installFull(pkg) {
this._assertLoaded();
pkg = this._packageToPackage(pkg); // If current version is installed, skip.
const installed = await this._isCurrent(pkg);
if (installed) {
// eslint-disable-next-line no-sync
this.eventPackageInstallCurrent.triggerSync({
package: pkg,
method: 'slim'
});
return [];
} // List packages to install.
const list = await this._packageInstallList(pkg);
const r = [];
for (const p of list) {
// eslint-disable-next-line no-sync
this.eventPackageInstallBefore.triggerSync({
package: pkg,
method: 'full'
});
const {
parent
} = p;
const tmpFile = this.pathToTemp(`${p.name}.part`);
const outFile = this._pathToPackage(p, p.file); // eslint-disable-next-line no-await-in-loop
const inIns = await this._isInstalled(p); // eslint-disable-next-line no-await-in-loop
const oldFile = inIns ? await this._packageInstallFile(p) : null; // Download or extract, remove temporary directory on failure.
// Write the package file last, means successful install.
try {
// eslint-disable-next-line no-await-in-loop
await this._tempDirEnsure(true);
if (parent) {
const pF = this._pathToPackage(parent, parent.file); // eslint-disable-next-line no-await-in-loop
await this._packageExtract(p, tmpFile, pF);
} else {
// eslint-disable-next-line no-await-in-loop
await this._packageDownload(p, tmpFile);
} // eslint-disable-next-line no-await-in-loop
await this._packageDirsEnsure(p);
if (oldFile) {
// eslint-disable-next-line no-await-in-loop
await fse.remove(oldFile);
} // eslint-disable-next-line no-await-in-loop
await fse.move(tmpFile, outFile); // eslint-disable-next-line no-await-in-loop
await this._packageMetaReceiptWrite(p);
} finally {
// eslint-disable-next-line no-await-in-loop
await this._tempDirRemove();
} // eslint-disable-next-line no-sync
this.eventPackageInstallAfter.triggerSync({
package: pkg,
method: 'full'
});
r.push(p);
}
return r;
}
/**
* Install multiple package with parents, higher dependencies first.
*
* @param pkgs Packages list.
* @returns Installed list.
*/
async _installFullMulti(pkgs) {
this._assertLoaded();
const list = this._packagesDependOrdered(pkgs);
return await arrayMapAsync(list, async pkg => ({
package: pkg,
installed: await this._installFull(pkg)
}));
}
/**
* Install package, without parents.
* Returns the list of packages downloaded or extracted to install.
* Returns empty array if current version is already installed.
*
* @param pkg The package.
* @returns List of packages processed.
*/
async _installSlim(pkg) {
this._assertLoaded();
pkg = this._packageToPackage(pkg); // If current version is installed, skip.
const installed = await this._isCurrent(pkg);
if (installed) {
// eslint-disable-next-line no-sync
this.eventPackageInstallCurrent.triggerSync({
package: pkg,
method: 'slim'
});
return [];
} // List packages to install.
const list = await this._packageInstallList(pkg); // If first root and not only, extract without downloading all.
let stream = false;
if (list.length > 1 && !list[0].parent) {
stream = true;
list.shift();
}
const outFile = this._pathToPackage(pkg, pkg.file);
const fileTmpBase = this.pathToTemp(pkg.name);
const fileTmp = i => `${fileTmpBase}.${i}.part`;
const oldFile = (await this._isInstalled(pkg)) ? await this._packageInstallFile(pkg) : null; // eslint-disable-next-line no-sync
this.eventPackageInstallBefore.triggerSync({
package: pkg,
method: 'slim'
});
const r = [];
try {
await this._tempDirEnsure(true);
await this._packageDirsEnsure(pkg);
let i = 0;
let tmpFileP = '';
let tmpFile = '';
for (const p of list) {
tmpFile = fileTmp(i++);
const {
parent
} = p; // If streaming from a root package, handle that.
if (stream) {
// eslint-disable-next-line no-await-in-loop
await this._packageStream(p, tmpFile);
stream = false;
} else {
// Use previous temp file if present.
// Else use parent file if not root file.
// A root package that is not streamed will be downloaded.
const archive = tmpFileP || (parent ? this._pathToPackage(parent, parent.file) : null);
if (archive) {
// eslint-disable-next-line no-await-in-loop
await this._packageExtract(p, tmpFile, archive);
} else {
// eslint-disable-next-line no-await-in-loop
await this._packageDownload(p, tmpFile);
}
} // Remove previous temporary file if present.
if (tmpFileP) {
// eslint-disable-next-line no-await-in-loop
await fse.remove(tmpFileP);
}
tmpFileP = tmpFile;
r.push(p);
} // Move the final file into place and write package file.
// Write the package file last, means successful install.
await this._packageDirsEnsure(pkg);
if (oldFile) {
await fse.remove(oldFile);
}
await fse.move(tmpFile, outFile);
await this._packageMetaReceiptWrite(pkg);
} finally {
await this._tempDirRemove();
} // eslint-disable-next-line no-sync
this.eventPackageInstallAfter.triggerSync({
package: pkg,
method: 'slim'
});
return r;
}
/**
* Install multiple package without parents, higher dependencies first.
*
* @param pkgs Packages list.
* @returns Installed list.
*/
async _installSlimMulti(pkgs) {
this._assertLoaded();
const list = this._packagesDependOrdered(pkgs);
return await arrayMapAsync(list, async pkg => ({
package: pkg,
installed: await this._installSlim(pkg)
}));
}
/**
* Remove package.
*
* @param pkg The package.
* @returns True if removed, false if nothing to remove.
*/
async _remove(pkg) {
this._assertLoaded();
const dir = this._pathToPackage(pkg);
const stat = await lstatExists(dir);
if (!stat) {
return false;
}
const dirMeta = this._pathToPackageMeta(pkg); // Remove meta directory first, avoid partial installed state.
await fse.remove(dirMeta);
await fse.remove(dir);
return true;
}
/**
* Check if package name is obsolete.
*
* @param pkg The package.
* @returns True if package obslete, else false.
*/
async _isObsolete(pkg) {
this._assertLoaded();
const r = !this._packageByName(pkg) && (await this._packageMetaDirExists(pkg));
return r;
}
/**
* List obsolete package names.
*
* @returns A list of obsolete package names.
*/
async _obsolete() {
this._assertLoaded();
const dirList = await this._packagesDirList();
return arrayFilterAsync(dirList, async entry => this._isObsolete(entry));
}
/**
* Cleanup all obsolete and outdated packages.
*
* @returns Lists of removed packages.
*/
async _cleanup() {
this._assertLoaded(); // Remove the temporary directory if present.
await this._tempDirRemove(); // Remove the obsolete packages.
const obsolete = await this._obsolete();
return await arrayMapAsync(obsolete, async pkg => {
// eslint-disable-next-line no-sync
this.eventPackageCleanupBefore.triggerSync({
package: pkg
});
const removed = await this._remove(pkg); // eslint-disable-next-line no-sync
this.eventPackageCleanupAfter.triggerSync({
package: pkg,
removed
});
return {
package: pkg,
removed
};
});
}
/**
* List all packages in the directory.
* Only those directories with the meta directory are returned.
* Dot directories are also always skipped.
*
* @returns List of all recognized package directories.
*/
async _packagesDirList() {
this._assertLoaded();
const dirList = await readDir(this.path, false);
return arrayFilterAsync(dirList, async entry => this._packageMetaDirExists(entry));
}
/**
* Extract package from archive path.
*
* @param pkg The package.
* @param file Out file.
* @param archive Archive file.
*/
async _packageExtract(pkg, file, archive) {
this._assertLoaded();
pkg = this._packageToPackage(pkg);
const zip = this._createZip();
await zip.openFile(archive);
await this._packageExtractZip(pkg, file, zip);
}
/**
* Extract package from zip instance.
*
* @param pkg The package.
* @param file Out file.
* @param zip Archive file.
*/
async _packageExtractZip(pkg, file, zip) {
this._assertLoaded();
const pkgO = pkg = this._packageToPackage(pkg);
const {
source,
size,
sha256
} = pkg; // eslint-disable-next-line no-sync
this.eventPackageExtractBefore.triggerSync({
package: pkgO
});
let found = false;
await zip.read(async entry => {
if (entry.path !== source) {
return false;
}
let read = 0; // eslint-disable-next-line no-sync
this.eventPackageExtractProgress.triggerSync({
package: pkgO,
total: size,
amount: 0
});
await zipEntryExtract(entry, file, size, [{
algorithm: 'sha256',
encoding: 'hex',
digest: sha256
}], data => {
read += data.length; // eslint-disable-next-line no-sync
this.eventPackageExtractProgress.triggerSync({
package: pkgO,
total: size,
amount: read
});
});
found = true;
return true;
});
if (!found) {
throw new Error(`Failed to locate for extraction: ${source}`);
} // eslint-disable-next-line no-sync
this.eventPackageExtractAfter.triggerSync({
package: pkgO
});
}
/**
* Download package.
*
* @param pkg The package.
* @param file Out file.
*/
async _packageDownload(pkg, file) {
this._assertLoaded();
const pkgO = pkg = this._packageToPackage(pkg);
const {
size,
sha256
} = pkg; // eslint-disable-next-line no-sync
this.eventPackageDownloadBefore.triggerSync({
package: pkgO
});
let read = 0;
await streamRequestDownload(this._request.stream({
url: pkg.source
}), file, size, [{
algorithm: 'sha256',
encoding: 'hex',
digest: sha256
}], response => {
const {
statusCode,
headers
} = response;
const contentLength = headers['content-length'];
this._assertStatusCode(200, statusCode);
if (contentLength) {
this._assertContentLength(size, contentLength);
} // eslint-disable-next-line no-sync
this.eventPackageDownloadProgress.triggerSync({
package: pkgO,
total: size,
amount: 0
});
}, data => {
read += data.length; // eslint-disable-next-line no-sync
this.eventPackageDownloadProgress.triggerSync({
package: pkgO,
total: size,
amount: read
});
}); // eslint-disable-next-line no-sync
this.eventPackageDownloadAfter.triggerSync({
package: pkgO
});
}
/**
* Stream package out of root package.
*
* @param pkg The package.
* @param file Out file.
*/
async _packageStream(pkg, file) {
this._assertLoaded();
pkg = this._packageToPackage(pkg);
const {
parent
} = pkg;
if (!parent || parent.parent) {
throw new Error('Can only stream direct children of root packages');
} // Extract from zip without downloading the full zip.
const streamer = this._packageStreamStreamer(parent);
const zip = this._createZip();
await zip.openStreamer(streamer, parent.size);
await this._packageExtractZip(pkg, file, zip);
}
/**
* Create streamer function for a package.
* Only works for a root package.
*
* @param pkg The package.
* @returns Streamer function.
*/
_packageStreamStreamer(pkg) {
this._assertLoaded();
const pkgO = pkg = this._packageToPackage(pkg);
const {
source
} = pkg;
return (start, end) => {
const size = end - start; // eslint-disable-next-line no-sync
this.eventPackageStreamBefore.triggerSync({
package: pkgO
});
let read = 0;
const stream = this._request.stream({
url: source,
headers: {
Range: `bytes=${start}-${end - 1}`
}
});
streamRequest(stream, null, null, response => {
const {
statusCode,
headers
} = response;
const contentLength = headers['content-length'];
this._assertStatusCode(206, statusCode);
if (contentLength) {
this._assertContentLength(size, contentLength);
} // eslint-disable-next-line no-sync
this.eventPackageStreamProgress.triggerSync({
package: pkgO,
total: size,
amount: 0
});
}, data => {
read += data.length; // eslint-disable-next-line no-sync
this.eventPackageStreamProgress.triggerSync({
package: pkgO,
total: size,
amount: read
});
}).then(() => {
// eslint-disable-next-line no-sync
this.eventPackageStreamAfter.triggerSync({
package: pkgO
});
}).catch(() => {// Do nothing, let ZIP library handle stream errors.
}); // Workaround for type issue.
return stream;
};
}
/**
* Request the packages file.
*
* @returns File contents as string.
*/
async _requestPackages() {
this._assertActive(); // Headers and gzip to avoid compression and reduce transfer size.
const {
response,
body
} = await this._request.promise({
url: this.packagesUrl,
headers: {
'Cache-Control': 'max-age=0',
Pragma: 'no-cache'
},
gzip: true
});
const {
statusCode
} = response;
this._assertStatusCode(200, statusCode);
if (typeof body !== 'string') {
throw new Error(`Unexpected response body type: ${typeof body}`);
}
return body;
}
/**
* Update the packages list.
*
* @returns Update report.
*/
async _updatePackages() {
this._assertActive(); // Read data, update list, write list to file, return report.
const data = await this._requestPackages();
const report = this._packages.update(data);
await this._packages.write();
return report;
}
/**
* Ensure base directories exists.
*/
async _ensureDirs() {
await fse.ensureDir(this.path);
await fse.ensureDir(this.pathMeta);
}
/**
* Ensure temp directory exists.
*
* @param clean Clean existing.
*/
async _tempDirEnsure(clean = false) {
if (clean) {
await this._tempDirRemove();
}
await fse.ensureDir(this.pathToTemp());
}
/**
* Ensure temp directory removed.
*/
async _tempDirRemove() {
await fse.remove(this.pathToTemp());
}
/**
* Create the main path.
*
* @param path The path, defaults to environment variable or relative.
* @returns Main path.
*/
_createPath(path) {
// Use specified, or environment variable, or relative default.
// eslint-disable-next-line no-process-env
return path || process.env[this._pathEnv] || this._mainDir;
}
/**
* Create the packages URL.
*
* @param defaultUrl The default URL if the environment variable not set.
* @returns Packages URL.
*/
_createPackagesUrl(defaultUrl) {
// eslint-disable-next-line no-process-env
return process.env[this._packagesUrlEnv] || defaultUrl;
}
/**
* Create the Lock instance.
*
* @returns Lock instance.
*/
_createLock() {
return new Lock(this.pathMeta);
}
/**
* Create the Packages instance.
*
* @returns Packages instance.
*/
_createPackages() {
return new Packages(this.pathMetaPackages);
}
/**
* Create the Request instance.
*
* @returns Request instance.
*/
_createRequest() {
return new Request();
}
/**
* Create a Zip instance.
*
* @returns Zip instance.
*/
_createZip() {
return new Zip();
}
}, _temp), (_descriptor = _applyDecoratedDescriptor(_class.prototype, "_packagesUrl", [_dec], {
configurable: true,
enumerable: true,
writable: true,
initializer: function () {
return PACKAGES_URL;
}
}), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, "_packagesFile", [_dec2], {
configurable: true,
enumerable: true,
writable: true,
initializer: function () {
return PACKAGES_FILE;
}
}), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, "_packageFile", [_dec3], {
configurable: true,
enumerable: true,
writable: true,
initializer: function () {
return PACKAGE_FILE;
}
}), _descriptor4 = _applyDecoratedDescriptor(_class.prototype, "_mainDir", [_dec4], {
configurable: true,
enumerable: true,
writable: true,
initializer: function () {
return MAIN_DIR;
}
}), _descriptor5 = _applyDecoratedDescriptor(_class.prototype, "_metaDir", [_dec5], {
configurable: true,
enumerable: true,
writable: true,
initializer: function () {
return META_DIR;
}
}), _descriptor6 = _applyDecoratedDescriptor(_class.prototype, "_tempDir", [_dec6], {
configurable: true,
enumerable: true,
writable: true,
initializer: function () {
return TEMP_DIR;
}
}), _descriptor7 = _applyDecoratedDescriptor(_class.prototype, "_pathEnv", [_dec7], {
configurable: true,
enumerable: true,
writable: true,
initializer: function () {
return PATH_ENV;
}
}), _descriptor8 = _applyDecoratedDescriptor(_class.prototype, "_packagesUrlEnv", [_dec8], {
configurable: true,
enumerable: true,
writable: true,
initializer: function () {
return PACKAGES_URL_ENV;
}
}), _descriptor9 = _applyDecoratedDescriptor(_class.prototype, "_inited", [_dec9], {
configurable: true,
enumerable: true,
writable: true,
initializer: function () {
return false;
}
}), _descriptor10 = _applyDecoratedDescriptor(_class.prototype, "_destroyed", [_dec10], {
configurable: true,
enumerable: true,
writable: true,
initializer: function () {
return false;
}
}), _descriptor11 = _applyDecoratedDescriptor(_class.prototype, "_exclusive", [_dec11], {
configurable: true,
enumerable: true,
writable: true,
initializer: function () {
return false;
}
}), _descriptor12 = _applyDecoratedDescriptor(_class.prototype, "_path", [_dec12], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor13 = _applyDecoratedDescriptor(_class.prototype, "_lock", [_dec13], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor14 = _applyDecoratedDescriptor(_class.prototype, "_packages", [_dec14], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor15 = _applyDecoratedDescriptor(_class.prototype, "_request", [_dec15], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
})), _class));
//# sourceMappingURL=manager.mjs.map