UNPKG

@sap-ux/project-access

Version:

Library to access SAP Fiori tools projects

255 lines 9.36 kB
import { join, relative } from 'node:path'; import { DirName, FileName } from '../constants.js'; import { fileExists, findFilesByExtension, readJSON } from '../file/index.js'; import { getCapCustomPaths, getCapProjectType } from './cap.js'; import { getI18nPropertiesPaths } from './i18n/i18n.js'; import { findAllApps, findFioriArtifacts } from './search.js'; import { getMainService, getServicesAndAnnotations } from './service.js'; import { getWebappPath } from './ui5-config.js'; import { gte, valid } from 'semver'; import { normalizePath } from '../path/index.js'; /** * Returns the project structure for a given Fiori project. * * @param root - project root folder * @param memFs - optional mem-fs-editor instance * @returns - project structure with project info like project type, apps, root folder */ export async function getProject(root, memFs) { if (!(await fileExists(join(root, FileName.Package), memFs))) { throw new Error(`The project root folder '${root}' is not a Fiori project. No 'package.json' found.`); } const capProjectType = await getCapProjectType(root); const projectType = capProjectType ?? 'EDMXBackend'; const capCustomPaths = projectType === 'EDMXBackend' ? undefined : await getCapCustomPaths(root); const appFolders = await getAppFolders(root, memFs); const apps = await getApps(root, appFolders, memFs); return { root: normalizePath(root), projectType, apps, capCustomPaths }; } /** * Returns the applications for the project. For single app * projects, this is just an array with one empty string. For CAP projects, this is an * array of operating system specific relative paths to the apps. * * @param root - project root folder * @param memFs - optional mem-fs-editor instance * @returns - array of operating specific application folders */ async function getAppFolders(root, memFs) { const apps = await findAllApps([root], memFs); return apps.length > 0 ? apps.map((app) => relative(root, app.appRoot)) : ['']; } /** * Get the application structure for each application in the project. * * @param root - project root folder * @param appFolders - array of relative application folders * @param memFs - optional mem-fs-editor instance * @returns - map of application structures */ async function getApps(root, appFolders, memFs) { const apps = {}; for (const appFolder of appFolders) { const applicationStructure = await getApplicationStructure(root, appFolder, memFs); if (applicationStructure) { apps[appFolder] = applicationStructure; } } return apps; } /** * Get the application structure for a given application. * * @param root - project root folder * @param appFolder - relative application folder * @param memFs - optional mem-fs-editor instance * @returns - application structure with application info like manifest, changes, main service, services, annotations */ async function getApplicationStructure(root, appFolder, memFs) { const appRoot = join(root, appFolder); const absoluteWebappPath = await getWebappPath(appRoot, memFs); const appType = (await getAppType(appRoot, memFs)); const manifest = join(absoluteWebappPath, FileName.Manifest); if (!(await fileExists(manifest, memFs))) { return undefined; } const manifestObject = await readJSON(manifest, memFs); const changes = join(absoluteWebappPath, DirName.Changes); const i18n = await getI18nPropertiesPaths(manifest, manifestObject, memFs); const mainService = getMainService(manifestObject); const services = await getServicesAndAnnotations(manifest, manifestObject, memFs); return { appRoot, appType, manifest, changes, i18n, mainService, services }; } /** * Get the used programming language of an application. * * @param appRoot - root folder of the application * @param [memFs] - optional mem-fs editor instance * @returns - used language, JavaScript or TypeScript */ export async function getAppProgrammingLanguage(appRoot, memFs) { const ignoreFolders = ['node_modules', '.git']; let appLanguage = ''; try { const webappPath = await getWebappPath(appRoot, memFs); if (await fileExists(webappPath, memFs)) { if ((await fileExists(join(appRoot, FileName.Tsconfig), memFs)) && (await findFilesByExtension('.ts', webappPath, ignoreFolders, memFs)).length > 0) { appLanguage = 'TypeScript'; } else if ((await findFilesByExtension('.js', webappPath, ignoreFolders, memFs)).length > 0) { appLanguage = 'JavaScript'; } } } catch { // could not detect app language } return appLanguage; } /** * Get the type of application or Fiori artifact. * * @param appRoot - path to application root * @param memFs - optional mem-fs-editor instance * @returns - type of application, e.g. SAP Fiori elements, SAPUI5 freestyle, SAPUI5 Extension, ... see AppType. */ export async function getAppType(appRoot, memFs) { let appType; try { const artifacts = await findFioriArtifacts({ wsFolders: [appRoot], artifacts: ['adaptations', 'applications', 'extensions', 'libraries'], memFs }); if ((artifacts.applications?.length ?? 0) + (artifacts.adaptations?.length ?? 0) + (artifacts.extensions?.length ?? 0) + (artifacts.libraries?.length ?? 0) === 1) { if (artifacts.applications?.length === 1) { appType = await getApplicationType(artifacts.applications[0], memFs); } else if (artifacts.adaptations?.length === 1) { appType = 'Fiori Adaptation'; } else if (artifacts.extensions?.length === 1) { appType = 'SAPUI5 Extension'; } else if (artifacts.libraries?.length === 1) { appType = 'Fiori Reuse'; } } } catch { // If error occurs we can't determine the type and return undefined } return appType; } /** * Get the application type from search results. * * @param application - application from findFioriArtifacts() results * @param memFs - optional mem-fs-editor instance * @returns - type of application: 'SAP Fiori elements' or 'SAPUI5 freestyle' */ async function getApplicationType(application, memFs) { let appType; const rootPackageJsonPath = join(application.projectRoot, FileName.Package); const packageJson = (await fileExists(rootPackageJsonPath, memFs)) ? await readJSON(rootPackageJsonPath, memFs) : null; if (application.projectRoot === application.appRoot) { appType = packageJson?.sapux ? 'SAP Fiori elements' : 'SAPUI5 freestyle'; } else if (packageJson) { appType = Array.isArray(packageJson.sapux) && packageJson.sapux.find((relAppPath) => join(application.projectRoot, ...relAppPath.split(/[/\\]/)) === application.appRoot) ? 'SAP Fiori elements' : 'SAPUI5 freestyle'; } else { appType = 'SAPUI5 freestyle'; } return appType; } /** * Returns the project type for a given Fiori project. * * @param projectRoot - root path of the project * @returns - project type like Edmx, CAPJava, CAPNodejs */ export async function getProjectType(projectRoot) { const capType = await getCapProjectType(projectRoot); if (capType === undefined) { return 'EDMXBackend'; } return capType; } /** * Returns the minUI5Version, as defined in manifest. * * @param manifest - manifest object * @returns minUI5Version, if present */ export function getMinUI5VersionFromManifest(manifest) { const dependencies = manifest['sap.ui5']?.dependencies; if (dependencies) { return dependencies.minUI5Version; } return undefined; } /** * Returns the valid minUI5Version(s) as string[]. * * @param manifest - manifest object * @param noValidation - disables the semver validation * @returns minUI5Version, as an array of strings */ export function getMinUI5VersionAsArray(manifest, noValidation = false) { const result = []; const minUI5Version = getMinUI5VersionFromManifest(manifest); if (minUI5Version) { const minUI5VersionArray = Array.isArray(minUI5Version) ? minUI5Version : [minUI5Version]; minUI5VersionArray.forEach((version) => { if (noValidation || valid(version)) { result.push(version); } }); } return result; } /** * Returns the minUI5Version in string format (if valid). * If it is defined as an array, returns the minimum valid version from it. * * @param manifest - manifest object * @returns the minimum version as string */ export function getMinimumUI5Version(manifest) { let result; const validVersionsArray = getMinUI5VersionAsArray(manifest); if (validVersionsArray.length > 0) { validVersionsArray.forEach((version) => { if (!result || gte(result, version)) { result = version; } }); } return result; } //# sourceMappingURL=info.js.map