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

[BREAKING] Require Project Graph #479

Merged
merged 5 commits into from
Jun 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
67 changes: 35 additions & 32 deletions lib/middleware/MiddlewareManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
* @memberof module:@ui5/server.middleware
*/
class MiddlewareManager {
constructor({tree, resources, options = {
constructor({graph, resources, options = {
sendSAPTargetCSP: false,
serveCSPReports: false
}}) {
if (!tree || !resources || !resources.all || !resources.rootProject || !resources.dependencies) {
if (!graph || !resources || !resources.all || !resources.rootProject || !resources.dependencies) {
throw new Error("[MiddlewareManager]: One or more mandatory parameters not provided");
}
this.tree = tree;
this.graph = graph;
this.resources = resources;
this.options = options;

Expand All @@ -44,15 +44,19 @@ class MiddlewareManager {
}

async addMiddleware(configuredMiddlewareName, {
wrapperCallback, mountPath = "/",
customMiddleware, wrapperCallback, mountPath = "/",
beforeMiddleware, afterMiddleware
} = {}) {
const middlewareInfo = middlewareRepository.getMiddleware(configuredMiddlewareName);
let middlewareCallback;
if (wrapperCallback) {
middlewareCallback = wrapperCallback(middlewareInfo);
if (customMiddleware) {
middlewareCallback = customMiddleware;
} else {
middlewareCallback = middlewareInfo.middleware;
const middlewareInfo = middlewareRepository.getMiddleware(configuredMiddlewareName);
if (wrapperCallback) {
middlewareCallback = wrapperCallback(middlewareInfo);
} else {
middlewareCallback = middlewareInfo.middleware;
}
}

let middlewareName = configuredMiddlewareName;
Expand Down Expand Up @@ -188,7 +192,7 @@ class MiddlewareManager {
return versionInfoModule({
resources,
middlewareUtil,
tree: this.tree
graph: this.graph
});
};
}
Expand All @@ -211,47 +215,46 @@ class MiddlewareManager {
}

async addCustomMiddleware() {
const project = this.tree;
const projectCustomMiddleware = project.server && project.server.customMiddleware;
if (!projectCustomMiddleware || projectCustomMiddleware.length === 0) {
const project = this.graph.getRoot();
const projectCustomMiddleware = project.getCustomMiddleware();
if (!projectCustomMiddleware.length === 0) {
return; // No custom middleware defined
}

for (let i = 0; i < projectCustomMiddleware.length; i++) {
const middlewareDef = projectCustomMiddleware[i];
if (!middlewareDef.name) {
throw new Error(`Missing name for custom middleware definition of project ${project.metadata.name} ` +
throw new Error(`Missing name for custom middleware definition of project ${project.getName()} ` +
`at index ${i}`);
}
if (middlewareDef.beforeMiddleware && middlewareDef.afterMiddleware) {
throw new Error(
`Custom middleware definition ${middlewareDef.name} of project ${project.metadata.name} ` +
`Custom middleware definition ${middlewareDef.name} of project ${project.getName()} ` +
`defines both "beforeMiddleware" and "afterMiddleware" parameters. Only one must be defined.`);
}
if (!middlewareDef.beforeMiddleware && !middlewareDef.afterMiddleware) {
throw new Error(
`Custom middleware definition ${middlewareDef.name} of project ${project.metadata.name} ` +
`Custom middleware definition ${middlewareDef.name} of project ${project.getName()} ` +
`defines neither a "beforeMiddleware" nor an "afterMiddleware" parameter. One must be defined.`);
}

await this.addMiddleware(middlewareDef.name, {
wrapperCallback: ({middleware: middleware, specVersion}) => {
return ({resources, middlewareUtil}) => {
const options = {
configuration: middlewareDef.configuration
};
const params = {resources, options};
if (
specVersion === "2.0" || specVersion === "2.1" ||
specVersion === "2.2" || specVersion === "2.3" ||
specVersion === "2.4" || specVersion === "2.5" ||
specVersion === "2.6"
) {
// Supply interface to MiddlewareUtil instance starting with specVersion 2.0
params.middlewareUtil = middlewareUtil.getInterface(specVersion);
}
return middleware(params);
customMiddleware: ({resources, middlewareUtil}) => {
const customMiddleware = this.graph.getExtension(middlewareDef.name);
const specVersion = customMiddleware.getSpecVersion();
const options = {
configuration: middlewareDef.configuration
};
const params = {resources, options};
if (
specVersion === "2.0" || specVersion === "2.1" ||
specVersion === "2.2" || specVersion === "2.3" ||
specVersion === "2.4" || specVersion === "2.5" ||
specVersion === "2.6"
) {
// Supply interface to MiddlewareUtil instance starting with specVersion 2.0
params.middlewareUtil = middlewareUtil.getInterface(specVersion);
}
return customMiddleware.getMiddleware()(params);
},
mountPath: middlewareDef.mountPath,
beforeMiddleware: middlewareDef.beforeMiddleware,
Expand Down
16 changes: 2 additions & 14 deletions lib/middleware/middlewareRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,14 @@ function getMiddleware(middlewareName) {
try {
const middleware = require(middlewareInfo.path);
return {
middleware,
specVersion: middlewareInfo.specVersion
middleware
};
} catch (err) {
throw new Error(
`middlewareRepository: Failed to require middleware module for ${middlewareName}: ${err.message}`);
}
}

function addMiddleware({name, specVersion, middlewarePath}) {
if (middlewareInfos[name]) {
throw new Error(`middlewareRepository: A middleware with the name ${name} has already been registered`);
}
middlewareInfos[name] = {
path: middlewarePath,
specVersion
};
}

module.exports = {
getMiddleware: getMiddleware,
addMiddleware: addMiddleware
getMiddleware: getMiddleware
};
5 changes: 2 additions & 3 deletions lib/middleware/serveIndex.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,8 @@ function createResourceInfo(resource) {
lastModified: new Date(stat.mtime).toLocaleString(),
size: formatSize(stat.size),
sizeInBytes: stat.size,
// TODO: project as public API of FS?
project: resource._project ? resource._project.id : "<unknown>",
projectPath: resource._project ? resource._project.path : "<unknown>"
project: resource.getProject()?.getName() || "<unknown>",
projectPath: resource.getProject()?.getPath() || "<unknown>"
};
}

Expand Down
11 changes: 5 additions & 6 deletions lib/middleware/serveResources.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,11 @@ function createMiddleware({resources, middlewareUtil}) {
if (rProperties.test(resourcePath)) {
// Special handling for *.properties files escape non ascii characters.
const nonAsciiEscaper = require("@ui5/builder").processors.nonAsciiEscaper;
const project = resource._project; // _project might not be defined
let propertiesFileSourceEncoding = project && project.resources &&
project.resources.configuration && project.resources.configuration.propertiesFileSourceEncoding;
const project = resource.getProject();
let propertiesFileSourceEncoding = project?.getPropertiesFileSourceEncoding();

if (!propertiesFileSourceEncoding) {
if (project && ["0.1", "1.0", "1.1"].includes(project.specVersion)) {
if (project && ["0.1", "1.0", "1.1"].includes(project.getSpecVersion())) {
// default encoding to "ISO-8859-1" for old specVersions
propertiesFileSourceEncoding = "ISO-8859-1";
} else {
Expand Down Expand Up @@ -84,9 +83,9 @@ function createMiddleware({resources, middlewareUtil}) {
// Also, only process .library, *.js and *.json files. Just like it's done in Application-
// and LibraryBuilder
if ((!charset || charset === "UTF-8") && rReplaceVersion.test(resourcePath)) {
if (resource._project) {
if (resource.getProject()) {
stream.setEncoding("utf8");
stream = stream.pipe(replaceStream("${version}", resource._project.version));
stream = stream.pipe(replaceStream("${version}", resource.getProject().getVersion()));
} else {
log.verbose("Project missing from resource %s", pathname);
}
Expand Down
21 changes: 12 additions & 9 deletions lib/middleware/versionInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@ const MANIFEST_JSON = "manifest.json";
* @module @ui5/server/middleware/versionInfo
* @param {object} parameters Parameters
* @param {module:@ui5/server.middleware.MiddlewareManager.middlewareResources} parameters.resources Parameters
* @param {object} parameters.tree Project tree
* @param {module:@ui5/project.graph.ProjectGraph} parameters.graph Project graph
* @returns {Function} Returns a server middleware closure.
*/
function createMiddleware({resources, tree: project}) {
function createMiddleware({resources, graph}) {
return async function versionInfo(req, res, next) {
try {
const dependencies = resources.dependencies;
const dotLibResources = await dependencies.byGlob("/resources/**/.library");

dotLibResources.sort((a, b) => {
return a._project.metadata.name.localeCompare(b._project.metadata.name);
return a.getProject().getName().localeCompare(b.getProject().getName());
});

const libraryInfosPromises = dotLibResources.map(async (dotLibResource) => {
const namespace = dotLibResource._project.metadata.namespace;
const namespace = dotLibResource.getProject().getNamespace();
const manifestResources = await dependencies.byGlob(`/resources/${namespace}/**/${MANIFEST_JSON}`);
let libraryManifest = manifestResources.find((manifestResource) => {
return manifestResource.getPath() === `/resources/${namespace}/${MANIFEST_JSON}`;
Expand All @@ -40,22 +40,25 @@ function createMiddleware({resources, tree: project}) {
resources: libResources,
options: {
omitMinVersions: true
},
getProjectVersion: (projectName) => {
return graph.getProject(projectName)?.getVersion();
}
});
}
return {
libraryManifest,
embeddedManifests,
name: dotLibResource._project.metadata.name,
version: dotLibResource._project.version
name: dotLibResource.getProject().getName(),
version: dotLibResource.getProject().getVersion()
};
});
const rootProject = graph.getRoot();
const libraryInfos = await Promise.all(libraryInfosPromises);

const [versionInfoResource] = await createVersionInfoProcessor({
options: {
rootProjectName: project.metadata.name,
rootProjectVersion: project.version,
rootProjectName: rootProject.getName(),
rootProjectVersion: rootProject.getVersion(),
libraryInfos
}
});
Expand Down
30 changes: 22 additions & 8 deletions lib/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ module.exports = {
* Start a server for the given project (sub-)tree.
*
* @public
* @param {object} tree A (sub-)tree
* @param {module:@ui5/project.graph.ProjectGraph} graph Project graph
* @param {object} options Options
* @param {number} options.port Port to listen to
* @param {boolean} [options.changePortIfInUse=false] If true, change the port if it is already in use
Expand All @@ -133,27 +133,41 @@ module.exports = {
* <code>h2</code>-flag and a <code>close</code> function,
* which can be used to stop the server.
*/
async serve(tree, {
async serve(graph, {
port: requestedPort, changePortIfInUse = false, h2 = false, key, cert,
acceptRemoteConnections = false, sendSAPTargetCSP = false, simpleIndex = false, serveCSPReports = false
}) {
const projectResourceCollections = resourceFactory.createCollectionsForTree(tree);
const rootProject = graph.getRoot();

const readers = [];
await graph.traverseBreadthFirst(async function({project: dep}) {
if (dep.getName() === rootProject.getName()) {
// Ignore root project
return;
}
readers.push(dep.getReader({style: "runtime"}));
});

const dependencies = resourceFactory.createReaderCollection({
name: `Dependency reader collection for project ${rootProject.getName()}`,
readers
});

const rootReader = rootProject.getReader({style: "runtime"});

// TODO change to ReaderCollection once duplicates are sorted out
const combo = new ReaderCollectionPrioritized({
name: "server - prioritize workspace over dependencies",
readers: [projectResourceCollections.source, projectResourceCollections.dependencies]
readers: [rootReader, dependencies]
});

const resources = {
rootProject: projectResourceCollections.source,
dependencies: projectResourceCollections.dependencies,
rootProject: rootReader,
dependencies: dependencies,
all: combo
};

const middlewareManager = new MiddlewareManager({
tree,
graph,
resources,
options: {
sendSAPTargetCSP,
Expand Down
9 changes: 0 additions & 9 deletions test/expected/build/application.a/dest-dev/index.html

This file was deleted.

5 changes: 0 additions & 5 deletions test/expected/build/application.a/dest-dev/test.js

This file was deleted.

9 changes: 0 additions & 9 deletions test/expected/build/application.a/dest/index.html

This file was deleted.

5 changes: 0 additions & 5 deletions test/expected/build/application.a/dest/test-dbg.js

This file was deleted.

1 change: 0 additions & 1 deletion test/expected/build/application.a/dest/test.js

This file was deleted.

This file was deleted.

This file was deleted.

13 changes: 0 additions & 13 deletions test/expected/build/application.b/dest/embedded/manifest.json

This file was deleted.

This file was deleted.

This file was deleted.

13 changes: 0 additions & 13 deletions test/expected/build/application.b/dest/manifest.json

This file was deleted.

11 changes: 0 additions & 11 deletions test/expected/build/library.d/dest/resources/library/d/.library

This file was deleted.

Loading