@apistudio/apim-cli
Version:
CLI for API Management Products
335 lines (334 loc) • 15.1 kB
JavaScript
import { ProjectAssetValidator } from './validator/asset-validator.js';
import JSZip from 'jszip';
import yaml from 'js-yaml';
import { convertNumberToString, isValidAsset, updateMapWithMetadata, updateRefs } from './utils.js';
import path from 'path';
export class BuildProjectAssets {
async loadZipFromBuffer(fileBuffer) {
console.log('Loading ZIP from buffer');
const zip = new JSZip();
return zip.loadAsync(fileBuffer);
}
async validate(fileBuffer) {
console.log('Validating ZIP file');
const AssetValidator = new ProjectAssetValidator();
const folderNames = new Set();
const filePathsInFolder = new Set();
await this.extractFolderNamesAndPaths(fileBuffer, folderNames, filePathsInFolder);
const validationPromises = Array.from(folderNames).map(async (folderName) => {
return this.validateFolder(fileBuffer, folderName, AssetValidator, filePathsInFolder, folderNames);
});
const validationResults = await Promise.all(validationPromises);
const isValid = validationResults.every(result => result.isValid);
// create a map with folderName and it's refMap
const allRefMaps = new Map();
validationResults.forEach((result, index) => {
const folderName = Array.from(folderNames)[index];
allRefMaps.set(folderName, result.refMap);
});
return { isValid, allRefMaps };
}
async extractFolderNamesAndPaths(fileBuffer, folderNames, filePathsInFolder) {
console.log('Extracting folder names and paths');
const zipContent = await this.loadZipFromBuffer(fileBuffer);
for (const fileName in zipContent.files) {
const folderName = fileName.split(path.sep)[0];
if (folderName !== 'dependencies') {
folderNames.add(folderName);
filePathsInFolder.add(fileName);
}
}
console.log(Array.from(folderNames).join(', '));
}
async validateFolder(fileBuffer, folderName, AssetValidator, filePathsInFolder, allFolderNames) {
console.log(`folder: ${folderName}`);
const assetReferenceValid = await AssetValidator.validateProjectAssetReference(fileBuffer, folderName, allFolderNames);
const pathReferenceValid = await AssetValidator.validateProjectPathReference(fileBuffer, folderName, filePathsInFolder);
const minimumAssetsValid = await AssetValidator.validateProjectHasMinimumAssets(fileBuffer);
const apiSpecVariableValid = await AssetValidator.validateProjectApiSpecVariable(fileBuffer, folderName);
const yamlValid = await AssetValidator.validateYaml(fileBuffer);
// return isvalid true if all the validations are true
// return refMap to consolidate assets later
return { isValid: assetReferenceValid.isValid && pathReferenceValid && minimumAssetsValid && apiSpecVariableValid && yamlValid,
refMap: assetReferenceValid.refMap
};
}
async getFileFromZip(fileBuffer, filePath) {
console.log(filePath);
const zipContent = await this.loadZipFromBuffer(fileBuffer);
const file = zipContent.file(filePath);
if (file) {
return file.async('string');
}
console.log(filePath);
return null;
}
async createProjectBuildZip(buffer, allRefMaps) {
console.log('Creating project build ZIP');
const buildZip = new JSZip();
await this.loadZipFromBuffer(buffer);
const folderNames = new Set();
const filePathsInFolder = new Set();
await this.extractFolderNamesAndPaths(buffer, folderNames, filePathsInFolder);
await this.addConsolidatedYAMLs(buildZip, buffer, folderNames, allRefMaps);
await this.addReferencedFiles(buildZip, buffer, folderNames);
return buildZip;
}
async addConsolidatedYAMLs(buildZip, buffer, folderNames, allRefMaps) {
console.log('Adding consolidated YAMLs to build ZIP');
for (const folderName of folderNames) {
const consolidatedYaml = await this.createConsolidatedYaml(buffer, folderName, allRefMaps);
buildZip.file(`${folderName}.yaml`, consolidatedYaml);
}
}
async addReferencedFiles(buildZip, buffer, folderNames) {
console.log('Adding referenced files to build ZIP');
const AssetValidator = new ProjectAssetValidator();
for (const folderName of folderNames) {
const refMap = await AssetValidator.createProjectPathReferenceMap(buffer, folderName);
const promises = Array.from(refMap.keys()).map(async (key) => {
const file = await this.getFileFromZip(buffer, path.normalize(`${folderName}/${key}`));
if (file !== null) {
buildZip.file(path.normalize(`resources/${folderName}/${key}`), file);
}
else {
console.log(key);
}
});
await Promise.all(promises);
}
}
async createConsolidatedYaml(buffer, folderName, allRefMaps) {
console.log(folderName);
const zipContent = await this.loadZipFromBuffer(buffer);
const visitedAsset = new Set();
let consolidatedYaml = '';
try {
const versionMap = await this.createVersionProcessingMap(buffer);
consolidatedYaml += await this.processYamlFiles(zipContent, folderName, visitedAsset, versionMap);
consolidatedYaml += await this.processDependencyFiles(zipContent, visitedAsset, versionMap);
consolidatedYaml += await this.processDependenciesInOtherFolders(zipContent, visitedAsset, versionMap, folderName, allRefMaps);
}
catch (err) {
console.log('processing ZIP', `${err}`);
}
return consolidatedYaml;
}
async processYamlFiles(zipContent, folderName, visitedAsset, versionMap) {
let consolidatedYaml = '';
for (const fileName in zipContent.files) {
const entry = zipContent.files[fileName];
if (!entry.dir &&
fileName.startsWith(folderName + path.sep) &&
(fileName.endsWith('.yaml') || fileName.endsWith('.yml'))) {
try {
const content = await entry.async('string');
consolidatedYaml += await this.processYamlContent(content, visitedAsset, versionMap);
}
catch (err) {
console.log('parsing YAML file', `${err}`);
}
}
}
return consolidatedYaml;
}
async processDependenciesInOtherFolders(zipContent, visitedAsset, versionMap, folderName, allRefMaps) {
console.log('Processing dependency YAML files');
let consolidatedYaml = '';
// retrieve the refMap of the current folderName and process it
for (const [refFolderName, refMap] of allRefMaps) {
if (refFolderName === folderName) {
for (const fileName in zipContent.files) {
const entry = zipContent.files[fileName];
if (this.shouldProcessFilesInOtherFolders(entry, fileName, folderName)) {
try {
const content = await entry.async('string');
consolidatedYaml += await this.processYamlContentForOtherFolders(content, visitedAsset, versionMap, refMap);
}
catch (err) {
console.log('parsing dependency YAML file', `${err}`);
}
}
}
}
}
return consolidatedYaml;
}
async processDependencyFiles(zipContent, visitedAsset, versionMap) {
console.log('Processing dependency YAML files');
let consolidatedYaml = '';
for (const fileName in zipContent.files) {
const entry = zipContent.files[fileName];
if (!entry.dir &&
fileName.startsWith('dependencies' + path.sep) &&
(fileName.endsWith('.yaml') || fileName.endsWith('.yml'))) {
try {
const content = await entry.async('string');
consolidatedYaml += await this.processYamlContent(content, visitedAsset, versionMap);
}
catch (err) {
console.log('parsing dependency YAML file', `${err}`);
}
}
}
return consolidatedYaml;
}
shouldProcessFile(fileName) {
return (fileName.endsWith('.yaml') || fileName.endsWith('.yml')) && !fileName.includes('resources');
}
shouldProcessFilesInOtherFolders(entry, fileName, folderName) {
return !entry.dir &&
!fileName.startsWith(folderName + path.sep) &&
(fileName.endsWith('.yaml') || fileName.endsWith('.yml'));
}
async createVersionProcessingMap(buffer) {
const refMap = new Map();
try {
const zipContent = await this.loadZipFromBuffer(buffer);
for (const fileName in zipContent.files) {
const entry = zipContent.files[fileName];
if (this.shouldProcessFile(fileName) && !entry.dir) {
await this.processFileContent(entry, refMap);
}
}
}
catch (err) {
console.log(`Error loading ZIP: ${err}`);
}
return refMap;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async processFileContent(entry, refMap) {
try {
const content = await entry.async('string');
const yamlContents = this.parseYaml(content);
this.processYamlContents(yamlContents, refMap);
}
catch (err) {
console.log(`Error parsing YAML: ${err}`);
}
}
parseYaml(content) {
try {
return yaml.loadAll(content);
}
catch (err) {
console.log(`Error parsing YAML: ${err}`);
return [];
}
}
processYamlContents(yamlContents, refMap) {
for (const yamlContent of yamlContents) {
if (isValidAsset(yamlContent)) {
const metadata = yamlContent['metadata'];
if (typeof metadata.version === 'string') {
updateMapWithMetadata(yamlContent, refMap);
}
}
}
}
async processYamlContent(content, visitedAsset, versionMap) {
let yamlResult = '';
try {
const yamlContents = yaml.loadAll(content);
for (const yamlContent of yamlContents) {
if (isValidAsset(yamlContent)) {
const metadata = yamlContent.metadata;
const contentString = `${metadata.namespace ? metadata.namespace : ''}:${metadata.name}:${convertNumberToString(metadata.version)}`;
if (visitedAsset.has(contentString)) {
console.log(`Skipping already visited asset: ${contentString}`);
continue;
}
yamlContent.metadata.version = convertNumberToString(metadata.version);
visitedAsset.add(contentString);
const processedYamlContent = updateRefs(yamlContent, versionMap);
yamlResult += '---' + '\n' + yaml.dump(processedYamlContent) + '\n';
}
}
}
catch (err) {
console.log('processing YAML content', `${err}`);
}
return yamlResult;
}
async processYamlContentForOtherFolders(content, visitedAsset, versionMap, refMap) {
let yamlResult = '';
try {
const yamlContents = yaml.loadAll(content);
for (const yamlContent of yamlContents) {
if (isValidAsset(yamlContent)) {
const metadata = yamlContent.metadata;
const contentString = `${metadata.namespace ? metadata.namespace : ''}:${metadata.name}:${convertNumberToString(metadata.version)}`;
// If the current file metadata is present in refMap and the metadata value is set to true, then it needs to be added
if (refMap.has(contentString) && refMap.get(contentString) === true) {
if (visitedAsset.has(contentString)) {
console.log(`Skipping already visited asset: ${contentString}`);
continue;
}
yamlContent.metadata.version = convertNumberToString(metadata.version);
visitedAsset.add(contentString);
const processedYamlContent = updateRefs(yamlContent, versionMap);
yamlResult += '---' + '\n' + yaml.dump(processedYamlContent) + '\n';
}
}
}
}
catch (err) {
console.log('processing YAML content', `${err}`);
}
return yamlResult;
}
async processProjectZip(fileBuffer) {
console.log('Processing project ZIP');
const validatedFileBuffer = await this.validate(fileBuffer);
if (!validatedFileBuffer.isValid) {
console.log('Project ZIP validation failed');
return null;
}
return this.createProjectBuildZip(fileBuffer, validatedFileBuffer.allRefMaps);
}
async extractGatewaysJson(buffer) {
console.log('Extracting gateways.json');
const zipContent = await this.loadZipFromBuffer(buffer);
return this.extractGatewaysJsonFromZip(zipContent);
}
async extractGatewaysJsonFromZip(zipContent) {
console.log('Extracting gateways.json from ZIP');
let gatewaysJsonContent = {};
try {
const gatewaysJsonFile = this.findGatewaysJsonFile(zipContent);
if (gatewaysJsonFile) {
gatewaysJsonContent = await this.parseJsonContent(gatewaysJsonFile);
}
else {
console.log('gateways.json file not found in ZIP');
}
}
catch (err) {
console.log('extracting gateways.json', `${err}`);
}
return gatewaysJsonContent;
}
findGatewaysJsonFile(zipContent) {
console.log('Finding gateways.json file in ZIP');
for (const fileName in zipContent.files) {
const entry = zipContent.files[fileName];
if (!entry.dir && fileName.includes('gateways.json')) {
return entry;
}
}
return null;
}
async parseJsonContent(file) {
console.log('Parsing JSON content');
let jsonContent = {};
try {
const content = await file.async('string');
jsonContent = JSON.parse(content);
}
catch (err) {
console.log('parsing JSON content', `${err}`);
}
return jsonContent;
}
}