UNPKG

@apistudio/apim-cli

Version:

CLI for API Management Products

204 lines (203 loc) 10.5 kB
import fs from "fs"; import { AssetCache } from "../../cache/asset-cache.js"; import { COLON, COMMA } from "../../constants/app-constants.js"; import { equalsIgnoreCase, isNullOrUndefined } from "../common/data-helper.js"; import { getRandomFileName, getSubDirectory, isDirectory, isDirOrFileExists, isYamlFile, normalizePath, readFile, } from "../common/fs-helper.js"; import { showError, showInfo, showWarning } from "../common/message-helper.js"; import { readMultiYaml, readYaml } from "../common/yaml-helper.js"; import { checkForDependencyAssets, loadCacheWithProject, } from "./asset-cache-helper.js"; import { getTargetModelAssetKind, isValidAsset } from "./asset-helper.js"; import { getOtherProjectsNames } from "./root-dir-helper.js"; import { DebugManager } from "../../debug/debug-manager.js"; import { ADDING_DEPENDENCY, ASSERT_ADDED, ASSET_DEPENDENCIES, DEPENDENT_ASSETS_TO_BE_PROCESSED, DUPLICATE_ENTRIES_FOR_KIND, FOLLOWING, INSIDE_THE_PROJECT_PATH, INVALID_DIRECTORY, IS_FOUND_IN, KIND, METADATA_NAME, NAME, NO_ENTRIES_FOUND_FOR_KIND, NO_FURTHER_DEPENDENCY, REF, SEARCHING, THERE_ARE, VERSION, } from "../../constants/message-constants.js"; import { KindEnums } from "@apic/api-model/common/StudioEnums.js"; import { bundleApiDependency } from './api-build-helper.js'; const addDependencyAsset = (file, zip, fileExtension = ".yml") => { if (isNullOrUndefined(file)) { return; } const fileName = getRandomFileName(fileExtension); const filePath = normalizePath(`${file.parentPath}/${file.name}`); zip.addLocalFile(filePath, "dependencies", fileName); }; const hasAssetInGivenAssets = (assets, metadataToSearch, kindToSearch) => { for (const asset of assets) { if (!isValidAsset(asset)) { continue; } if (isSameAsset(asset.metadata, metadataToSearch) && equalsIgnoreCase(kindToSearch, getTargetModelAssetKind(asset.kind))) { return true; } } return false; }; // search for the dependency asset for the given asset ref value and project directory path const searchAsset = (kindToSearch, assetRefValueToSearch, projectDirPath) => { if (!isDirOrFileExists(projectDirPath) || !isDirectory(projectDirPath)) { throw new Error(`${INVALID_DIRECTORY} ${projectDirPath}`); } const entries = fs.readdirSync(projectDirPath, { withFileTypes: true, recursive: true, }); const metadataToSearch = fromAssetRefValue(assetRefValueToSearch); const filteredEntries = entries.filter((entry) => { if (entry.isDirectory()) { return false; } if (!isYamlFile(entry.name)) { return false; } const assets = readMultiYaml(normalizePath(`${entry.parentPath}/${entry.name}`), readFile(entry.parentPath, entry.name)); return hasAssetInGivenAssets(assets, metadataToSearch, kindToSearch); }); if (filteredEntries.length > 1 && DebugManager.getInstance().isDebugEnabled()) { showWarning(`${DUPLICATE_ENTRIES_FOR_KIND} - '${kindToSearch}', ${METADATA_NAME} '${metadataToSearch.name}' ${IS_FOUND_IN} '${projectDirPath}'`); } if (filteredEntries.length === 0 && DebugManager.getInstance().isDebugEnabled()) { showWarning(`${NO_ENTRIES_FOUND_FOR_KIND} - '${kindToSearch}', ${METADATA_NAME} '${metadataToSearch.name}' ${IS_FOUND_IN} '${projectDirPath}'`); return undefined; } return filteredEntries[0]; }; const isSameAsset = (input1, input2) => { const isNamespaceAndNameEqual = input1.namespace === input2.namespace && input1.name === input2.name; const isVersionEqual = (() => { const version1 = Number(input1.version); const version2 = Number(input2.version); if (Number.isNaN(version1) && Number.isNaN(version2)) { return input1.version === input2.version; } return version1 === version2; })(); return isNamespaceAndNameEqual && isVersionEqual; }; const fromAssetRefValue = (assetRefValue) => { const split = assetRefValue.split(COLON); if (split.length === 1) { return { name: split[0], }; } else if (split.length === 2) { return { name: split[0], version: split[1], }; } return { namespace: split[0], name: split[1], version: split[2], }; }; const searchAndBundleDependency = (cachedUnProcessedAsset, rootDirPath, projects, zipFile) => { try { for (const project of projects) { const projectDirPath = getSubDirectory(rootDirPath, project); if (DebugManager.getInstance().isDebugEnabled()) { showInfo(`\n\n ${SEARCHING}: ${KIND} - ${cachedUnProcessedAsset.kind} ${REF} - ${cachedUnProcessedAsset.ref} ${INSIDE_THE_PROJECT_PATH} '${projectDirPath}'`); } const result = searchAsset(cachedUnProcessedAsset.kind, cachedUnProcessedAsset.ref, projectDirPath); if (!isNullOrUndefined(result)) { const fileContent = readFile(result.parentPath, result.name); const asset = readYaml(fileContent); const isApiKind = equalsIgnoreCase(getTargetModelAssetKind(asset.kind), KindEnums.API); /* (*) add dependency to zip file*/ if (DebugManager.getInstance().isDebugEnabled()) { showInfo(`${ADDING_DEPENDENCY}: ${KIND}-'${asset.metadata.namespace}', ${NAME}-'${asset.metadata.name}', ${VERSION}-'${asset.metadata.version}'`); } if (isApiKind) { bundleApiDependency(asset, result, cachedUnProcessedAsset, rootDirPath, project, zipFile); } else { // Non-API dependencies go to dependencies folder (existing behavior) addDependencyAsset(result, zipFile); showInfo(`${ASSERT_ADDED} ${asset.metadata.namespace}:${asset.metadata.name}:${asset.metadata.version}`); } /* (*) mark the added asset as processed */ AssetCache.getInstance().markAsProcessed(asset); /* (*) check for any further dependencies from the current asset */ // Pass the source project to maintain the dependency chain const sourceProjectForNestedDeps = cachedUnProcessedAsset.sourceProject || project; checkForDependencyAssets(asset, sourceProjectForNestedDeps); return; } } /* (*) if there are no assets found, mark this unprocessed asset as checked. */ AssetCache.getInstance().markUnProcessedAssetAsChecked(cachedUnProcessedAsset); } catch (error) { throw new Error(`Failure in search asset: kind - ${cachedUnProcessedAsset.kind} ref - ${cachedUnProcessedAsset.ref} with error: ${error.message}`); } }; const loadDependenciesFromProjects = (rootDirPath, projects, zipFile) => { const newlyAddedUnProcessedAssets = AssetCache.getInstance().getNewlyAddedUnProcessedAssets(); for (const newlyAddedUnProcessedAsset of newlyAddedUnProcessedAssets) { searchAndBundleDependency(newlyAddedUnProcessedAsset, rootDirPath, projects, zipFile); } }; const checkAndLoadDependenciesFromProjects = (rootDirPath, projects, zipFile) => { while (!haveCheckedUnProcessedAssets() && haveUnCheckedUnProcessedAssets()) { const unProcessedAssets = AssetCache.getInstance().getNewlyAddedUnProcessedAssets(); if (DebugManager.getInstance().isDebugEnabled()) { showInfo(`\n\n ${THERE_ARE} ${unProcessedAssets.size} ${DEPENDENT_ASSETS_TO_BE_PROCESSED}`); // logging newly added dependencies showInfo(`${FOLLOWING} ${unProcessedAssets.size} ${ASSET_DEPENDENCIES} `); } unProcessedAssets.forEach((unProcessedAsset) => { if (DebugManager.getInstance().isDebugEnabled()) { showInfo(`${KIND}: '${unProcessedAsset.kind}' ${REF}: ${unProcessedAsset.ref}`); } }); loadDependenciesFromProjects(rootDirPath, projects, zipFile); } }; const checkCacheState = () => { if (haveCheckedUnProcessedAssets()) { const unProcessedAssets = AssetCache.getInstance().getCheckedUnProcessedAssets(); showError(`Following ${unProcessedAssets.size} asset dependencies cannot be resolved:`); unProcessedAssets.forEach((unProcessedAsset) => showError(`kind: '${unProcessedAsset.kind}' ref: ${unProcessedAsset.ref}`)); throw new Error("Dependency assets cannot be resolved"); } if (!haveUnCheckedUnProcessedAssets()) { const unProcessedAssets = AssetCache.getInstance().getNewlyAddedUnProcessedAssets(); if (unProcessedAssets.size === 0) { if (DebugManager.getInstance().isDebugEnabled()) { showInfo(`${NO_FURTHER_DEPENDENCY}`); } return; } } }; const haveCheckedUnProcessedAssets = () => { return AssetCache.getInstance().getCheckedUnProcessedAssets().size > 0; }; const haveUnCheckedUnProcessedAssets = () => { return AssetCache.getInstance().getNewlyAddedUnProcessedAssets().size > 0; }; const checkAndLoadDependencies = (rootDirPath, projectNames, zipFile, excludeCurrProj = false) => { /* (*) Parse the current project assets and update cache with processed and to be processed */ AssetCache.getInstance().clear(); loadCacheWithProject(zipFile); /* (*) Check the current projects before checking in the other projects */ if (!excludeCurrProj) { const currentProjects = new Set(projectNames.split(COMMA)); checkAndLoadDependenciesFromProjects(rootDirPath, currentProjects, zipFile); /* (*) mark unprocessed asset if any as unchecked to make to search in other projects */ AssetCache.getInstance().markAllUnProcessedAssetAsUnchecked(); } /* (*) Check and load refers to the dependencies directory */ const otherProjects = getOtherProjectsNames(rootDirPath, projectNames); if (!otherProjects) { showInfo('Skip checking files in other projects as no other projects found'); return; } checkAndLoadDependenciesFromProjects(rootDirPath, otherProjects, zipFile); /* (*) Check for cache state and throw error if some assets cannot be resolved*/ checkCacheState(); }; export { addDependencyAsset, checkAndLoadDependencies, fromAssetRefValue, isSameAsset, searchAsset, };