fume-fhir-converter
Version:
FHIR-Utilized Mapping Engine - Community
250 lines • 10.8 kB
JavaScript
;
/**
* © 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