@sap-ux/project-access
Version:
Library to access SAP Fiori tools projects
266 lines • 9.72 kB
JavaScript
import { dirname, join } from 'node:path';
import { ui5Libs } from './constants.js';
import { ReuseLibType } from '../types/index.js';
import { findFiles, readJSON } from '../file/index.js';
import { FileName } from '../constants.js';
import { existsSync, promises as fs } from 'node:fs';
import { XMLParser } from 'fast-xml-parser';
import { getI18nPropertiesPaths } from '../project/i18n/index.js';
import { getPropertiesI18nBundle } from '@sap-ux/i18n';
/**
* Reads the manifest file and returns the reuse library.
*
* @param manifest - manifest file content
* @param manifestPath - path to the manifest file
* @param reuseLibs - existing reuse libraries
* @param projectRoot - root of the project
* @returns reuse library or undefined
*/
const getLibraryFromManifest = async (manifest, manifestPath, reuseLibs, projectRoot) => {
let reuseLib;
const manifestType = manifest['sap.app']?.type;
if ((manifestType === 'component' || manifestType === 'library') && manifestPath) {
const reuseType = getReuseType(manifestPath);
const libDeps = getManifestDependencies(manifest);
const description = await getManifestDesc(manifest, manifestPath);
const libIndex = reuseLibs.findIndex((reuseLib) => reuseLib.name === manifest?.['sap.app']?.id);
if (libIndex === -1) {
reuseLib = {
name: `${manifest['sap.app']?.id ?? ''}`,
path: dirname(manifestPath),
type: reuseType,
uri: manifest['sap.platform.abap']?.uri ?? '',
dependencies: libDeps,
libRoot: projectRoot,
description
};
}
}
return reuseLib;
};
/**
* Reads library file and returns a reuse lib object.
*
* @param library - library file content
* @param libraryPath - path to the library file
* @param projectRoot - root of the project
* @returns reuse library or undefined
*/
const getLibraryFromLibraryFile = async (library, libraryPath, projectRoot) => {
let libEntry;
const parsedFile = new XMLParser({ removeNSPrefix: true }).parse(library, false);
if (parsedFile?.library?.name) {
const manifestType = parsedFile?.library ? 'library' : 'component';
if (manifestType === 'component' || manifestType === 'library') {
const reuseType = getReuseType(libraryPath);
const libDeps = getLibraryDependencies(parsedFile);
const description = await getLibraryDesc(parsedFile, libraryPath);
libEntry = {
name: `${parsedFile.library.name}`,
path: dirname(libraryPath),
type: reuseType,
uri: parsedFile.library?.appData?.manifest?.['sap.platform.abap']?.uri ?? '',
dependencies: libDeps,
libRoot: projectRoot,
description
};
}
}
return libEntry;
};
/**
* Updates the library options with the new library.
*
* @param reuseLibs - existing library options
* @param reuseLib - new library
*/
const updateLibOptions = (reuseLibs, reuseLib) => {
if (reuseLib) {
const libIndex = reuseLibs.findIndex((lib) => lib.name === reuseLib.name);
if (libIndex >= 0) {
// replace
reuseLibs[libIndex] = reuseLib;
}
else {
reuseLibs.push(reuseLib);
}
}
};
/**
* Returns an array of the reuse libraries found in the folders.
*
* @param libs - array of libraries found in the workspace folders.
* @returns list of reuse library
*/
export const getReuseLibs = async (libs) => {
const reuseLibs = [];
if (libs) {
for (const lib of libs) {
const excludeFolders = ['.git', 'node_modules', 'dist'];
const manifestPaths = await findFiles('manifest.json', lib.projectRoot, excludeFolders);
const libraryPaths = [
...(await findFiles('library.js', lib.projectRoot, excludeFolders)),
...(await findFiles('library.ts', lib.projectRoot, excludeFolders))
];
for (const manifestPath of manifestPaths) {
const manifestFilePath = join(manifestPath, FileName.Manifest);
const manifest = await readJSON(manifestFilePath);
const library = await getLibraryFromManifest(manifest, manifestFilePath, reuseLibs, lib.projectRoot);
if (library) {
reuseLibs.push(library);
}
}
for (const libraryPath of libraryPaths) {
try {
const libraryFilePath = join(libraryPath, FileName.Library);
const library = (await fs.readFile(libraryFilePath, { encoding: 'utf8' })).toString();
const libFile = await getLibraryFromLibraryFile(library, libraryFilePath, lib.projectRoot);
updateLibOptions(reuseLibs, libFile);
}
catch {
// ignore exception
}
}
}
}
return reuseLibs;
};
/**
* Gets the type of reuse library.
*
* @param libraryPath - path to the reuse library
* @returns the type of reuse library
*/
function getReuseType(libraryPath) {
return existsSync(join(dirname(libraryPath), '/library.js')) ||
existsSync(join(dirname(libraryPath), '/library.ts'))
? ReuseLibType.Library
: ReuseLibType.Component;
}
/**
* Checks for missing dependencies in the selected reuse libraries.
*
* @param answers - reuse libraries selected by the user
* @param reuseLibs - all available reuse libraries
* @returns a string with the missing dependencies
*/
export function checkDependencies(answers, reuseLibs) {
const missingDeps = [];
answers.forEach((answer) => {
const dependencies = answer.dependencies;
if (dependencies?.length) {
dependencies.forEach((dependency) => {
if (!reuseLibs.some((lib) => {
return dependency === lib.name;
})) {
missingDeps.push(dependency);
}
});
}
});
return missingDeps.join();
}
/**
* Returns the library description.
*
* @param library - library object
* @param libraryPath - library path
* @returns library description
*/
export async function getLibraryDesc(library, libraryPath) {
let libraryDesc = library?.library?.documentation;
if (typeof libraryDesc === 'string' && libraryDesc.startsWith('{{')) {
const key = libraryDesc.substring(2, libraryDesc.length - 2);
libraryDesc = await geti18nPropertyValue(join(dirname(libraryPath), library.library?.appData?.manifest?.i18n?.toString() ?? ''), key);
}
return libraryDesc?.toString() ?? '';
}
/**
* Returns the library dependencies.
*
* @param library - library object
* @returns array of dependencies
*/
export function getLibraryDependencies(library) {
const result = [];
if (library?.library?.dependencies?.dependency) {
let deps = library.library.dependencies.dependency;
if (!Array.isArray(deps)) {
deps = [deps];
}
deps.forEach((lib) => {
// ignore libs that start with SAPUI5 delivered namespaces
if (!ui5Libs.some((substring) => {
return lib.libraryName === substring || lib.libraryName.startsWith(substring + '.');
})) {
result.push(lib.libraryName);
}
});
}
return result;
}
/**
* Returns the i18n property value.
*
* @param i18nPath - i18n path
* @param key - property key
* @returns i18n property value
*/
async function geti18nPropertyValue(i18nPath, key) {
let value = '';
try {
const bundle = await getPropertiesI18nBundle(i18nPath);
const node = bundle[key].find((i) => i.key.value === key);
if (node) {
value = node.value.value;
}
}
catch (e) {
// ignore exception
}
return value;
}
/**
* Returns the manifest description.
*
* @param manifest - manifest object
* @param manifestPath - manifestPath path
* @returns manifest description
*/
export async function getManifestDesc(manifest, manifestPath) {
let manifestDesc = manifest['sap.app']?.description;
if (typeof manifestDesc === 'string' && manifestDesc.startsWith('{{')) {
const key = manifestDesc.substring(2, manifestDesc.length - 2);
const { 'sap.app': i18nPath } = await getI18nPropertiesPaths(manifestPath, manifest);
manifestDesc = await geti18nPropertyValue(i18nPath, key);
}
return (manifestDesc ?? '').toString();
}
/**
* Returns the manifest dependencies.
*
* @param manifest - manifest object
* @returns array of dependencies
*/
export function getManifestDependencies(manifest) {
const result = [];
const depTypes = ['libs', 'components'];
Object.values(depTypes).forEach((reuseType) => {
const dependencies = manifest['sap.ui5']?.dependencies?.[reuseType];
if (dependencies) {
const libs = manifest?.['sap.ui5']?.dependencies?.libs;
if (libs) {
Object.keys(libs).forEach((manifestLibKey) => {
// ignore libs that start with SAPUI5 delivered namespaces
if (!ui5Libs.some((substring) => {
return manifestLibKey === substring || manifestLibKey.startsWith(substring + '.');
})) {
result.push(manifestLibKey);
}
});
}
}
});
return result;
}
//# sourceMappingURL=helpers.js.map