@apistudio/apim-cli
Version:
CLI for API Management Products
181 lines • 7.23 kB
JavaScript
/**
* Copyright IBM Corp. 2024, 2025
*/
import { checkFileExtension, isRelativePath, isValidAsset, ZipProcessor, } from '../index.js';
import { Logger } from '@apic/studio-shared';
import { BuildProjectAssets } from '../build-project-assets.js';
import yaml from 'js-yaml';
import path from 'path';
import JSZip from 'jszip';
import { AppConstants } from '../constants/app.constants.js';
export const processGatewayJson = async (fileBuffer) => {
Logger.info('Starting extraction of gateway JSON.');
const obj = new ZipProcessor();
return obj.extractGatewaysJson(fileBuffer);
};
export const normalizeZipPaths = async (zip) => {
Logger.debug('Starting path normalization in zip file.');
const normalizedZip = new JSZip();
await Promise.all(Object.values(zip.files).map(async (file) => {
const normalizedPath = path.normalize(file.name);
if (file.dir) {
normalizedZip.folder(normalizedPath);
}
else {
const fileData = await file.async('nodebuffer');
normalizedZip.file(normalizedPath, fileData);
Logger.debug(`Normalized file: ${normalizedPath}`);
}
}));
Logger.debug('Path normalization completed.');
return normalizedZip;
};
export const updateRelativePath = async (asset, basePath) => {
const extract = (obj) => {
for (const key in obj) {
const value = obj[key];
if (key === AppConstants.pathVariable && typeof value === 'string') {
if (isRelativePath(value)) {
const baseDir = path.dirname(basePath);
const resolvedPath = path.join(baseDir, value);
const normalizedPath = path.normalize(resolvedPath).replace(/\\+/g, '/');
obj[key] = normalizedPath.slice(normalizedPath.indexOf('/') + 1);
}
}
else if (typeof value === 'object' && value !== null) {
extract(value);
}
}
};
if (asset.spec) {
extract(asset.spec);
}
return asset;
};
export const updatePathValueInContent = async (content, basePath) => {
const parsedObjs = yaml.loadAll(content);
const updatedObjs = await Promise.all(parsedObjs.map(async (obj) => {
if (isValidAsset(obj)) {
return await updateRelativePath(obj, basePath);
}
return obj;
}));
return updatedObjs.map((obj) => yaml.dump(obj)).join('---\n');
};
export const resolveRelativePaths = async (zipBuffer) => {
Logger.debug('Starting path normalization in zip file.');
const zip = await JSZip.loadAsync(zipBuffer);
const resolvedZip = new JSZip();
await Promise.all(Object.values(zip.files).map(async (file) => {
if (file.dir) {
resolvedZip.folder(file.name);
Logger.debug(`Resolved directory: ${file.name}`);
}
else {
const nameArray = file.name.split(path.sep);
const name = nameArray[nameArray.length - 1];
if (name && checkFileExtension(name)) {
const content = await file.async('string');
if (!content || content.trim().length === 0) {
Logger.info(`Skipping empty file: ${file.name}`);
return;
}
const updatedContent = await updatePathValueInContent(content, file.name);
resolvedZip.file(file.name, updatedContent);
}
else {
const fileData = await file.async('nodebuffer');
resolvedZip.file(file.name, fileData);
}
}
}));
Logger.debug('Path normalization completed.');
return resolvedZip;
};
export const posixNormalization = async (zip) => {
const normalizedZip = new JSZip();
await Promise.all(Object.values(zip.files).map(async (file) => {
const normalizedPath = file.name.replace(/\\/g, '/').replace(/\/+/g, '/');
if (file.dir) {
normalizedZip.folder(normalizedPath);
Logger.debug(`POSIX normalized directory: ${normalizedPath}`, {
code: '0301',
type: 'directory',
});
}
else {
const fileData = await file.async('nodebuffer');
normalizedZip.file(normalizedPath, fileData);
Logger.debug(`POSIX normalized file: ${normalizedPath}`, {
code: '0301',
type: 'file',
});
}
}));
return normalizedZip;
};
export const processProjectBuild = async (fileBuffer, _mode) => {
Logger.info('Starting project build processing.');
try {
const zip = await JSZip.loadAsync(fileBuffer);
Logger.debug('Loading and normalizing zip paths.');
const normalizedZip = await normalizeZipPaths(zip);
const normalizedBuffer = await normalizedZip.generateAsync({
type: 'nodebuffer',
});
const resolvedZip = await resolveRelativePaths(normalizedBuffer);
Logger.debug('Normalized zip buffer generated.');
const obj = new BuildProjectAssets();
Logger.info('Processing normalized project zip.');
const { zip: result, errors } = await obj.processProjectZip(await resolvedZip.generateAsync({
type: 'nodebuffer',
}), _mode);
if (!result) {
const errorDetail = errors?.[0] || '';
Logger.error(`Project build processing failed. Error: ${errorDetail}`, undefined, {
code: '0003',
});
let errorMessage = '';
if (errorDetail) {
errorMessage = `${errorDetail}. Review the error and try publishing again.`;
}
else {
errorMessage = `Project build failed. Check your configuration and try again.`;
}
// Return standardized error response
return {
success: false,
statusCode: 400,
message: 'Project build validation failed',
data: null,
errors: [errorMessage],
};
}
Logger.info('Project build processing succeeded.');
const temporary_zip = await posixNormalization(result);
const zipBuffer = await temporary_zip.generateAsync({ type: 'nodebuffer' });
// Return successful response with the zip buffer
return {
success: true,
statusCode: 200,
message: 'Project build successful',
data: zipBuffer,
errors: [],
};
}
catch (error) {
// Handle unexpected errors
const errorMessage = error instanceof Error
? error.message
: `Project build failed. The project has problems we couldn't identify.`;
Logger.error(`Unexpected error in project build: ${errorMessage}`, error instanceof Error ? error : new Error(String(error)));
return {
success: false,
statusCode: 500,
message: 'Project build validation failed',
data: null,
errors: [errorMessage],
};
}
};
//# sourceMappingURL=build-manager.js.map