Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Add ui5Framework translator and resolvers #265

Merged
merged 49 commits into from
Mar 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
47bc6b2
[FEATURE] Add ui5Framework translator
matz3 Feb 14, 2020
1aad089
Add ui5Framework translator tests
matz3 Feb 17, 2020
6a5f0d4
[FIX] Move mock-require to devDependencies
matz3 Feb 17, 2020
62a9eec
Add more tests
matz3 Feb 17, 2020
dbefaf2
Enhance unit tests
matz3 Feb 18, 2020
a4b2364
Add ui5framework integration tests
matz3 Feb 18, 2020
d2b5311
Enhance integration tests
matz3 Feb 18, 2020
7b501c6
_level property seems to be correct
matz3 Feb 18, 2020
8d4d3d7
Add ui5framework OpenUI5 integration test
matz3 Feb 18, 2020
ad21937
Implement OpenUI5 framework resolution
matz3 Feb 19, 2020
55a51d1
Refactoring
matz3 Feb 20, 2020
ec9a568
Finish refactoring
matz3 Feb 20, 2020
9208a5b
Fix linting / remove duplicated test
matz3 Feb 20, 2020
dd36dd6
Upgrade pacote to v11, fix issues
matz3 Feb 20, 2020
0f5d09e
Using a hash sign in test names might cause issues with xunit
matz3 Feb 20, 2020
c6878ab
Remove prepare and lazy load SAPUI5 metadata
matz3 Feb 24, 2020
bfe9e4f
Make _getNpmPackageName static
RandomByte Feb 25, 2020
0a68834
Refactoring start...again
RandomByte Feb 25, 2020
78b9ba1
Move resolver tests
matz3 Feb 25, 2020
aba1cac
Openui5Resolver tests
matz3 Feb 25, 2020
bcae6a7
Sapui5Resolver test
matz3 Feb 25, 2020
d3c6656
Add Sapui5Resolver handleLibrary test
matz3 Feb 25, 2020
b90e1c9
Add AbstractResolver test
matz3 Feb 25, 2020
759f780
Continue refactoring
RandomByte Feb 25, 2020
50de91d
Map metadata in resolver, fix tests, move logging to translator
matz3 Feb 26, 2020
8f76af3
Fix tests
matz3 Feb 26, 2020
2d926e2
Fix error handling, add more tests, refactor
matz3 Mar 3, 2020
279b982
Refactor and extend integrationtests
matz3 Mar 3, 2020
8b66800
More tests, error handling, logging
matz3 Mar 3, 2020
06cb981
Finally fix error handling, add lock/unlock
matz3 Mar 4, 2020
a78078e
Fix log level
matz3 Mar 4, 2020
8c18eac
Refactor integrationtest
matz3 Mar 4, 2020
d3ea30a
Fix passing npmconfig to pacote
matz3 Mar 4, 2020
871c017
Handle development dependencies
matz3 Mar 5, 2020
6e77fed
Update _synchronize test
matz3 Mar 5, 2020
6d977de
Only pass log property when it is set
matz3 Mar 5, 2020
98136c4
Fix tests on windows
matz3 Mar 5, 2020
24b108b
Export Openui5/Sapui5Resolver / add jsdoc
matz3 Mar 5, 2020
8973a10
Further refactoring / error handling
matz3 Mar 6, 2020
3c3ccd1
Revert async function
matz3 Mar 6, 2020
78c8dbb
Don't use require for metadata.json
matz3 Mar 6, 2020
24c3fa9
Remove space
matz3 Mar 9, 2020
c866abf
Fix tests
matz3 Mar 9, 2020
5f98116
Adopt JSDoc
matz3 Mar 9, 2020
39974f0
Adjustments based on feedback
matz3 Mar 10, 2020
afcef42
Add more Installer tests
matz3 Mar 10, 2020
d0d75e6
WIP
matz3 Mar 10, 2020
74bd5ba
Add versionOverride option
matz3 Mar 10, 2020
fbf51b6
Adopt error messages
matz3 Mar 10, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
module.exports = {
normalizer: require("./lib/normalizer"),
projectPreprocessor: require("./lib/projectPreprocessor"),
/**
* @public
* @see module:@ui5/project.ui5Framework
* @namespace
*/
ui5Framework: {
Openui5Resolver: require("./lib/ui5Framework/Openui5Resolver"),
Sapui5Resolver: require("./lib/ui5Framework/Sapui5Resolver")
},
/**
* @private
* @see module:@ui5/project.translators
Expand Down
17 changes: 15 additions & 2 deletions lib/normalizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,28 @@ const Normalizer = {
* @param {string} [options.configPath] Path to configuration file
* @param {string} [options.translatorName] Translator to use
* @param {Object} [options.translatorOptions] Options to pass to translator
* @param {Object} [options.frameworkOptions] Options to pass to the framework installer
* @returns {Promise<Object>} Promise resolving to tree object
*/
generateProjectTree: async function(options = {}) {
const tree = await Normalizer.generateDependencyTree(options);
let tree = await Normalizer.generateDependencyTree(options);

if (options.configPath) {
tree.configPath = options.configPath;
}
return projectPreprocessor.processTree(tree);
tree = await projectPreprocessor.processTree(tree);

if (tree.framework) {
const ui5Framework = require("./translators/ui5Framework");
log.verbose(`Root project ${tree.metadata.name} defines framework ` +
`configuration. Installing UI5 dependencies...`);
let frameworkTree = await ui5Framework.generateDependencyTree(tree, options.frameworkOptions);
if (frameworkTree) {
frameworkTree = await projectPreprocessor.processTree(frameworkTree);
ui5Framework.mergeTrees(tree, frameworkTree);
}
}
return tree;
},

/**
Expand Down
2 changes: 1 addition & 1 deletion lib/translators/npm.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const path = require("path");
const readPkgUp = require("read-pkg-up");
const readPkg = require("read-pkg");
const {promisify} = require("util");
const fs = require("fs");
const fs = require("graceful-fs");
const realpath = promisify(fs.realpath);
const resolveModulePath = promisify(require("resolve"));
const parentNameRegExp = new RegExp(/:([^:]+):$/i);
Expand Down
221 changes: 221 additions & 0 deletions lib/translators/ui5Framework.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
const log = require("@ui5/logger").getLogger("normalizer:translators:ui5Framework");

class ProjectProcessor {
constructor({libraryMetadata}) {
this._libraryMetadata = libraryMetadata;
this._projectCache = {};
}
getProject(libName) {
log.verbose(`Creating project for library ${libName}...`);

if (this._projectCache[libName]) {
log.verbose(`Returning cached project for library ${libName}`);
return this._projectCache[libName];
}

if (!this._libraryMetadata[libName]) {
throw new Error(`Failed to find library ${libName} in dist packages metadata.json`);
}

const depMetadata = this._libraryMetadata[libName];

const dependencies = [];
dependencies.push(...depMetadata.dependencies.map((depName) => {
return this.getProject(depName);
}));

if (depMetadata.optionalDependencies) {
const resolvedOptionals = depMetadata.optionalDependencies.map((depName) => {
if (this._libraryMetadata[depName]) {
log.verbose(`Resolving optional dependency ${depName} for project ${libName}...`);
return this.getProject(depName);
}
}).filter(($)=>$);

dependencies.push(...resolvedOptionals);
}

this._projectCache[libName] = {
id: depMetadata.id,
version: depMetadata.version,
path: depMetadata.path,
dependencies
};
return this._projectCache[libName];
}
}

const utils = {
getAllNodesOfTree(tree) {
const nodes = {};
const queue = [...tree];
while (queue.length) {
const project = queue.shift();
if (!nodes[project.metadata.name]) {
nodes[project.metadata.name] = project;
queue.push(...project.dependencies);
}
}
return nodes;
},
isFrameworkProject(project) {
return project.id.startsWith("@openui5/") || project.id.startsWith("@sapui5/");
},
shouldIncludeDependency({optional, development}, root) {
// Root project should include all dependencies
// Otherwise only non-optional and non-development dependencies should be included
return root || (optional !== true && development !== true);
},
getFrameworkLibrariesFromTree(project, ui5Dependencies = [], root = true) {
if (utils.isFrameworkProject(project)) {
// Ignoring UI5 Framework libraries in dependencies
return ui5Dependencies;
}
if (project.framework && project.framework.libraries) {
project.framework.libraries.forEach((dependency) => {
if (!ui5Dependencies.includes(dependency.name) && utils.shouldIncludeDependency(dependency, root)) {
ui5Dependencies.push(dependency.name);
}
});
} else {
log.verbose(`Project ${project.metadata.name} defines no framework.libraries configuration`);
// Possible future enhancement: Fallback to detect OpenUI5 framework dependencies in package.json
}
project.dependencies.map((depProject) => {
utils.getFrameworkLibrariesFromTree(depProject, ui5Dependencies, false);
});
return ui5Dependencies;
},
ProjectProcessor
};

/**
*
*
* @private
* @namespace
* @alias module:@ui5/project.translators.ui5Framework
*/
module.exports = {
/**
*
*
* @public
* @param {Object} tree
* @param {Object} [options]
* @param {Object} [options.versionOverride] Framework version to use instead of the root projects framework
* version from the provided <code>tree</code>
* @returns {Promise<Object|null>} Promise
*/
generateDependencyTree: async function(tree, options = {}) {
// Don't create a tree when root project doesn't have a framework configuration
if (!tree.framework) {
return null;
}

const frameworkName = tree.framework.name;
if (frameworkName !== "SAPUI5" && frameworkName !== "OpenUI5") {
throw new Error(
`Unknown framework.name "${frameworkName}" for project ${tree.id}. Must be "OpenUI5" or "SAPUI5"`
);
}

let version;
if (!tree.framework.version) {
throw new Error(
`framework.version is not defined for project ${tree.id}`
);
} else if (options.versionOverride) {
log.info(
`Overriding configured ${frameworkName} version ` +
`${tree.framework.version} with supplied version ${options.versionOverride}`
);
version = options.versionOverride;
} else {
version = tree.framework.version;
}

log.info(`Using ${frameworkName} version: ${version}`);

const referencedLibraries = utils.getFrameworkLibrariesFromTree(tree);

let resolver;
if (frameworkName === "OpenUI5") {
const Openui5Resolver = require("../ui5Framework/Openui5Resolver");
resolver = new Openui5Resolver({cwd: tree.path, version});
} else if (frameworkName === "SAPUI5") {
const Sapui5Resolver = require("../ui5Framework/Sapui5Resolver");
resolver = new Sapui5Resolver({cwd: tree.path, version});
}

let startTime;
if (log.isLevelEnabled("verbose")) {
startTime = process.hrtime();
}

const {libraryMetadata} = await resolver.install(referencedLibraries);

if (log.isLevelEnabled("verbose")) {
const timeDiff = process.hrtime(startTime);
const prettyHrtime = require("pretty-hrtime");
log.verbose(`${frameworkName} dependencies ${referencedLibraries.join(", ")} resolved in ${prettyHrtime(timeDiff)}`);
}

const projectProcessor = new utils.ProjectProcessor({
libraryMetadata
});

const libraries = referencedLibraries.map((libName) => {
return projectProcessor.getProject(libName);
});

// Use root project (=requesting project) as root of framework tree
const frameworkTree = {
id: tree.id,
version: tree.version,
path: tree.path,
dependencies: libraries
};
return frameworkTree;
},

mergeTrees: function(projectTree, frameworkTree) {
const frameworkLibs = utils.getAllNodesOfTree(frameworkTree.dependencies);

log.verbose(`Merging framework tree into project tree "${projectTree.metadata.name}"`);

const queue = [projectTree];
while (queue.length) {
const project = queue.shift();

project.dependencies = project.dependencies.filter((depProject) => {
if (utils.isFrameworkProject(depProject)) {
log.verbose(
`A translator has already added the UI5 framework library ${depProject.metadata.name} ` +
`(id: ${depProject.id}) to the dependencies of project ${project.metadata.name}. ` +
`This dependency will be ignored.`);
log.info(`If project ${project.metadata.name} contains a package.json in which it defines a ` +
`dependency to the UI5 framework library ${depProject.id}, this dependency should be removed.`);
return false;
}
return true;
});
queue.push(...project.dependencies);

if (project.framework && project.framework.libraries) {
const frameworkDeps = project.framework.libraries.map((dependency) => {
if (!frameworkLibs[dependency.name]) {
throw new Error(`Missing framework library ${dependency.name} ` +
`required by project ${project.metadata.name}`);
}
return frameworkLibs[dependency.name];
});
project.dependencies.push(...frameworkDeps);
}
}
return projectTree;
},

// Export for testing only
_utils: process.env.NODE_ENV === "test" ? utils : undefined
};
Loading