apim-policy-utils
Version:
An XML file scripts maniputaling and debugging tool targeting to help working with Azure APIM Policy files in xml format.
177 lines (154 loc) • 6.57 kB
text/typescript
import * as fs from 'fs';
import path from 'path';
import { separator } from './constants';
export async function combineScript(directoryPath: string, destinationPath?: string) {
const filenames = await getFilenamesInDirectory(directoryPath);
// Compute the xml filename for storing the result
const dirArray = directoryPath.split('/')
const xmlFilename = `${dirArray[dirArray.length - 1]}.xml`
// Read xmlContent from the generated xmlfile
let xmlFileContent = fs.readFileSync(`${directoryPath}/replaced.xml`, 'utf8');
// Get code outside of block-xxx.csx file and inline-xxx.csx file
filenames.forEach(filename => {
if ((filename.startsWith('inline') || filename.startsWith('block')) && filename.endsWith('.csx')) {
let codeSnippet = getCodeInMethod(`${directoryPath}/${filename}`, 'ExtractedScript')
codeSnippet = refineCode(filename, codeSnippet);
const xmlPlaceholder = filename.slice(0, -4);
xmlFileContent = replaceAll(xmlFileContent, xmlPlaceholder, codeSnippet)
}
// Write the combined XML to a file
if (!destinationPath) {
fs.writeFileSync(`${directoryPath}/${xmlFilename}`, xmlFileContent);
} else {
updateFileInDirectory(destinationPath, xmlFilename, xmlFileContent)
.then(() => {
console.log('File update complete.');
})
.catch((error) => {
console.error('An error occurred while updating the file:', error);
});
}
});
}
function refineCode(file: string, codeSnippet: string | null): string | null {
if (codeSnippet === null) {
return '';
}
const inlinePrefix = "return "
if (file.startsWith('inline')) {
codeSnippet = codeSnippet.trim()
if (codeSnippet.startsWith(inlinePrefix)) {
codeSnippet = codeSnippet.substring(inlinePrefix.length).trim() // remove "return " prefix
return codeSnippet.slice(0, -1) // remove ";" suffix
}
}
if (file.startsWith('block')) {
const lines = codeSnippet.split('\n').map(line => line);
const nonEmptyLines = lines.filter(line => line !== '');
const indentation = nonEmptyLines[0].match(/^\s*/)?.[0];
const formattedContent = nonEmptyLines.map((line, index) => {
if (index === 0 || index === nonEmptyLines.length - 1) {
return line;
}
return `${indentation}${line}`;
}).join('\n');
return `${formattedContent}`
}
return codeSnippet
}
function getFilenamesInDirectory(directoryPath: string): Promise<string[]> {
return new Promise((resolve, reject) => {
fs.readdir(directoryPath, (err, files) => {
if (err) {
reject(err);
} else {
const filenames: string[] = [];
files.forEach((file) => {
filenames.push(file);
});
resolve(filenames);
}
});
});
}
function getCodeInMethod(csxFilePath: string, methodName: string): string | null {
try {
// Read the contents of the file at the given path
const fileContents: string = fs.readFileSync(csxFilePath, 'utf8');
// Find the starting index of the desired method
const startRegex = new RegExp(`(?<=\\b(?:public|private|internal)?\\s+(?:async\\s+)?(?:static\\s+)?(?:readonly\\s+)?(?:partial\\s+)?(?:unsafe\\s+)?(?:virtual\\s+)?(?:override\\s+)?\\w+\\s+${methodName}\\s*\\()`);
const startIndex: number = fileContents.search(startRegex);
if (startIndex === -1) {
console.error(`Method '${methodName}' not found in file at path '${csxFilePath}'`);
return null;
}
// Find the ending index of the desired method
let openBraces = 0;
let actualStartIndex: number = startIndex;
let endIndex: number = startIndex;
for (let i = startIndex; i < fileContents.length; i++) {
if (fileContents[i] === '{') {
openBraces++;
if (openBraces === 1) {
actualStartIndex = i;
}
} else if (fileContents[i] === '}') {
openBraces--;
if (openBraces === 0) {
endIndex = i;
break;
}
}
}
// The code within the method
let codeInMethod: string = fileContents.slice(actualStartIndex, endIndex + 1);
codeInMethod = removeSurroundingChars(codeInMethod);
// Remove everything before the generated sepatators
codeInMethod = removeCodeAboveSeparator(codeInMethod);
codeInMethod = convertNamedValue(codeInMethod)
return codeInMethod;
} catch (error: any) {
console.error(`Error reading file at path '${csxFilePath}': ${error.message}`);
return null;
}
}
function replaceAll(xml: string, toReplace: string, replacement: string | null): string {
const regex = new RegExp(toReplace, 'g');
replacement = replacement ? replacement : '';
xml = xml.replace(regex, replacement);
return xml;
}
function removeSurroundingChars(str: string): string {
if (str.startsWith('{') && str.endsWith('}')) {
str = str.slice(1, -1);
}
return str;
}
function removeCodeAboveSeparator(input: string): string {
const separatorIndex = input.indexOf(separator);
if (separatorIndex === -1) {
return "";
}
return input.substring(separatorIndex + separator.length);
}
function convertNamedValue(input: string): string {
const regex = /{nv_(\w+)}/g;
return input.replace(regex, (match, p1) => `{{${p1.replace(/_/g, "-")}}}`);
}
async function updateFileInDirectory(destinationPath: string, fileName: string, content: string): Promise<void> {
const files = fs.readdirSync(destinationPath);
if (files.includes(fileName)) {
const filePath = path.join(destinationPath, fileName);
fs.writeFileSync(filePath, content);
return;
}
for (const file of files) {
const filePath = path.join(destinationPath, file);
const isDirectory = fs.statSync(filePath).isDirectory();
if (isDirectory) {
await updateFileInDirectory(filePath, fileName, content);
}
}
const rootFilePath = path.join(destinationPath, fileName);
fs.writeFileSync(rootFilePath, content);
}