227 lines
7.1 KiB
TypeScript
227 lines
7.1 KiB
TypeScript
import * as plugins from './smartnpm.plugins';
|
|
import * as paths from './smartnpm.paths';
|
|
|
|
// interfaces
|
|
import { ISearchObject } from './smartnpm.interfaces';
|
|
|
|
// classes
|
|
import { NpmPackage } from './smartnpm.classes.npmpackage';
|
|
import { ICacheDescriptor, RegistryCache } from './smartnpm.classes.registrycache';
|
|
|
|
export interface INpmRegistryConstructorOptions {
|
|
npmRegistryUrl?: string;
|
|
}
|
|
|
|
export class NpmRegistry {
|
|
public options: INpmRegistryConstructorOptions;
|
|
public registryCache: RegistryCache;
|
|
private searchDomain = 'https://api.npms.io/v2/search?q=';
|
|
|
|
constructor(optionsArg: INpmRegistryConstructorOptions = {}) {
|
|
const defaultOptions: INpmRegistryConstructorOptions = {
|
|
npmRegistryUrl: 'https://registry.npmjs.org',
|
|
};
|
|
this.options = {
|
|
...defaultOptions,
|
|
...optionsArg,
|
|
};
|
|
this.registryCache = new RegistryCache(this);
|
|
}
|
|
|
|
/**
|
|
* gets info about a package
|
|
* @param packageName
|
|
*/
|
|
public async getPackageInfo(packageName: string): Promise<NpmPackage> {
|
|
const fullMetadata = await plugins.packageJson(packageName, {
|
|
registryUrl: this.options.npmRegistryUrl,
|
|
fullMetadata: true,
|
|
}).catch(err => {
|
|
console.log(err);
|
|
return null;
|
|
});
|
|
const versionData = await plugins.packageJson(packageName, {
|
|
registryUrl: this.options.npmRegistryUrl,
|
|
allVersions: true
|
|
});
|
|
const npmPackage = await NpmPackage.createFromFullMetadataAndVersionData(this, fullMetadata, versionData as any);
|
|
return npmPackage;
|
|
}
|
|
|
|
/**
|
|
* saves a package to disk
|
|
* @param packageName
|
|
* @param targetDir
|
|
*/
|
|
public async savePackageToDisk(packageName: string, targetDir: string): Promise<void> {
|
|
plugins.smartfile.fs.ensureDirSync(paths.nogitDir);
|
|
const npmPackage = await this.getPackageInfo(packageName);
|
|
await npmPackage.saveToDisk(targetDir);
|
|
}
|
|
|
|
/**
|
|
* gets a file from a package as Smartfile
|
|
*/
|
|
public async getFileFromPackage(packageNameArg: string, filePathArg: string, optionsArg?: {
|
|
distTag?: string;
|
|
version?: string;
|
|
}): Promise<plugins.smartfile.Smartfile> {
|
|
// lets create a cache descriptor
|
|
const cacheDescriptor: ICacheDescriptor = {
|
|
registryUrl: this.options.npmRegistryUrl,
|
|
packageName: packageNameArg,
|
|
filePath: filePathArg,
|
|
distTag: optionsArg?.distTag,
|
|
version: optionsArg?.version
|
|
};
|
|
|
|
// lets see if we have something cached
|
|
const cachedFile: plugins.smartfile.Smartfile = await this.registryCache.getCachedFile(cacheDescriptor);
|
|
|
|
// lets handle both occasions
|
|
if (!cachedFile) {
|
|
const npmPackage = await this.getPackageInfo(packageNameArg);
|
|
if (!optionsArg?.version && !optionsArg?.distTag) {
|
|
const latestAvailable = npmPackage.allDistTags.find(packageArg => packageArg.name === 'latest');
|
|
if (!latestAvailable) {
|
|
optionsArg = {
|
|
version: npmPackage.getBestMatchingVersion('*')
|
|
};
|
|
}
|
|
}
|
|
const fileResult = await npmPackage.getFileFromPackage(filePathArg, optionsArg);
|
|
this.registryCache.cacheSmartFile(cacheDescriptor, fileResult);
|
|
return fileResult;
|
|
} else {
|
|
return cachedFile;
|
|
}
|
|
}
|
|
|
|
public async getFilesFromPackage(packageNameArg: string, filePath: string, optionsArg?: {
|
|
distTag?: string;
|
|
version?: string;
|
|
}): Promise<plugins.smartfile.Smartfile[]> {
|
|
const npmPackage = await this.getPackageInfo(packageNameArg);
|
|
if (!optionsArg?.version && !optionsArg?.distTag) {
|
|
const latestAvailable = npmPackage.allDistTags.find(packageDistTagArg => packageDistTagArg.name === 'latest');
|
|
if (!latestAvailable) {
|
|
optionsArg = {
|
|
version: npmPackage.getBestMatchingVersion('*')
|
|
};
|
|
}
|
|
}
|
|
return npmPackage.getFilesFromPackage(filePath, optionsArg);
|
|
}
|
|
|
|
public async getPackageAsSmartfileVirtualDir(packageNameArg: string): Promise<plugins.smartfile.VirtualDirectory> {
|
|
/**
|
|
* TODO: rewrite as memory only
|
|
*/
|
|
const baseDir = plugins.path.join(paths.nogitDir, packageNameArg.replace('/', '__'));
|
|
await plugins.smartfile.fs.ensureDir(baseDir);
|
|
await this.savePackageToDisk(packageNameArg, baseDir);
|
|
const virtualDir = await plugins.smartfile.VirtualDirectory.fromFsDirPath(baseDir);
|
|
await plugins.smartfile.fs.remove(baseDir);
|
|
return virtualDir;
|
|
}
|
|
|
|
/**
|
|
* searches for a package on npm
|
|
* @param searchObjectArg
|
|
*/
|
|
public async searchOnNpm(searchObjectArg: ISearchObject) {
|
|
if (this.options.npmRegistryUrl !== 'https://registry.npmjs.org') {
|
|
throw Error(`cannot search registries other than registry.gitlab.com`);
|
|
}
|
|
let searchString = '';
|
|
const addToSearchString = (addStringArg: string) => {
|
|
searchString = `${searchString}+${addStringArg}`;
|
|
};
|
|
|
|
// name
|
|
if (searchObjectArg.name) {
|
|
searchString = `${searchObjectArg.name}`;
|
|
}
|
|
|
|
// metadata
|
|
if (searchObjectArg.author) {
|
|
addToSearchString(`author:${searchObjectArg.author}`);
|
|
}
|
|
if (searchObjectArg.maintainer) {
|
|
addToSearchString(`maintainer:${searchObjectArg.maintainer}`);
|
|
}
|
|
if (searchObjectArg.scope) {
|
|
addToSearchString(`scope:${searchObjectArg.scope}`);
|
|
}
|
|
|
|
// status
|
|
if (searchObjectArg.deprecated) {
|
|
if (searchObjectArg.deprecated === true) {
|
|
addToSearchString(`is:deprecated`);
|
|
} else {
|
|
addToSearchString(`not:deprecated`);
|
|
}
|
|
}
|
|
if (searchObjectArg.unstable) {
|
|
if (searchObjectArg.unstable === true) {
|
|
addToSearchString(`is:unstable`);
|
|
} else {
|
|
addToSearchString(`not:unstable`);
|
|
}
|
|
}
|
|
if (searchObjectArg.insecure) {
|
|
if (searchObjectArg.insecure === true) {
|
|
addToSearchString(`is:insecure`);
|
|
} else {
|
|
addToSearchString(`not:insecure`);
|
|
}
|
|
}
|
|
|
|
// search behaviour
|
|
if (searchObjectArg.boostExact) {
|
|
addToSearchString(`boost-exact:${searchObjectArg.boostExact}`);
|
|
}
|
|
if (searchObjectArg.scoreEffect) {
|
|
addToSearchString(`score-effect:${searchObjectArg.scoreEffect}`);
|
|
}
|
|
|
|
// analytics
|
|
if (searchObjectArg.qualityWeight) {
|
|
addToSearchString(`author:${searchObjectArg.qualityWeight}`);
|
|
}
|
|
if (searchObjectArg.popularityWeight) {
|
|
addToSearchString(`author:${searchObjectArg.popularityWeight}`);
|
|
}
|
|
if (searchObjectArg.maintenanceWeight) {
|
|
addToSearchString(`author:${searchObjectArg.maintenanceWeight}`);
|
|
}
|
|
|
|
console.log(
|
|
`info: Search on npm for ${plugins.consolecolor.coloredString(searchString, 'pink')}`
|
|
);
|
|
|
|
let body: any;
|
|
try {
|
|
const response = await plugins.smartrequest.getJson(this.searchDomain + searchString, {});
|
|
body = response.body;
|
|
} catch {
|
|
// we do nothing
|
|
}
|
|
|
|
// lets create the packageArray
|
|
const packageArray: NpmPackage[] = [];
|
|
|
|
// if request failed just return it empty
|
|
if (!body || typeof body === 'string') {
|
|
return packageArray;
|
|
}
|
|
|
|
for (const packageSearchInfoArg of body.results) {
|
|
const npmPackage = await this.getPackageInfo(packageSearchInfoArg.package.name);
|
|
packageArray.push(npmPackage);
|
|
}
|
|
|
|
return packageArray;
|
|
}
|
|
}
|