import path from "node:path";
import projectGraphBuilder from "./projectGraphBuilder.js";
import ui5Framework from "./helpers/ui5Framework.js";
import createWorkspace from "./helpers/createWorkspace.js";
import {getLogger} from "@ui5/logger";
const log = getLogger("generateProjectGraph");
/**
* Helper module to create a [@ui5/project/graph/ProjectGraph]{@link @ui5/project/graph/ProjectGraph}
* from a directory
*
* @public
* @module @ui5/project/graph
*/
/**
* Generates a [@ui5/project/graph/ProjectGraph]{@link @ui5/project/graph/ProjectGraph} by resolving
* dependencies from package.json files and configuring projects from ui5.yaml files
*
* @public
* @static
* @param {object} [options]
* @param {string} [options.cwd=process.cwd()] Directory to start searching for the root module
* @param {object} [options.rootConfiguration]
* Configuration object to use for the root module instead of reading from a configuration file
* @param {string} [options.rootConfigPath]
* Configuration file to use for the root module instead the default ui5.yaml. Either a path relative to
* <code>cwd</code> or an absolute path. In both case, platform-specific path segment separators must be used.
* @param {string} [options.versionOverride] Framework version to use instead of the one defined in the root project
* @param {string} [options.resolveFrameworkDependencies=true]
* Whether framework dependencies should be added to the graph
* @param {string|null} [options.workspaceName=default]
* Name of the workspace configuration that should be used. "default" if not provided.
* @param {module:@ui5/project/ui5Framework/maven/CacheMode} [options.cacheMode]
* Cache mode to use when consuming SNAPSHOT versions of a framework
* @param {string} [options.workspaceConfigPath=ui5-workspace.yaml]
* Workspace configuration file to use if no object has been provided
* @param {@ui5/project/graph/Workspace~Configuration} [options.workspaceConfiguration]
* Workspace configuration object to use instead of reading from a configuration file.
* Parameter <code>workspaceName</code> can either be omitted or has to match with the given configuration name
* @returns {Promise<@ui5/project/graph/ProjectGraph>} Promise resolving to a Project Graph instance
*/
export async function graphFromPackageDependencies({
cwd, rootConfiguration, rootConfigPath,
versionOverride, cacheMode, resolveFrameworkDependencies = true,
workspaceName="default",
workspaceConfiguration, workspaceConfigPath = "ui5-workspace.yaml"
}) {
log.verbose(`Creating project graph using npm provider...`);
const {
default: NpmProvider
} = await import("./providers/NodePackageDependencies.js");
cwd = cwd ? path.resolve(cwd) : process.cwd();
rootConfigPath = utils.resolveConfigPath(cwd, rootConfigPath);
let workspace;
if (workspaceName || workspaceConfiguration) {
workspace = await createWorkspace({
cwd,
name: workspaceName,
configObject: workspaceConfiguration,
configPath: workspaceConfigPath
});
}
const provider = new NpmProvider({
cwd,
rootConfiguration,
rootConfigPath
});
const projectGraph = await projectGraphBuilder(provider, workspace);
if (resolveFrameworkDependencies) {
await ui5Framework.enrichProjectGraph(projectGraph, {versionOverride, cacheMode, workspace});
}
return projectGraph;
}
/**
* Generates a [@ui5/project/graph/ProjectGraph]{@link @ui5/project/graph/ProjectGraph} from a
* YAML file following the structure of
* [@ui5/project/graph/providers/DependencyTree~TreeNode]{@link @ui5/project/graph/providers/DependencyTree~TreeNode}.
*
* Documentation:
* [Static Dependency Definition]{@link https://sap.github.io/ui5-tooling/stable/pages/Overview/#static-dependency-definition}
*
* @public
* @static
* @param {object} options
* @param {object} [options.filePath=projectDependencies.yaml] Path to the dependency configuration file
* @param {string} [options.cwd=process.cwd()] Directory to resolve relative paths to
* @param {object} [options.rootConfiguration]
* Configuration object to use for the root module instead of reading from a configuration file
* @param {string} [options.rootConfigPath]
* Configuration file to use for the root module instead the default ui5.yaml. Either a path relative to
* <code>cwd</code> or an absolute path. In both case, platform-specific path segment separators must be used.
* @param {string} [options.versionOverride] Framework version to use instead of the one defined in the root project
* @param {module:@ui5/project/ui5Framework/maven/CacheMode} [options.cacheMode]
* Cache mode to use when consuming SNAPSHOT versions of a framework
* @param {string} [options.resolveFrameworkDependencies=true]
* Whether framework dependencies should be added to the graph
* @returns {Promise<@ui5/project/graph/ProjectGraph>} Promise resolving to a Project Graph instance
*/
export async function graphFromStaticFile({
filePath = "projectDependencies.yaml", cwd,
rootConfiguration, rootConfigPath,
versionOverride, cacheMode, resolveFrameworkDependencies = true
}) {
log.verbose(`Creating project graph using static file...`);
const {
default: DependencyTreeProvider
} = await import("./providers/DependencyTree.js");
cwd = cwd ? path.resolve(cwd) : process.cwd();
rootConfigPath = utils.resolveConfigPath(cwd, rootConfigPath);
const dependencyTree = await utils.readDependencyConfigFile(cwd, filePath);
const provider = new DependencyTreeProvider({
dependencyTree,
rootConfiguration,
rootConfigPath
});
const projectGraph = await projectGraphBuilder(provider);
if (resolveFrameworkDependencies) {
await ui5Framework.enrichProjectGraph(projectGraph, {versionOverride, cacheMode});
}
return projectGraph;
}
/**
* Generates a [@ui5/project/graph/ProjectGraph]{@link @ui5/project/graph/ProjectGraph} from the
* given <code>dependencyTree</code> following the structure of
* [@ui5/project/graph/providers/DependencyTree~TreeNode]{@link @ui5/project/graph/providers/DependencyTree~TreeNode}
*
* @public
* @static
* @param {object} options
* @param {@ui5/project/graph/providers/DependencyTree~TreeNode} options.dependencyTree
* @param {string} [options.cwd=process.cwd()] Directory to resolve relative paths to
* @param {object} [options.rootConfiguration]
* Configuration object to use for the root module instead of reading from a configuration file
* @param {string} [options.rootConfigPath]
* Configuration file to use for the root module instead the default ui5.yaml. Either a path relative to
* <code>cwd</code> or an absolute path. In both case, platform-specific path segment separators must be used.
* @param {string} [options.versionOverride] Framework version to use instead of the one defined in the root project
* @param {module:@ui5/project/ui5Framework/maven/CacheMode} [options.cacheMode]
* Cache mode to use when consuming SNAPSHOT versions of a framework
* @param {string} [options.resolveFrameworkDependencies=true]
* Whether framework dependencies should be added to the graph
* @returns {Promise<@ui5/project/graph/ProjectGraph>} Promise resolving to a Project Graph instance
*/
export async function graphFromObject({
dependencyTree, cwd,
rootConfiguration, rootConfigPath,
versionOverride, cacheMode, resolveFrameworkDependencies = true
}) {
log.verbose(`Creating project graph using object...`);
const {
default: DependencyTreeProvider
} = await import("./providers/DependencyTree.js");
cwd = cwd ? path.resolve(cwd) : process.cwd();
rootConfigPath = utils.resolveConfigPath(cwd, rootConfigPath);
const dependencyTreeProvider = new DependencyTreeProvider({
dependencyTree,
rootConfiguration,
rootConfigPath
});
const projectGraph = await projectGraphBuilder(dependencyTreeProvider);
if (resolveFrameworkDependencies) {
await ui5Framework.enrichProjectGraph(projectGraph, {versionOverride, cacheMode});
}
return projectGraph;
}
const utils = {
resolveConfigPath: function(cwd, configPath) {
if (configPath && !path.isAbsolute(configPath)) {
configPath = path.join(cwd, configPath);
}
return configPath;
},
readDependencyConfigFile: async function(cwd, filePath) {
const {
default: fs
} = await import("graceful-fs");
const {promisify} = await import("util");
const readFile = promisify(fs.readFile);
const parseYaml =(await import("js-yaml")).load;
filePath = utils.resolveConfigPath(cwd, filePath);
let dependencyTree;
try {
const contents = await readFile(filePath, {encoding: "utf-8"});
dependencyTree = parseYaml(contents, {
filename: filePath
});
utils.resolveProjectPaths(cwd, dependencyTree);
} catch (err) {
throw new Error(
`Failed to load dependency tree configuration from path ${filePath}: ${err.message}`);
}
return dependencyTree;
},
resolveProjectPaths: function(cwd, project) {
if (!project.path) {
throw new Error(`Missing or empty attribute 'path' for project ${project.id}`);
}
project.path = path.resolve(cwd, project.path);
if (!project.id) {
throw new Error(`Missing or empty attribute 'id' for project with path ${project.path}`);
}
if (!project.version) {
throw new Error(`Missing or empty attribute 'version' for project ${project.id}`);
}
if (project.dependencies) {
project.dependencies.forEach((project) => utils.resolveProjectPaths(cwd, project));
}
return project;
}
};
// Export function for testing only
/* istanbul ignore else */
if (process.env.NODE_ENV === "test") {
graphFromStaticFile._utils = utils;
}