UNPKG

@apistudio/apim-cli

Version:

CLI for API Management Products

181 lines 7.23 kB
/** * 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