diff --git a/internal/documentation/scripts/generateCliDoc.js b/internal/documentation/scripts/generateCliDoc.js
index e833dff57b7..f5e142bdbd8 100644
--- a/internal/documentation/scripts/generateCliDoc.js
+++ b/internal/documentation/scripts/generateCliDoc.js
@@ -60,6 +60,8 @@ function parseOutput(stdout) {
}
if (section.includes("Options:")) {
obj.addOptions = section.split("\n").filter(function(el) {
+ // Skip section headers (non-indented lines ending with ":", e.g. "Options:")
+ if (!el.startsWith(" ") && el.trim().endsWith(":")) return false;
const array = obj.commonOptions;
array.forEach(function(item, index, array) {
array[index] = item.replace(/\s+/g, "");
@@ -77,6 +79,121 @@ function parseOutput(stdout) {
}
}
+
+function parseAliases(details) {
+ if (!details || !details.startsWith("[aliases:")) return [];
+ return details.slice("[aliases:".length, -1).trim().split(",").map((s) => s.trim()).filter(Boolean);
+}
+
+// Calls --help for commandPath and builds a full command entry from the current obj state.
+/**
+ * @param {string} commandPath
+ * @param {string[]} aliases
+ */
+function buildCommandEntry(commandPath, aliases = []) {
+ execute(commandPath + " --help");
+
+ const commandsObj = [];
+ obj.commands.shift();
+ // `< 1` (not `<= 1`) so commands with exactly one subcommand are included too
+ if (!(obj.commands.length < 1)) {
+ for (const all of obj.commands) {
+ const temp = checkChars(all);
+ const {command, description} = splitString(temp);
+ commandsObj.push({childCommand: command, commandDescription: description});
+ }
+ }
+
+ const positionalObj = [];
+ obj.positionals.shift();
+ if (!(obj.positionals.length < 1)) {
+ let index = 0;
+ for (const all of obj.positionals) {
+ const temp = checkChars(all);
+ const {command, description, details} = splitString(temp);
+ if (!(/\S/.test(command))) {
+ if (index > 0) {
+ positionalObj[index - 1].positionalDescription =
+ positionalObj[index - 1].positionalDescription.concat("
", description);
+ positionalObj[index - 1].positionalDetails = details;
+ }
+ continue;
+ }
+ positionalObj.push({
+ positional: command,
+ positionalDescription: description,
+ positionalDetails: details
+ });
+ index++;
+ }
+ }
+
+ const optionObj = [];
+ if (!(obj.addOptions.length < 1)) {
+ for (const all of obj.addOptions) {
+ const temp = checkChars(all);
+ // yargs appends a trailing empty line to some sections; skip it to avoid ghost table rows
+ if (temp == "") {
+ continue;
+ }
+ const {command, description, details} = splitString(temp);
+ optionObj.push({option: command, optionDescription: description, optionDetails: details});
+ }
+ }
+
+ const exampleObj = [];
+ obj.examples.shift();
+ if (!(obj.examples.length <= 1)) {
+ for (const all of obj.examples) {
+ const temp = checkChars(all);
+ if (temp == "") {
+ continue;
+ }
+ const {command, description} = splitString(temp);
+ exampleObj.push({example: command, exampleDescription: description});
+ }
+ }
+
+ return {
+ command: commandPath,
+ description: obj.desc,
+ usage: obj.usage,
+ aliases,
+ childCommands: commandsObj,
+ positionals: positionalObj,
+ options: optionObj,
+ examples: exampleObj
+ };
+}
+
+/**
+ * Iterates commandsArray by index so newly appended subcommand entries are
+ * automatically picked up on subsequent iterations — depth-first without explicit recursion.
+ *
+ * @param {Record[]} commandsArray
+ */
+function discoverSubCommands(commandsArray) {
+ const seen = new Set(commandsArray.map((e) => e.command));
+
+ for (let i = 0; i < commandsArray.length; i++) {
+ for (const child of commandsArray[i].childCommands || []) {
+ const subPath = child.childCommand && child.childCommand.trim();
+ if (!subPath || !/\S/.test(subPath)) {
+ continue;
+ }
+ // A command with positionals ( or [arg]) is a leaf — skip recursion.
+ if (subPath.includes("<") || subPath.includes("[")) {
+ continue;
+ }
+ if (seen.has(subPath)) {
+ continue;
+ }
+ seen.add(subPath);
+ commandsArray.push(buildCommandEntry(subPath));
+ }
+ }
+}
+
function generateDoc() {
execute("ui5 --help");
@@ -104,74 +221,16 @@ function generateDoc() {
const commands = obj.commands;
for (const all of commands) {
const command = all.trim().split(" ").slice(0, 2).join(" ");
- execute(command + " --help");
-
- const commandsObj = [];
- obj.commands.shift();
- if (!(obj.commands.length <= 1)) {
- for (const all of obj.commands) {
- const temp = checkChars(all);
- const {command, description} = splitString(temp);
- commandsObj.push({childCommand: command, commandDescription: description});
- }
- }
-
- const positionalObj = [];
- obj.positionals.shift();
-
- if (!(obj.positionals.length < 1)) {
- let index = 0;
- for (const all of obj.positionals) {
- const temp = checkChars(all);
- const {command, description, details} = splitString(temp);
- if (!(/\S/.test(command))) {
- positionalObj[index - 1].positionalDescription =
- positionalObj[index - 1].positionalDescription.concat("
", description);
- positionalObj[index - 1].positionalDetails =details;
- continue;
- }
- positionalObj.push({
- positional: command,
- positionalDescription: description,
- positionalDetails: details
- });
- index++;
- }
- }
- const optionObj = [];
- obj.addOptions.shift();
- if (!(obj.addOptions.length <= 1)) {
- for (const all of obj.addOptions) {
- const temp = checkChars(all);
- const {command, description, details} = splitString(temp);
- optionObj.push({option: command, optionDescription: description, optionDetails: details});
- }
- }
-
- const exampleObj = [];
- obj.examples.shift();
- if (!(obj.examples.length <= 1)) {
- for (const all of obj.examples) {
- const temp = checkChars(all);
- if (temp == "") {
- continue;
- }
- const {command, description} = splitString(temp);
- exampleObj.push({example: command, exampleDescription: description});
- }
- }
-
- const commandObj = {
- command: command,
- description: obj.desc,
- usage: obj.usage,
- childCommands: commandsObj,
- positionals: positionalObj,
- options: optionObj,
- examples: exampleObj
- };
+ // Parse [aliases: ...] from the root commands-list line before execute() overwrites obj
+ const {details: lineDetails} = splitString(checkChars(all));
+ const commandObj = buildCommandEntry(command, parseAliases(lineDetails));
commandsArray.push(commandObj);
}
+
+ discoverSubCommands(commandsArray);
+ // Order subcommands right after their parent commands
+ commandsArray.sort((a, b) => a.command.localeCompare(b.command));
+
let content = template({
common: obj.common.split("Usage:").join(""),
commonOptions: optionObj,
diff --git a/internal/documentation/scripts/resources/CLI.template.md b/internal/documentation/scripts/resources/CLI.template.md
index 19a4f06ff9c..2ed7fdf378c 100644
--- a/internal/documentation/scripts/resources/CLI.template.md
+++ b/internal/documentation/scripts/resources/CLI.template.md
@@ -50,6 +50,10 @@ These options you can use with each command.
{{description}}
+{{#if aliases}}
+**Aliases**: {{#each aliases}}`ui5 {{this}}`{{#unless @last}}, {{/unless}}{{/each}}
+
+{{/if}}
**Usage**
`