import * as plugins from './smartnpm.plugins.js'; import * as paths from './smartnpm.paths.js'; // interfaces import { type ISearchObject } from './smartnpm.interfaces.js'; // classes import { NpmPackage } from './smartnpm.classes.npmpackage.js'; import { type ICacheDescriptor, RegistryCache } from './smartnpm.classes.registrycache.js'; 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 { const fullMetadata = await plugins .packageJson.default(packageName, { registryUrl: this.options.npmRegistryUrl, fullMetadata: true, }) .catch((err) => { console.log(err); return null; }); const versionData = await plugins.packageJson.default(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 { 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 { // 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); if (fileResult) { this.registryCache.cacheSmartFile(cacheDescriptor, fileResult); } return fileResult; } else { return cachedFile; } } public async getFilesFromPackage( packageNameArg: string, filePath: string, optionsArg?: { distTag?: string; version?: string; } ): Promise { 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 { /** * 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; } }