Skip to content

Commit 6e25974

Browse files
authored
Adding documentation generation command (#89)
1 parent 5c9d466 commit 6e25974

8 files changed

Lines changed: 213 additions & 7 deletions

File tree

.vscode/launch.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@
1818
"<itwin-id>"
1919
]
2020
},
21+
{
22+
"type": "node",
23+
"request": "launch",
24+
"name": "Launch docs generator",
25+
"skipFiles": [
26+
"<node_internals>/**"
27+
],
28+
"program": "${workspaceFolder}\\bin\\run",
29+
"args": [
30+
"docs-generator"
31+
]
32+
},
2133
{
2234
"name": "Debug All Tests",
2335
"skipFiles": [

integration-tests/formating/formatting.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ describe('Command formatting tests', async () => {
1111
userPlugins: false,
1212
});
1313

14-
allCommands = config.commands.filter(command => !command.id.startsWith("plugins") && !command.id.startsWith("help"))
14+
allCommands = config.commands.filter(command => !command.id.startsWith("plugins") && !command.id.startsWith("help") && !command.hidden)
1515
.map((command) =>
1616
({
1717
cmd: command,

package.json

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,87 @@
7171
},
7272
"topicSeparator": " ",
7373
"topics": {
74-
"hello": {
75-
"description": "Say hello to the world and others"
74+
"auth": {
75+
"description": "Authenticate itp with Bentley."
76+
},
77+
"itwin": {
78+
"description": "Work with iTwins."
79+
},
80+
"itwin repository": {
81+
"description": "Work with iTwin repositories."
82+
},
83+
"imodel": {
84+
"description": "Work with iModels of an iTwin."
85+
},
86+
"imodel view": {
87+
"description": "Work with views for an iModel."
88+
},
89+
"imodel changeset": {
90+
"description": "Work with changesets of an iModel."
91+
},
92+
"imodel namedversion": {
93+
"description": "Work with named versions of an iModel."
94+
},
95+
"imodel connection": {
96+
"description": "Work with iModel connections."
97+
},
98+
"imodel connection run": {
99+
"description": "Work with iModel connection runs."
100+
},
101+
"imodel connection sourcefile": {
102+
"description": "Work with iModel connection source files."
103+
},
104+
"access-control": {
105+
"description": "Manage iTwin access control."
106+
},
107+
"access-control permissions": {
108+
"description": "Get permission information."
109+
},
110+
"access-control role": {
111+
"description": "Manage iTwin access control roles."
112+
},
113+
"access-control group": {
114+
"description": "Manage iTwin access control groups."
115+
},
116+
"access-control member": {
117+
"description": "Manage members that can access an iTwin."
118+
},
119+
"access-control member owner": {
120+
"description": "Manage access control for iTwin owners."
121+
},
122+
"access-control member group": {
123+
"description": "Manage groups that can access an iTwin."
124+
},
125+
"access-control member user": {
126+
"description": "Manage users that can access an iTwin."
127+
},
128+
"storage": {
129+
"description": "Interact with the storage repository of an iTwin."
130+
},
131+
"storage folder": {
132+
"description": "Interact with folders inside a storage repository."
133+
},
134+
"storage file": {
135+
"description": "Interact with files inside a storage repository."
136+
},
137+
"user": {
138+
"description": "Access user information."
139+
},
140+
"changed-elements": {
141+
"description": "Compare two changesets of an iModel."
142+
},
143+
"workflows": {
144+
"description": "Workflow reference for combined commands."
145+
},
146+
"combined-commands": {
147+
"description": "Workflow reference for combined commands."
76148
}
77149
},
78150
"additionalHelpFlags": ["-h"]
79151
},
80152
"repository": "iTwin/itwin-cli",
81153
"scripts": {
82-
"clean": "shx rm -rf dist",
154+
"clean": "shx rm -rf dist tsconfig.tsbuildinfo",
83155
"build": "npm run clean && tsc -b",
84156
"lint": "eslint . --ext .ts",
85157
"postpack": "shx rm -f oclif.manifest.json",

pipelines/scripts/install-and-test-installer.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ if ($version -like "itp/* win32-x64 node-v20.16.0") {
1818
}
1919

2020
$help = & itp itwin --help
21-
if ($help -like "Create an iTwin*") {
21+
if ($help -like "Work with iTwins.*") {
2222
Write-Output "Help output check passed"
2323
} else {
2424
Write-Error "Unexpected help output: $help"

pipelines/test_installers.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
displayName: 'Check itp version'
5151
- script: |
5252
itwin_help=$(itp itwin --help)
53-
if [[ $itwin_help == "Create an iTwin"* ]]; then
53+
if [[ $itwin_help == "Work with iTwins."* ]]; then
5454
echo "Help output is correct: $itwin_help"
5555
else
5656
echo "Unexpected help output: $itwin_help"
@@ -85,7 +85,7 @@ jobs:
8585
displayName: 'Check itp version'
8686
- script: |
8787
itwin_help=$(itp itwin --help)
88-
if [[ $itwin_help == "Create an iTwin"* ]]; then
88+
if [[ $itwin_help == "Work with iTwins."* ]]; then
8989
echo "Help output is correct: $itwin_help"
9090
else
9191
echo "Unexpected help output: $itwin_help"

src/commands/access-control/group/create.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@
55

66
import { Flags } from "@oclif/core";
77

8+
import { apiReference } from "../../../extensions/api-reference.js";
89
import BaseCommand from "../../../extensions/base-command.js";
910

1011
export default class CreateAccessControlGroup extends BaseCommand {
12+
static apiReference : apiReference = {
13+
link: "https://developer.bentley.com/apis/access-control-v2/operations/create-itwin-group/",
14+
name: "Create iTwin Group",
15+
}
16+
1117
static description = 'Create a new group for an iTwin.';
1218

1319
static examples = [

src/commands/docs-generator.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { Command, Config, Flags } from "@oclif/core";
2+
import fs from "node:fs";
3+
import path from "node:path";
4+
5+
import BaseCommand from "../extensions/base-command.js";
6+
7+
export default class DocsGenerator extends BaseCommand {
8+
static description = "Generate command and overview markdown files for the CLI.";
9+
10+
static flags = {
11+
"output-dir": Flags.string({
12+
char: "o",
13+
description: "The output directory for the documentation files.",
14+
15+
}),
16+
}
17+
18+
static hidden = true;
19+
20+
generateCommandMarkdown(command: Command.Loadable, flags: [string, Command.Flag.Cached][]): string {
21+
const options = flags.length > 0
22+
?
23+
flags.filter(([_, flag]) => !flag.hidden && flag.helpGroup !== "GLOBAL")
24+
.sort(([_, flag]) => (flag.required ? -1 : 1))
25+
.map(([name, flag]) => {
26+
const required = flag.required ? "**Required:** Yes" : "**Required:** No";
27+
const typeValue = flag.type === "option" ? flag.helpValue ?? "" : "<flag>";
28+
const type = `**Type:** \`${typeValue}\``;
29+
const description = flag.description ?? "";
30+
const flagName = flag.char ? `-${flag.char}, --${name}` : `--${name}`;
31+
return `- **\`${flagName}\`** \n ${description} \n ${type} ${required}`;
32+
})
33+
.join("\n\n")
34+
: "";
35+
36+
37+
38+
let examplesText = "";
39+
40+
if (command.examples){
41+
for (const example of command.examples) {
42+
if (typeof example === "string") {
43+
examplesText += `\n${example}`;
44+
}
45+
46+
if(typeof example === "object") {
47+
examplesText += `\n# ${example.description}\n${example.command}\n`;
48+
}
49+
}
50+
}
51+
52+
const commandName = command.id.split(":").join(" ");
53+
54+
examplesText = examplesText.replaceAll("<%= config.bin %>", "itp").replaceAll("<%= command.id %>", commandName).trimEnd();
55+
56+
const apiReference = command.apiReference as string;
57+
const apiReferenceName = command.apiReferenceName as string;
58+
59+
return `# itp ${commandName}\n\n${command.description || ""}\n\n## Options\n\n${options}\n\n## Examples\n\n\`\`\`bash${examplesText}\n\`\`\`\n\n## API Reference\n\n[${apiReferenceName}](${apiReference})`;
60+
}
61+
62+
async generateDocs(config: Config, basePath: string) {
63+
const filteredCommands = config.commands.filter(c => !c.id.includes("help") && !c.id.includes("plugins") && !c.hidden);
64+
if(!filteredCommands) {
65+
return;
66+
}
67+
68+
for (const command of filteredCommands) {
69+
const markdown: string = this.generateCommandMarkdown(command, Object.entries(command.flags));
70+
71+
const commandDepth = command.id.split(":");
72+
let filePath = basePath;
73+
for (const depth of commandDepth) {
74+
filePath = `${filePath}/${depth}`;
75+
}
76+
77+
this.writeToFile(filePath, markdown);
78+
}
79+
}
80+
81+
async run() {
82+
const {flags} = await this.parse(DocsGenerator);
83+
84+
const config = await Config.load(
85+
{
86+
devPlugins: false,
87+
root: process.cwd(),
88+
userPlugins: false,
89+
}
90+
);
91+
92+
this.generateDocs(config, flags["output-dir"] ?? `${config.root}/docs`);
93+
}
94+
95+
writeToFile(filePath: string, markdown: string) {
96+
const finalPath = `${filePath}.md`;
97+
console.log(`Writing to directory: ${finalPath}`);
98+
console.log(markdown);
99+
100+
const dirName = path.dirname(finalPath);
101+
if (!fs.existsSync(dirName)) {
102+
fs.mkdirSync(dirName, {
103+
recursive: true,
104+
});
105+
}
106+
107+
fs.writeFileSync(finalPath, markdown);
108+
}
109+
}
110+
111+
112+

src/extensions/api-reference.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export type apiReference = {
2+
link: string;
3+
name: string;
4+
};

0 commit comments

Comments
 (0)