Skip to content

Commit

Permalink
[FIX] graph: Always resolve rootConfigPath to CWD
Browse files Browse the repository at this point in the history
This should restore the v2 behavior where referencing a ui5.yaml file
via the '--config' CLI parameter can always be done with a path relative
to the directory the CLI has been invoked in.

v3 used to pass the relative path to graph/Module which would resolve it
against the module's root path (typically the location of the
package.json) using @ui5/fs.

This can be unexpected, for example if the CLI is invoked in a
subdirectory of the module. Also, referencing configuration files
outside of the module (e.g. "../ui5.yaml") was no longer possible.
  • Loading branch information
RandomByte committed Feb 14, 2023
1 parent 1cd94f7 commit ef3e569
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 61 deletions.
79 changes: 47 additions & 32 deletions lib/graph/graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,6 @@ import createWorkspace from "./helpers/createWorkspace.js";
import {getLogger} from "@ui5/logger";
const log = getLogger("generateProjectGraph");

function resolveProjectPaths(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) => resolveProjectPaths(cwd, project));
}
return project;
}

/**
* Helper module to create a [@ui5/project/graph/ProjectGraph]{@link @ui5/project/graph/ProjectGraph}
* from a directory
Expand All @@ -43,7 +24,8 @@ function resolveProjectPaths(cwd, project) {
* @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
* 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
Expand All @@ -67,6 +49,7 @@ export async function graphFromPackageDependencies({
} = await import("./providers/NodePackageDependencies.js");

cwd = cwd ? path.resolve(cwd) : process.cwd();
rootConfigPath = utils.resolveConfigPath(cwd, rootConfigPath);

let workspace;
if (workspaceName || workspaceConfiguration) {
Expand Down Expand Up @@ -106,24 +89,28 @@ export async function graphFromPackageDependencies({
* @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
* 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
* @returns {Promise<@ui5/project/graph/ProjectGraph>} Promise resolving to a Project Graph instance
*/
export async function graphFromStaticFile({
cwd, filePath = "projectDependencies.yaml",
filePath = "projectDependencies.yaml", cwd,
rootConfiguration, rootConfigPath,
versionOverride, resolveFrameworkDependencies = true
}) {
log.verbose(`Creating project graph using static file...`);

const dependencyTree = await utils.readDependencyConfigFile(cwd ? path.resolve(cwd) : process.cwd(), filePath);

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,
Expand All @@ -148,25 +135,30 @@ export async function graphFromStaticFile({
* @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
* 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
* @returns {Promise<@ui5/project/graph/ProjectGraph>} Promise resolving to a Project Graph instance
*/
export async function graphFromObject({
dependencyTree,
dependencyTree, cwd,
rootConfiguration, rootConfigPath,
versionOverride, 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,
Expand All @@ -183,6 +175,12 @@ export async function graphFromObject({
}

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
Expand All @@ -191,22 +189,39 @@ const utils = {
const readFile = promisify(fs.readFile);
const parseYaml =(await import("js-yaml")).load;

if (!path.isAbsolute(filePath)) {
filePath = path.join(cwd, filePath);
}
filePath = utils.resolveConfigPath(cwd, filePath);

let dependencyTree;
try {
const contents = await readFile(filePath, {encoding: "utf-8"});
dependencyTree = parseYaml(contents, {
filename: filePath
});
resolveProjectPaths(cwd, dependencyTree);
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;
}
};

Expand Down
22 changes: 11 additions & 11 deletions test/lib/graph/graph.integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ test.serial("graphFromPackageDependencies with workspace object", async (t) => {
const res = await graphFromPackageDependencies({
cwd: "cwd",
rootConfiguration: "rootConfiguration",
rootConfigPath: "rootConfigPath",
rootConfigPath: "/rootConfigPath",
versionOverride: "versionOverride",
workspaceConfiguration: {
specVersion: "workspace/1.0",
Expand All @@ -68,7 +68,7 @@ test.serial("graphFromPackageDependencies with workspace object", async (t) => {
t.deepEqual(npmProviderConstructorStub.getCall(0).args[0], {
cwd: path.join(__dirname, "..", "..", "..", "cwd"),
rootConfiguration: "rootConfiguration",
rootConfigPath: "rootConfigPath",
rootConfigPath: "/rootConfigPath",
}, "Created NodePackageDependencies provider instance with correct parameters");

t.is(projectGraphBuilderStub.callCount, 1, "projectGraphBuilder got called once");
Expand Down Expand Up @@ -96,7 +96,7 @@ test.serial("graphFromPackageDependencies with workspace object and workspace na
const res = await graphFromPackageDependencies({
cwd: "cwd",
rootConfiguration: "rootConfiguration",
rootConfigPath: "rootConfigPath",
rootConfigPath: "/rootConfigPath",
versionOverride: "versionOverride",
workspaceName: "dolphin",
workspaceConfiguration: {
Expand All @@ -118,7 +118,7 @@ test.serial("graphFromPackageDependencies with workspace object and workspace na
t.deepEqual(npmProviderConstructorStub.getCall(0).args[0], {
cwd: path.join(__dirname, "..", "..", "..", "cwd"),
rootConfiguration: "rootConfiguration",
rootConfigPath: "rootConfigPath",
rootConfigPath: "/rootConfigPath",
}, "Created NodePackageDependencies provider instance with correct parameters");

t.is(projectGraphBuilderStub.callCount, 1, "projectGraphBuilder got called once");
Expand All @@ -142,7 +142,7 @@ test.serial("graphFromPackageDependencies with workspace object not matching wor
await t.throwsAsync(graphFromPackageDependencies({
cwd: "cwd",
rootConfiguration: "rootConfiguration",
rootConfigPath: "rootConfigPath",
rootConfigPath: "/rootConfigPath",
versionOverride: "versionOverride",
workspaceName: "other",
workspaceConfiguration: {
Expand Down Expand Up @@ -171,7 +171,7 @@ test.serial("graphFromPackageDependencies with workspace file", async (t) => {
const res = await graphFromPackageDependencies({
cwd: libraryHPath,
rootConfiguration: "rootConfiguration",
rootConfigPath: "rootConfigPath",
rootConfigPath: "/rootConfigPath",
versionOverride: "versionOverride",
workspaceName: "default",
});
Expand All @@ -182,7 +182,7 @@ test.serial("graphFromPackageDependencies with workspace file", async (t) => {
t.deepEqual(npmProviderConstructorStub.getCall(0).args[0], {
cwd: libraryHPath,
rootConfiguration: "rootConfiguration",
rootConfigPath: "rootConfigPath"
rootConfigPath: "/rootConfigPath"
}, "Created NodePackageDependencies provider instance with correct parameters");

t.is(projectGraphBuilderStub.callCount, 1, "projectGraphBuilder got called once");
Expand Down Expand Up @@ -210,7 +210,7 @@ test.serial("graphFromPackageDependencies with workspace file at custom path", a
const res = await graphFromPackageDependencies({
cwd: "cwd",
rootConfiguration: "rootConfiguration",
rootConfigPath: "rootConfigPath",
rootConfigPath: "/rootConfigPath",
versionOverride: "versionOverride",
workspaceName: "default",
workspaceConfigPath: path.join(libraryHPath, "ui5-workspace.yaml")
Expand All @@ -222,7 +222,7 @@ test.serial("graphFromPackageDependencies with workspace file at custom path", a
t.deepEqual(npmProviderConstructorStub.getCall(0).args[0], {
cwd: path.join(__dirname, "..", "..", "..", "cwd"),
rootConfiguration: "rootConfiguration",
rootConfigPath: "rootConfigPath"
rootConfigPath: "/rootConfigPath"
}, "Created NodePackageDependencies provider instance with correct parameters");

t.is(projectGraphBuilderStub.callCount, 1, "projectGraphBuilder got called once");
Expand Down Expand Up @@ -250,7 +250,7 @@ test.serial("graphFromPackageDependencies with inactive workspace file at custom
const res = await graphFromPackageDependencies({
cwd: "cwd",
rootConfiguration: "rootConfiguration",
rootConfigPath: "rootConfigPath",
rootConfigPath: "/rootConfigPath",
versionOverride: "versionOverride",
workspaceName: "default",
workspaceConfigPath: path.join(libraryHPath, "custom-ui5-workspace.yaml")
Expand All @@ -262,7 +262,7 @@ test.serial("graphFromPackageDependencies with inactive workspace file at custom
t.deepEqual(npmProviderConstructorStub.getCall(0).args[0], {
cwd: path.join(__dirname, "..", "..", "..", "cwd"),
rootConfiguration: "rootConfiguration",
rootConfigPath: "rootConfigPath"
rootConfigPath: "/rootConfigPath"
}, "Created NodePackageDependencies provider instance with correct parameters");

t.is(projectGraphBuilderStub.callCount, 1, "projectGraphBuilder got called once");
Expand Down
Loading

0 comments on commit ef3e569

Please sign in to comment.