UNPKG

fume-fhir-converter

Version:

FHIR-Utilized Mapping Engine - Community

250 lines 10.8 kB
"use strict"; /** * © Copyright Outburn Ltd. 2022-2024 All Rights Reserved * Project name: FUME-COMMUNITY */ Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const axios_1 = tslib_1.__importDefault(require("axios")); const fs_extra_1 = tslib_1.__importDefault(require("fs-extra")); const path_1 = tslib_1.__importDefault(require("path")); const promises_1 = require("stream/promises"); const tar = tslib_1.__importStar(require("tar")); const temp_1 = tslib_1.__importDefault(require("temp")); const config_1 = tslib_1.__importDefault(require("../../config")); const logger_1 = require("../logger"); const getCachePath_1 = require("./getCachePath"); const cachePath = (0, getCachePath_1.getCachePackagesPath)(); const registryUrl = 'https://packages.fhir.org'; const fallbackTarballUrl = (packageObject) => `https://packages.simplifier.net/${packageObject.id}/-/${packageObject.id}-${packageObject.version}.tgz`; /** * Takes a PackageObject and returns the corresponding directory name of the package * @param packageObject A PackageObject with both name and version keys * @returns (string) Directory name in the standard format `name#version` */ const toDirName = (packageObject) => packageObject.id + '#' + packageObject.version; const getPackageDirPath = (packageObject) => { const dirName = toDirName(packageObject); const packPath = path_1.default.join(cachePath, dirName); return packPath; }; const getPackageIndexPath = (packageObject) => path_1.default.join(getPackageDirPath(packageObject), 'package', '.fume.index.json'); /** * Scans a package folder and generates a new `.fume.index.json` file * @param packagePath The path where the package is installed * @returns PackageIndex */ const generatePackageIndex = async (packageObject) => { (0, logger_1.getLogger)().info(`Generating new .fume.index.json file for package ${packageObject.id}@${packageObject.version}...`); const packagePath = getPackageDirPath(packageObject); const indexPath = getPackageIndexPath(packageObject); const evalAttribute = (att) => (typeof att === 'string' ? att : undefined); try { const fileList = await fs_extra_1.default.readdir(path_1.default.join(packagePath, 'package')); const files = await Promise.all(fileList.filter(file => file.endsWith('.json') && file !== 'package.json' && !file.endsWith('.index.json')).map(async (file) => { const content = JSON.parse(await fs_extra_1.default.readFile(path_1.default.join(packagePath, 'package', file), { encoding: 'utf8' })); const indexEntry = { filename: file, resourceType: content.resourceType, id: content.id, url: evalAttribute(content.url), name: evalAttribute(content.name), version: evalAttribute(content.version), kind: evalAttribute(content.kind), type: evalAttribute(content.type), supplements: evalAttribute(content.supplements), content: evalAttribute(content.content), baseDefinition: evalAttribute(content.baseDefinition), derivation: evalAttribute(content.derivation), date: evalAttribute(content.date) }; return indexEntry; })); const indexJson = { 'index-version': 2, files }; await fs_extra_1.default.writeFile(indexPath, JSON.stringify(indexJson, null, 2)); return indexJson; } catch (e) { (0, logger_1.getLogger)().error(e); throw (e); } ; }; const getPackageIndexFile = async (packageObject) => { const path = getPackageIndexPath(packageObject); if (await fs_extra_1.default.exists(path)) { const contents = await fs_extra_1.default.readFile(path, { encoding: 'utf8' }); const parsed = JSON.parse(contents); return parsed; } ; /* If we got here it means the index file is missing */ /* Hence, we need to build it */ const newIndex = await generatePackageIndex(packageObject); return newIndex; }; /** * Checks if the package folder is found in the package cache * @param packageObject An object with `name` and `version` keys * @returns `true` if the package folder was found, `false` otherwise */ const isInstalled = (packageObject) => { const packPath = getPackageDirPath(packageObject); return fs_extra_1.default.existsSync(packPath); }; /** * Extracts the version of the package * @param packageId (string) Raw package identifier string. Could be `name@version`, `name#version` or just `name` * @returns The version part of the package identifier. If not supplied, `latest` will be returned */ const getVersionFromPackageString = (packageId) => { const byPound = packageId.split('#'); const byAt = packageId.split('@'); if (byPound.length === 2) return byPound[1]; if (byAt.length === 2) return byAt[1]; return 'latest'; }; /** * Queries the registry for the package information * @param packageName Only the package name (no version) * @returns The response object from the registry */ const getPackageDataFromRegistry = async (packageName) => { const packageData = await axios_1.default.get(`${registryUrl}/${packageName}/`); return packageData.data; }; /** * Checks the package registry for the latest published version of a package * @param packageName (string) The package id alone, without the version part * @returns The latest published version of the package */ const checkLatestPackageDist = async (packageName) => { const packageData = await getPackageDataFromRegistry(packageName); const latest = packageData['dist-tags']?.latest; return latest; }; /** * Parses a package identifier string into a PackageObject. * If the version was not supplied it will be resolved to the latest published version * @param packageId (string) Raw package identifier string. Could be `name@version`, `name#version` or just `name` * @returns a PackageObject with name and version */ const toPackageObject = async (packageId) => { const packageName = packageId.split('#')[0].split('@')[0]; let packageVersion = getVersionFromPackageString(packageId); if (packageVersion === 'latest') packageVersion = await checkLatestPackageDist(packageName); return { id: packageName, version: packageVersion }; }; /** * Resolve a package object into a URL for the package tarball * @param packageObject * @returns Tarball URL */ const getTarballUrl = async (packageObject) => { let tarballUrl; try { const packageData = await getPackageDataFromRegistry(packageObject.id); const versionData = packageData.versions[packageObject.version]; tarballUrl = versionData?.dist?.tarball; } catch { tarballUrl = fallbackTarballUrl(packageObject); } ; return tarballUrl; }; /** * Move an extracted package content from temporary directory into the FHIR package cache * @param packageObject * @param tempDirectory * @returns The final path of the package in the cache */ const cachePackageTarball = async (packageObject, tempDirectory) => { const finalPath = path_1.default.join(cachePath, toDirName(packageObject)); if (!isInstalled(packageObject)) { await fs_extra_1.default.move(tempDirectory, finalPath); (0, logger_1.getLogger)().info(`Installed ${packageObject.id}@${packageObject.version} in the FHIR package cache: ${finalPath}`); } return finalPath; }; /** * Downloads the tarball file into a temp folder and returns the path * @param packageObject */ const downloadTarball = async (packageObject) => { const tarballUrl = await getTarballUrl(packageObject); const res = await axios_1.default.get(tarballUrl, { responseType: 'stream' }); if (res?.status === 200 && res?.data) { try { const tarballStream = res.data; temp_1.default.track(); const tempDirectory = temp_1.default.mkdirSync(); await (0, promises_1.pipeline)(tarballStream, tar.x({ cwd: tempDirectory })); (0, logger_1.getLogger)().info(`Downloaded ${packageObject.id}@${packageObject.version} to a temporary directory`); return tempDirectory; } catch (e) { (0, logger_1.getLogger)().error(`Failed to extract tarball of package ${packageObject.id}@${packageObject.version}`); throw e; } } else { throw new Error(`Failed to download package ${packageObject.id}@${packageObject.version} from URL: ${tarballUrl}`); } }; const getManifest = async (packageObject) => { const manifestPath = path_1.default.join(cachePath, toDirName(packageObject), 'package', 'package.json'); const manifestFile = await fs_extra_1.default.readFile(manifestPath, { encoding: 'utf8' }); if (manifestFile) { const manifest = JSON.parse(manifestFile); return manifest; } else { (0, logger_1.getLogger)().warn(`Could not find package manifest for ${packageObject.id}@${packageObject.version}`); return { name: packageObject.id, version: packageObject.version }; } }; const getDependencies = async (packageObject) => { return (await getManifest(packageObject))?.dependencies; }; /** * Ensures that a package and all of its dependencies are installed in the global package cache. * If a version is not supplied, the latest release will be looked up and installed. * @param packagId string in the format packageId@version | packageId | packageId#version */ const ensure = async (packageId) => { const packageObject = await toPackageObject(packageId); const installed = isInstalled(packageObject); let installedPath; if (!installed) { try { const tempPath = await downloadTarball(packageObject); installedPath = await cachePackageTarball(packageObject, tempPath); } catch (e) { (0, logger_1.getLogger)().error(e); throw new Error(`Failed to install package ${packageId}`); } } else { installedPath = getPackageDirPath(packageObject); } ; const packageIndex = await getPackageIndexFile(packageObject); config_1.default.addFhirPackage(packageObject, await getManifest(packageObject), installedPath, packageIndex); // package itself is installed now. Ensure dependencies. const deps = await getDependencies(packageObject); for (const pack in deps) { await ensure(pack + '@' + deps[pack]); } ; return true; }; exports.default = ensure; //# sourceMappingURL=ensurePackageInstalled.js.map