@apistudio/apim-cli
Version:
CLI for API Management Products
289 lines (250 loc) • 9.62 kB
text/typescript
import path from 'path';
import AdmZip from 'adm-zip';
import {showError, showInfo} from '../common/message-helper.js';
import { readMultiYaml } from '../common/yaml-helper.js';
import { readFile } from '../common/fs-helper.js';
import {searchAssetByKind} from './asset-searchByKind-helper.js';
import { getAllProjectNames } from './root-dir-helper.js';
import Table from 'cli-table3';
import { GatewayResponseAPI } from '../../model/studio/deploy-response-model.js';
import {
INVALID_OR_EMPTY_DEPLOYMENT_RESPONSES,
NO_VALID_API_ASSET_FOUND,
ADDING_ENDPOINT_FILE_FAILED,
TESTING_LIST_ALL_PROJECTS,
CHECKING_ALL_PROJECTS,
IDENTIFIED_PROJECTS,
FAILED_TO_BUILD_TEST_ASSETS,
ERROR_IN_COMBINING_TEST_ASSET,
LINE,
TEST_STARTED,
PROCESS_ENDPOINT_RESPONSE,
PROCESS_GATEWAY_RESPONSE,
PROCESS_ENDPOINT_RESPONSE_SUCCESS,
ENDPOINT_TEST,
COMBINING_TEST_ZIP,
COMBINING_TEST_ZIP_SUCCESS,
BUILDING_TEST,
BUILDING_TEST_SUCCESS,
JSON_FILE_MISSING,
UPDATE_ENDPOINT_FAILED,
ASSETS_BUILD_FAILED,
APIENDPOINTS,
} from '../../constants/message-constants.js';
import { ENDPOINT_FILE, TEST } from '../../constants/app-constants.js';
import { Test } from '@apic/api-model/test/Test.js';
import {BuildTestAssetsResult,executeBuildTestAssets} from '../../testers/project/projects-asset-testers.js';
import { DebugManager } from '../../debug/debug-manager.js';
import { KindEnums } from '@apic/api-model/common/StudioEnums.js';
import { buildAssets } from '../../actions/helpers/build-action-helper.js';
import { executeDeployment } from '../../deployers/project/projects-deployer.js';
import { GatewaysJson } from '@apic/studio-shared';
export type APIEndpoints = Record<string, string[]>;
/**
* @param jsonObject - The JSON object to be constructed.
* @param key - The key to add to the JSON object.
* @param value - The array of endpoints to add.
*/
export interface BuildTestProjectsResult {
zipBuffer: Buffer;
apiReference: Record<string,string>;
}
const constructJSON = (jsonObject: APIEndpoints,key: string,value: string[]): void => {
jsonObject[key] = value;
};
export const constructKey = (response: GatewayResponseAPI): string => {
return `${response.namespace}:${response.assetName}:${response.version}`;
};
export const createJSONBuffer = (apiGatewayEndpoints: APIEndpoints): Buffer => {
return Buffer.from(JSON.stringify(apiGatewayEndpoints, null, 2), 'utf-8');
};
export const getAPIRefToBuild = (testFilePath: string): string[] | null => {
const apiReferences : string[] = [];
const parentDirPath = path.dirname(testFilePath);
const testFileName = path.basename(testFilePath);
const yamlContent = readFile(parentDirPath, testFileName);
const parsedYamls = readMultiYaml<Test>(testFileName, yamlContent);
parsedYamls.forEach((parsedYaml) => {
const ref = parsedYaml?.spec?.api?.$ref;
if (parsedYaml.kind?.toLowerCase() === TEST && ref) {
apiReferences.push(ref);
}
})
return apiReferences;
};
export const processEndpointFromResponse = (responses: GatewayResponseAPI[]): Buffer => {
const apiGatewayEndpoints: APIEndpoints = {};
if(DebugManager.getInstance().isDebugEnabled()){
showInfo(PROCESS_ENDPOINT_RESPONSE);
}
if (!Array.isArray(responses) || responses.length === 0) {
showError(INVALID_OR_EMPTY_DEPLOYMENT_RESPONSES);
throw new Error(INVALID_OR_EMPTY_DEPLOYMENT_RESPONSES);
}
responses.forEach((response) => {
if(DebugManager.getInstance().isDebugEnabled()){
showInfo(`${PROCESS_GATEWAY_RESPONSE}${response.name}`);
}
const key = constructKey(response);
if (apiGatewayEndpoints[key]) {
apiGatewayEndpoints[key] = [
...new Set([...apiGatewayEndpoints[key], ...response.gatewayEndpoints]),
];
} else {
constructJSON(apiGatewayEndpoints, key, response.gatewayEndpoints);
}
});
if(DebugManager.getInstance().isDebugEnabled()){
showInfo(PROCESS_ENDPOINT_RESPONSE_SUCCESS);
}
return createJSONBuffer(apiGatewayEndpoints);
};
export const addEndpointToZip = (testZipBuffer: Buffer,endpointFile: Buffer): Buffer => {
try {
if(DebugManager.getInstance().isDebugEnabled()){
showInfo(ENDPOINT_TEST);
}
const testZipBufferWithEndpoint = new AdmZip(testZipBuffer);
testZipBufferWithEndpoint.addFile(ENDPOINT_FILE, endpointFile);
return testZipBufferWithEndpoint.toBuffer();
} catch (error) {
showError(ADDING_ENDPOINT_FILE_FAILED);
throw new Error(
`${ADDING_ENDPOINT_FILE_FAILED} ${(error as Error).message}`
);
}
};
const addFileToZip = (combinedZip: AdmZip, entry: AdmZip.IZipEntry) => {
if (!combinedZip.getEntry(entry.entryName)) {
combinedZip.addFile(entry.entryName, entry.getData());
}
};
const displayErrorMessage = (result: BuildTestAssetsResult, project: string) => {
if (!result || !result.zipBuffer) {
showError(`${FAILED_TO_BUILD_TEST_ASSETS} ${project}`);
throw new Error(`${FAILED_TO_BUILD_TEST_ASSETS} ${project}`);
}
};
export const updateEndpointZip = async (finalZipBuffer: Buffer, newEndpoints: Buffer): Promise<Buffer> => {
try {
const zip = new AdmZip(finalZipBuffer);
const jsonEntry = zip.getEntry(ENDPOINT_FILE);
if (!jsonEntry) {
throw new Error(JSON_FILE_MISSING);
}
const existingEndpointsBuffer = jsonEntry.getData();
const existingEndpoints: APIEndpoints = JSON.parse(existingEndpointsBuffer.toString('utf-8'));
const newEndpointsData: APIEndpoints = JSON.parse(newEndpoints.toString('utf-8'));
const updatedEndpoints = { ...existingEndpoints, ...newEndpointsData };
const updatedEndpointsBuffer = createJSONBuffer(updatedEndpoints);
zip.updateFile(ENDPOINT_FILE, updatedEndpointsBuffer);
return zip.toBuffer();
} catch (error) {
showError(`${UPDATE_ENDPOINT_FAILED} ${(error as Error).message}`);
throw new Error(`${UPDATE_ENDPOINT_FAILED} ${(error as Error).message}`);
}
};
export const combineTestAsset = async (rootDir: string, assetsToTest: Record<string, string>): Promise<BuildTestProjectsResult> => {
try {
if (DebugManager.getInstance().isDebugEnabled()) {
showInfo(COMBINING_TEST_ZIP);
}
const combinedZip = new AdmZip();
const apiReferences: Record<string, string> = {};
for (const project in assetsToTest) {
if (Object.prototype.hasOwnProperty.call(assetsToTest, project)) {
if (DebugManager.getInstance().isDebugEnabled()) {
showInfo(`${BUILDING_TEST} ${project}`);
}
const metadata = assetsToTest[project];
const result = await testAssets(rootDir, project, metadata);
displayErrorMessage(result, project);
if (DebugManager.getInstance().isDebugEnabled()) {
showInfo(`${BUILDING_TEST_SUCCESS} ${project}`);
}
const projectZip = new AdmZip(result.zipBuffer);
projectZip.getEntries().forEach((entry) => {
addFileToZip(combinedZip, entry);
});
apiReferences[project] = result.apiReference;
}
}
if (DebugManager.getInstance().isDebugEnabled()) {
showInfo(COMBINING_TEST_ZIP_SUCCESS);
}
return {
zipBuffer: combinedZip.toBuffer(),
apiReference: apiReferences
};
} catch (error) {
showError(`${ERROR_IN_COMBINING_TEST_ASSET} ${(error as Error).message}`);
throw error;
}
};
export const findProjectForApi = (apiReference: Record<string, string>, notFoundApisList: string[]): Record<string, string> => {
const apiReferenceMap: Record<string, string> = {};
for (const [project, reference] of Object.entries(apiReference)) {
const apiList = reference.split(',').map(api => api.trim());
for (const api of notFoundApisList) {
if (apiList.includes(api)) {
apiReferenceMap[project] = apiReferenceMap[project]
? `${apiReferenceMap[project]},${api}`
: api;
}
}
}
return apiReferenceMap;
};
export const buildAndDeployAssets = async (
localDir: string,
assetsToBuildAndDeploy: Record<string, string>,
gatewayJson: GatewaysJson
): Promise<{ buildBuffer: Buffer; deploymentResult: GatewayResponseAPI[] }> => {
const combinedZip = new AdmZip();
for (const [project, metadata] of Object.entries(assetsToBuildAndDeploy)) {
try {
const resultBuffer = await buildAssets(metadata, localDir, project);
const projectZip = new AdmZip(resultBuffer);
projectZip.getEntries().forEach((entry) => {
combinedZip.addFile(entry.entryName, entry.getData());
});
} catch (error) {
showError(`${ASSETS_BUILD_FAILED} ${project}: ${(error as Error).message}`);
}
}
const combinedZipBuffer = combinedZip.toBuffer();
const deploymentResult = await executeDeployment(gatewayJson, combinedZipBuffer);
return {
buildBuffer: combinedZipBuffer,
deploymentResult
};
};
export const testProjects = async (all: boolean, rootDir: string,projects: string): Promise<Record<string, string>> => {
if (all) {
showInfo(TESTING_LIST_ALL_PROJECTS + rootDir);
showInfo(CHECKING_ALL_PROJECTS + rootDir);
projects = getAllProjectNames(rootDir);
showInfo(IDENTIFIED_PROJECTS + projects);
}
return searchAssetByKind(KindEnums.Test,rootDir, projects);
};
export const testAssets = (rootDir: string,projects: string,assets: string): Promise<BuildTestAssetsResult> => {
showInfo(LINE);
showInfo(TEST_STARTED);
showInfo(LINE);
return executeBuildTestAssets(rootDir, projects, assets);
};
export const formattedEndpoints = (endpoints: Record<string, string[]>): void => {
const endPointsTable = new Table({
head: ['APIs', 'Gateway Endpoints'],
style: {
head: ['blue'],
border: ['yellow'],
},
});
Object.entries(endpoints).forEach(([api, urls]) => {
endPointsTable.push([api, urls.join('\n')]);
});
showInfo(APIENDPOINTS);
console.log(endPointsTable.toString());
};