diff --git a/lib/graph/Workspace.js b/lib/graph/Workspace.js index a3963182d..ad0c850f2 100644 --- a/lib/graph/Workspace.js +++ b/lib/graph/Workspace.js @@ -74,6 +74,20 @@ class Workspace { return this.#configuration.metadata.name; } + /** + * Returns an array of [Module]{@ui5/project/graph/Module} instances found in the configured + * dependency-management resolution paths of this workspace, sorted by module ID. + * + * @public + * @returns {Promise<@ui5/project/graph/Module[]>} + * Array of Module instances sorted by module ID + */ + async getModules() { + const {moduleIdMap} = await this._getResolvedModules(); + const sortedMap = new Map([...moduleIdMap].sort((a, b) => String(a[0]).localeCompare(b[0]))); + return Array.from(sortedMap.values()); + } + /** * For a given project name (e.g. the value of the metadata.name property in a ui5.yaml), * returns a [Module]{@ui5/project/graph/Module} instance or undefined depending on whether the project @@ -82,7 +96,7 @@ class Workspace { * @public * @param {string} projectName Name of the project * @returns {Promise<@ui5/project/graph/Module|undefined>} - * Module instance of undefined if none is found + * Module instance, or undefined if none is found */ async getModuleByProjectName(projectName) { const {projectNameMap} = await this._getResolvedModules(); @@ -98,7 +112,7 @@ class Workspace { * @public * @param {string} nodeId Node ID of the module * @returns {Promise<@ui5/project/graph/Module|undefined>} - * Module instance of undefined if none is found + * Module instance, or undefined if none is found */ async getModuleByNodeId(nodeId) { const {moduleIdMap} = await this._getResolvedModules(); diff --git a/lib/specifications/Project.js b/lib/specifications/Project.js index cdb236bae..2ce1fc8c4 100644 --- a/lib/specifications/Project.js +++ b/lib/specifications/Project.js @@ -61,7 +61,8 @@ class Project extends Specification { * @returns {boolean} True if the project is a framework project */ isFrameworkProject() { - return this.__id.startsWith("@openui5/") || this.__id.startsWith("@sapui5/"); + const id = this.getId(); + return id.startsWith("@openui5/") || id.startsWith("@sapui5/"); } /** diff --git a/lib/specifications/Specification.js b/lib/specifications/Specification.js index 361cf4066..5c2af4142 100644 --- a/lib/specifications/Specification.js +++ b/lib/specifications/Specification.js @@ -97,8 +97,10 @@ class Specification { this._version = version; this._modulePath = modulePath; - // The configured name (metadata.name) should be the unique identifier - // The ID property as supplied by the translators is only here for debugging and potential tracing purposes + // The ID property is filled from the provider (e.g. package.json "name") and might differ between providers. + // It is mainly used to detect framework libraries marked by @openui5 / @sapui5 scopes of npm package. + // (see Project#isFrameworkProject) + // In general, the configured name (metadata.name) should be used instead as the unique identifier of a project. this.__id = id; // Deep clone config to prevent changes by reference @@ -157,7 +159,23 @@ class Specification { /* === Attributes === */ /** - * Get the name of this specification + * Gets the ID of this specification. + * + *

Note: Only to be used for special occasions as it is specific to the provider that was used and does + * not necessarily represent something defined by the project.

+ * + * For general purposes of a unique identifier use + * {@link @ui5/project/specifications/Specification#getName getName} instead. + * + * @public + * @returns {string} Specification ID + */ + getId() { + return this.__id; + } + + /** + * Gets the name of this specification. Represents a unique identifier. * * @public * @returns {string} Specification name @@ -167,7 +185,7 @@ class Specification { } /** - * Get the kind of this specification, for example project or extension + * Gets the kind of this specification, for example project or extension * * @public * @returns {string} Specification kind @@ -177,7 +195,7 @@ class Specification { } /** - * Get the type of this specification, + * Gets the type of this specification, * for example application or library in case of projects, * and task or server-middleware in case of extensions * @@ -199,7 +217,7 @@ class Specification { } /** - * Get the specification's generic version, as typically defined in a package.json + * Gets the specification's generic version, as typically defined in a package.json * * @public * @returns {string} Project version @@ -209,7 +227,7 @@ class Specification { } /** - * Get the specification's file system path. This might not be POSIX-style on some platforms + * Gets the specification's file system path. This might not be POSIX-style on some platforms * * @public * @returns {string} Project root path @@ -220,7 +238,7 @@ class Specification { /* === Resource Access === */ /** - * Get a [ReaderCollection]{@link @ui5/fs/ReaderCollection} for the root directory of the specification. + * Gets a [ReaderCollection]{@link @ui5/fs/ReaderCollection} for the root directory of the specification. * Resource readers always use POSIX-style * * @public diff --git a/test/lib/graph/Workspace.js b/test/lib/graph/Workspace.js index af79ca3d4..8515b0a69 100644 --- a/test/lib/graph/Workspace.js +++ b/test/lib/graph/Workspace.js @@ -82,6 +82,9 @@ test("Basic resolution", async (t) => { t.is(await workspace.getModuleByNodeId("library.d"), libD, "getModuleByNodeId returns correct module for library.d"); + const modules = await workspace.getModules(); + t.deepEqual(modules, [libD, libE], "getModules returns modules sorted by module ID"); + t.deepEqual(Array.from(moduleIdMap.keys()).sort(), ["library.d", "library.e"], "Correct module ID keys"); moduleIdMap.forEach((value, key) => { t.is(value, projectNameMap.get(key), `Same instance of module ${key} in both maps`); diff --git a/test/lib/specifications/Specification.js b/test/lib/specifications/Specification.js index 23c7fe1d6..3d6621c0e 100644 --- a/test/lib/specifications/Specification.js +++ b/test/lib/specifications/Specification.js @@ -66,6 +66,7 @@ test("Specification can't be instantiated", (t) => { test("Instantiate a basic project", async (t) => { const project = await Specification.create(t.context.basicProjectInput); + t.is(project.getId(), "application.a.id", "Returned correct ID"); t.is(project.getName(), "application.a", "Returned correct name"); t.is(project.getVersion(), "1.0.0", "Returned correct version"); t.is(project.getRootPath(), applicationAPath, "Returned correct project path");