Skip to content

Commit

Permalink
[BREAKING] Require Project Graph (#479)
Browse files Browse the repository at this point in the history
Adapt to new Project Graph API (see SAP/ui5-project#457)

BREAKING CHANGE:
* Server now requires a Project Graph instance instead.
* Standard middleware now rely on Project instances being available on Resources (see SAP/ui5-fs#381)
* MiddlewareRepository#addMiddleware has been removed. Custom middleware need to be added to the project graph instead
  • Loading branch information
RandomByte authored Jun 13, 2022
1 parent 251676a commit d62f85a
Show file tree
Hide file tree
Showing 259 changed files with 418 additions and 2,153 deletions.
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

0 comments on commit d62f85a

Please sign in to comment.