import fsPath from "node:path";
import Project from "../Project.js";
import * as resourceFactory from "@ui5/fs/resourceFactory";
/**
* Module
*
* @public
* @class
* @alias @ui5/project/specifications/types/Module
* @extends @ui5/project/specifications/Project
* @hideconstructor
*/
class Module extends Project {
constructor(parameters) {
super(parameters);
this._paths = null;
this._writer = null;
}
/* === Attributes === */
/**
* Since Modules have multiple source paths, this function always throws with an exception
*
* @public
* @throws {Error} Projects of type module have more than one source path
*/
getSourcePath() {
throw new Error(`Projects of type module have more than one source path`);
}
/* === Resource Access === */
/**
* Get a [ReaderCollection]{@link @ui5/fs/ReaderCollection} for accessing all resources of the
* project in the specified "style":
*
* <ul>
* <li><b>buildtime:</b> Resource paths are always prefixed with <code>/resources/</code>
* or <code>/test-resources/</code> followed by the project's namespace.
* Any configured build-excludes are applied</li>
* <li><b>dist:</b> Resource paths always match with what the UI5 runtime expects.
* This means that paths generally depend on the project type. Applications for example use a "flat"-like
* structure, while libraries use a "buildtime"-like structure.
* Any configured build-excludes are applied</li>
* <li><b>runtime:</b> Resource paths always match with what the UI5 runtime expects.
* This means that paths generally depend on the project type. Applications for example use a "flat"-like
* structure, while libraries use a "buildtime"-like structure.
* This style is typically used for serving resources directly. Therefore, build-excludes are not applied
* <li><b>flat:</b> Resource paths are never prefixed and namespaces are omitted if possible. Note that
* project types like "theme-library", which can have multiple namespaces, can't omit them.
* Any configured build-excludes are applied</li>
* </ul>
*
* If project resources have been changed through the means of a workspace, those changes
* are reflected in the provided reader too.
*
* Resource readers always use POSIX-style paths.
*
* @public
* @param {object} [options]
* @param {string} [options.style=buildtime] Path style to access resources.
* Can be "buildtime", "dist", "runtime" or "flat"
* @returns {@ui5/fs/ReaderCollection} A reader collection instance
*/
getReader({style = "buildtime"} = {}) {
// Apply builder excludes to all styles but "runtime"
const excludes = style === "runtime" ? [] : this.getBuilderResourcesExcludes();
const readers = this._paths.map(({name, virBasePath, fsBasePath}) => {
return resourceFactory.createReader({
name,
virBasePath,
fsBasePath,
project: this,
excludes
});
});
if (readers.length === 1) {
return readers[0];
}
const readerCollection = resourceFactory.createReaderCollection({
name: `Reader collection for module project ${this.getName()}`,
readers
});
return resourceFactory.createReaderCollectionPrioritized({
name: `Reader/Writer collection for project ${this.getName()}`,
readers: [this._getWriter(), readerCollection]
});
}
/**
* Get a resource reader/writer for accessing and modifying a project's resources
*
* @public
* @returns {@ui5/fs/ReaderCollection} A reader collection instance
*/
getWorkspace() {
const reader = this.getReader();
const writer = this._getWriter();
return resourceFactory.createWorkspace({
reader,
writer
});
}
_getWriter() {
if (!this._writer) {
this._writer = resourceFactory.createAdapter({
virBasePath: "/"
});
}
return this._writer;
}
/* === Internals === */
/**
* @private
* @param {object} config Configuration object
*/
async _configureAndValidatePaths(config) {
await super._configureAndValidatePaths(config);
this._log.verbose(`Path mapping for module project ${this.getName()}:`);
this._log.verbose(` Physical root path: ${this.getRootPath()}`);
this._log.verbose(` Mapped to:`);
if (config.resources?.configuration?.paths) {
const pathMappings = Object.entries(config.resources.configuration.paths);
if (this._log.isLevelEnabled("verbose")) {
// Log synchronously before async dir-exists checks
pathMappings.forEach(([virBasePath, relFsPath]) => {
this._log.verbose(` ${virBasePath} => ${relFsPath}`);
});
}
this._paths = await Promise.all(pathMappings.map(async ([virBasePath, relFsPath]) => {
if (!(await this._dirExists("/" + relFsPath))) {
throw new Error(
`Unable to find source directory '${relFsPath}' in module project ${this.getName()}`);
}
return {
name: `'${relFsPath}'' reader for module project ${this.getName()}`,
virBasePath,
fsBasePath: fsPath.join(this.getRootPath(), relFsPath)
};
}));
} else {
this._log.verbose(` / => <project root>`);
if (!(await this._dirExists("/"))) {
throw new Error(
`Unable to find root directory of module project ${this.getName()}`);
}
this._paths = [{
name: `Root reader for module project ${this.getName()}`,
virBasePath: "/",
fsBasePath: this.getRootPath()
}];
}
}
}
export default Module;