@waron97/prbot 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +5 -0
- package/CLAUDE.md +0 -0
- package/README.md +128 -0
- package/index.js +135 -79
- package/package.json +5 -3
- package/src/commands/autopr.js +309 -0
- package/src/commands/changelog.js +189 -0
- package/src/commands/init.js +144 -0
- package/src/commands/pr.js +124 -0
- package/src/commands/ver.js +83 -0
- package/src/config.js +7 -0
- package/src/index.js +106 -0
- package/src/lib/addons.js +8 -0
- package/src/lib/git.js +12 -0
package/CLAUDE.md
ADDED
|
File without changes
|
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# prbot
|
|
2
|
+
|
|
3
|
+
CLI tool for managing PRs, changelogs, and Odoo workflow XML files in the addons repo.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g .
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
prbot init
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Prompts for all required config values, writes them to `~/.config/prbot/config`, installs shell tab completion, and patches `~/.bashrc`. Run once, re-run anytime to update config.
|
|
18
|
+
|
|
19
|
+
After first run:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
source ~/.bashrc
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Config keys
|
|
26
|
+
|
|
27
|
+
| Key | Description |
|
|
28
|
+
|-----|-------------|
|
|
29
|
+
| `ADDONS_PATH` | Path to local Odoo addons repo |
|
|
30
|
+
| `KC_URL` | Keycloak token endpoint URL |
|
|
31
|
+
| `KC_USER` | Keycloak username |
|
|
32
|
+
| `KC_PASSWORD` | Keycloak password |
|
|
33
|
+
| `KC_ID` | Keycloak client ID |
|
|
34
|
+
| `KC_SECRET` | Keycloak client secret |
|
|
35
|
+
| `RIP_URL` | RIP API base URL |
|
|
36
|
+
| `TRIDENT_URL` | Trident (Odoo) instance URL |
|
|
37
|
+
| `TRIDENT_UID` | Trident user ID |
|
|
38
|
+
| `TRIDENT_TOKEN` | Trident API token |
|
|
39
|
+
| `TRIDENT_DB` | Trident database name |
|
|
40
|
+
| `DEVOPS_TOKEN` | Azure DevOps personal access token |
|
|
41
|
+
| `DEVOPS_ORG` | Azure DevOps organization |
|
|
42
|
+
| `DEVOPS_PROJECT` | Azure DevOps project |
|
|
43
|
+
| `DEVOPS_REPO` | Azure DevOps repository name |
|
|
44
|
+
| `AUTOPR_TARGET_BRANCH` | Target branch for auto-created PRs (default: `15.0-dev`) |
|
|
45
|
+
|
|
46
|
+
## Commands
|
|
47
|
+
|
|
48
|
+
### `prbot pr <module>`
|
|
49
|
+
|
|
50
|
+
Fetches workflow XML for `<module>` from RIP, writes files into `ADDONS_PATH/config/<module>/data/`, and commits.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
prbot pr config_wf_contestazione
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Options:
|
|
57
|
+
|
|
58
|
+
| Flag | Description |
|
|
59
|
+
|------|-------------|
|
|
60
|
+
| `-b, --bump <level>` | Also bump manifest version after commit. Level: `major`, `minor`, `patch` |
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
prbot pr config_wf_contestazione -b minor
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `prbot ver <module>`
|
|
67
|
+
|
|
68
|
+
Bumps the version in `__manifest__.py` for `<module>` and commits.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
prbot ver config_wf_contestazione --bump patch
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### `prbot changelog <pr>`
|
|
75
|
+
|
|
76
|
+
Writes a changelog entry into `CHANGELOG.md` for a given PR number. Prompts to select the target section, detects existing indentation, and appends the entry with refs.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
prbot changelog 42 -m "Fix invoice state race condition" -t 1234 -t 5678 -j TESTML-1 -j TESTML-2
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Options:
|
|
83
|
+
|
|
84
|
+
| Flag | Description |
|
|
85
|
+
|------|-------------|
|
|
86
|
+
| `-m, --message <text>` | Changelog entry message (prompted if omitted) |
|
|
87
|
+
| `-t, --trident <code>` | Trident issue code (repeatable) |
|
|
88
|
+
| `-j, --jira <code>` | JIRA issue code (repeatable) |
|
|
89
|
+
|
|
90
|
+
### `prbot autopr`
|
|
91
|
+
|
|
92
|
+
End-to-end PR automation: creates a branch, pushes it, opens a draft PR on Azure DevOps, appends the PR link to each Trident task's release checklist, writes a changelog entry, commits, and pushes.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Single Trident task
|
|
96
|
+
prbot autopr -t 1234
|
|
97
|
+
|
|
98
|
+
# Multiple Trident tasks (all get PR link; first with work package drives section matching)
|
|
99
|
+
prbot autopr -t 1234 -t 5678
|
|
100
|
+
|
|
101
|
+
# Multiple Trident + multiple JIRA
|
|
102
|
+
prbot autopr -t 1234 -t 5678 -j TESTML-1 -j TESTML-2
|
|
103
|
+
|
|
104
|
+
# JIRA only — skips all Trident fetch/write operations
|
|
105
|
+
prbot autopr -j JIRA-99 --branch my-branch
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Options:
|
|
109
|
+
|
|
110
|
+
| Flag | Description |
|
|
111
|
+
|------|-------------|
|
|
112
|
+
| `-t, --trident <id>` | Trident task ID (repeatable) |
|
|
113
|
+
| `-j, --jira <code>` | JIRA issue code (repeatable) |
|
|
114
|
+
| `-m, --message <text>` | Changelog entry message (prompted if omitted) |
|
|
115
|
+
| `-b, --branch <name>` | Branch name (default: `autopr_<first-task-id>` or `autopr_<first-jira>`) |
|
|
116
|
+
| `-n, --name <text>` | PR title (default: Trident task name) |
|
|
117
|
+
|
|
118
|
+
### `prbot init`
|
|
119
|
+
|
|
120
|
+
Interactive setup: writes `~/.config/prbot/config` and installs shell completion.
|
|
121
|
+
|
|
122
|
+
## Tab completion
|
|
123
|
+
|
|
124
|
+
After `prbot init` and sourcing `~/.bashrc`, `<module>` arguments autocomplete from directories in `ADDONS_PATH/config/`.
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
prbot pr config_wf_<TAB> # lists all workflow modules
|
|
128
|
+
```
|
package/index.js
CHANGED
|
@@ -3,12 +3,55 @@
|
|
|
3
3
|
import { configDotenv } from "dotenv";
|
|
4
4
|
import fetch from "node-fetch";
|
|
5
5
|
import fs from "fs/promises";
|
|
6
|
+
import {
|
|
7
|
+
readdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
appendFileSync,
|
|
10
|
+
existsSync,
|
|
11
|
+
mkdirSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from "fs";
|
|
6
14
|
import path from "path";
|
|
7
15
|
import { execFile } from "child_process";
|
|
8
16
|
import { program } from "commander";
|
|
17
|
+
import omelette from "omelette";
|
|
18
|
+
import inquirer from "inquirer";
|
|
19
|
+
|
|
20
|
+
const CONFIG_DIR = path.join(process.env.HOME || "", ".config", "prbot");
|
|
21
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config");
|
|
22
|
+
const COMPLETION_SCRIPT = path.join(CONFIG_DIR, "completion.sh");
|
|
23
|
+
|
|
24
|
+
const completion = omelette("prbot <command> <module>");
|
|
25
|
+
completion.on("command", ({ reply }) => {
|
|
26
|
+
reply(["pr", "ver", "init"]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
completion.on("module", ({ before, reply }) => {
|
|
30
|
+
if (before === "init") {
|
|
31
|
+
reply([]);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const raw = readFileSync(CONFIG_FILE, "utf-8");
|
|
36
|
+
const match = raw.match(/^ADDONS_PATH=(.+)$/m);
|
|
37
|
+
if (!match) {
|
|
38
|
+
reply([]);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const addonsPath = match[1].trim().replace(/^~/, process.env.HOME || "");
|
|
42
|
+
reply(readdirSync(path.join(addonsPath, "config")));
|
|
43
|
+
} catch {
|
|
44
|
+
reply([]);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
completion.init();
|
|
9
49
|
|
|
10
|
-
|
|
11
|
-
|
|
50
|
+
const isCompletionMode =
|
|
51
|
+
process.argv.includes("--compbash") || process.argv.includes("--compzsh");
|
|
52
|
+
|
|
53
|
+
if (!isCompletionMode) {
|
|
54
|
+
configDotenv({ path: CONFIG_FILE });
|
|
12
55
|
}
|
|
13
56
|
|
|
14
57
|
async function getToken() {
|
|
@@ -236,17 +279,13 @@ async function verbot(module_name, level) {
|
|
|
236
279
|
}
|
|
237
280
|
|
|
238
281
|
program
|
|
239
|
-
.command("pr")
|
|
240
|
-
.option("-m, --module <module>")
|
|
282
|
+
.command("pr <module>")
|
|
241
283
|
.option("-b, --bump <level>")
|
|
242
|
-
.action((opts) => {
|
|
243
|
-
|
|
244
|
-
throw new Error("No module specified");
|
|
245
|
-
}
|
|
246
|
-
main(opts.module)
|
|
284
|
+
.action((module, opts) => {
|
|
285
|
+
main(module)
|
|
247
286
|
.then(() => {
|
|
248
287
|
if (opts.bump) {
|
|
249
|
-
return verbot(
|
|
288
|
+
return verbot(module, opts.bump);
|
|
250
289
|
}
|
|
251
290
|
})
|
|
252
291
|
.catch((err) => {
|
|
@@ -255,83 +294,100 @@ program
|
|
|
255
294
|
});
|
|
256
295
|
|
|
257
296
|
program
|
|
258
|
-
.command("
|
|
259
|
-
.description("
|
|
260
|
-
.action((
|
|
261
|
-
if (
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
297
|
+
.command("init")
|
|
298
|
+
.description("Create config file and install shell completion")
|
|
299
|
+
.action(async () => {
|
|
300
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
301
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const existing = existsSync(CONFIG_FILE)
|
|
305
|
+
? Object.fromEntries(
|
|
306
|
+
readFileSync(CONFIG_FILE, "utf-8")
|
|
307
|
+
.split("\n")
|
|
308
|
+
.flatMap((line) => {
|
|
309
|
+
const m = line.match(/^([A-Z_]+)=(.*)$/);
|
|
310
|
+
return m ? [[m[1], m[2]]] : [];
|
|
311
|
+
}),
|
|
312
|
+
)
|
|
313
|
+
: {};
|
|
314
|
+
|
|
315
|
+
const answers = await inquirer.prompt([
|
|
316
|
+
{
|
|
317
|
+
type: "input",
|
|
318
|
+
name: "ADDONS_PATH",
|
|
319
|
+
message: "Addons path:",
|
|
320
|
+
default: existing.ADDONS_PATH ?? "~/codebase/sorgenia/addons",
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
type: "input",
|
|
324
|
+
name: "KC_URL",
|
|
325
|
+
message: "Keycloak URL:",
|
|
326
|
+
default: existing.KC_URL ?? "",
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
type: "input",
|
|
330
|
+
name: "KC_USER",
|
|
331
|
+
message: "Keycloak user:",
|
|
332
|
+
default: existing.KC_USER ?? "",
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
type: "password",
|
|
336
|
+
name: "KC_PASSWORD",
|
|
337
|
+
message: "Keycloak password:",
|
|
338
|
+
default: existing.KC_PASSWORD ?? "",
|
|
339
|
+
mask: "*",
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
type: "input",
|
|
343
|
+
name: "KC_ID",
|
|
344
|
+
message: "Keycloak client ID:",
|
|
345
|
+
default: existing.KC_ID ?? "",
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
type: "input",
|
|
349
|
+
name: "KC_SECRET",
|
|
350
|
+
message: "Keycloak client secret:",
|
|
351
|
+
default: existing.KC_SECRET ?? "",
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
type: "input",
|
|
355
|
+
name: "RIP_URL",
|
|
356
|
+
message: "RIP URL:",
|
|
357
|
+
default: existing.RIP_URL ?? "",
|
|
358
|
+
},
|
|
359
|
+
]);
|
|
360
|
+
|
|
361
|
+
writeFileSync(
|
|
362
|
+
CONFIG_FILE,
|
|
363
|
+
Object.entries(answers)
|
|
364
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
365
|
+
.join("\n") + "\n",
|
|
366
|
+
);
|
|
367
|
+
console.log(`Config written to ${CONFIG_FILE}`);
|
|
368
|
+
|
|
369
|
+
writeFileSync(COMPLETION_SCRIPT, completion.generateCompletionCode());
|
|
370
|
+
console.log(`Completion script written to ${COMPLETION_SCRIPT}`);
|
|
317
371
|
|
|
318
|
-
|
|
319
|
-
`
|
|
372
|
+
const rcFile = path.join(process.env.HOME || "", ".bashrc");
|
|
373
|
+
const sourceLine = `source ${COMPLETION_SCRIPT}`;
|
|
374
|
+
const rcContent = existsSync(rcFile) ? readFileSync(rcFile, "utf-8") : "";
|
|
375
|
+
if (!rcContent.includes(sourceLine)) {
|
|
376
|
+
appendFileSync(rcFile, `\n# prbot completion\n${sourceLine}\n`);
|
|
377
|
+
console.log(`Registered completion in ${rcFile} — run: source ~/.bashrc`);
|
|
320
378
|
} else {
|
|
321
|
-
console.
|
|
322
|
-
process.exit(1);
|
|
379
|
+
console.log("Completion already registered in ~/.bashrc");
|
|
323
380
|
}
|
|
324
381
|
});
|
|
325
382
|
|
|
326
383
|
program
|
|
327
|
-
.command("ver")
|
|
328
|
-
.option("-m, --module <module>")
|
|
384
|
+
.command("ver <module>")
|
|
329
385
|
.option("-b, --bump <level>")
|
|
330
|
-
.action((opts) => {
|
|
331
|
-
if (!opts.
|
|
332
|
-
throw new Error("No
|
|
386
|
+
.action((module, opts) => {
|
|
387
|
+
if (!opts.bump) {
|
|
388
|
+
throw new Error("No bump level specified");
|
|
333
389
|
}
|
|
334
|
-
verbot(
|
|
390
|
+
verbot(module, opts.bump);
|
|
335
391
|
});
|
|
336
392
|
|
|
337
393
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waron97/prbot",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
8
|
},
|
|
9
9
|
"bin": {
|
|
10
|
-
"prbot": "index.js"
|
|
10
|
+
"prbot": "src/index.js"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@
|
|
13
|
+
"@inquirer/search": "^2.0.1",
|
|
14
14
|
"commander": "^14.0.2",
|
|
15
15
|
"dotenv": "^17.2.3",
|
|
16
16
|
"eslint-plugin-es5": "^1.5.0",
|
|
17
|
+
"inquirer": "^13.4.2",
|
|
17
18
|
"node-fetch": "^3.3.2",
|
|
19
|
+
"omelette": "^0.4.17",
|
|
18
20
|
"prettier": "^3.5.3"
|
|
19
21
|
},
|
|
20
22
|
"devDependencies": {
|