@waron97/prbot 1.1.0 → 1.1.1

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.
@@ -0,0 +1,5 @@
1
+ {
2
+ "permissions": {
3
+ "allow": ["Bash(node *)"]
4
+ }
5
+ }
package/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # prbot
2
+
3
+ CLI tool for fetching Odoo workflow XML files from the RIP API, writing them into the addons repo, and committing the result.
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
+
37
+ ## Commands
38
+
39
+ ### `prbot pr <module>`
40
+
41
+ Fetches workflow XML for `<module>` from RIP, writes files into `ADDONS_PATH/config/<module>/data/`, and commits.
42
+
43
+ ```bash
44
+ prbot pr config_wf_contestazione
45
+ ```
46
+
47
+ Options:
48
+
49
+ | Flag | Description |
50
+ |------|-------------|
51
+ | `-b, --bump <level>` | Also bump manifest version after commit. Level: `major`, `minor`, `patch` |
52
+
53
+ ```bash
54
+ prbot pr config_wf_contestazione -b minor
55
+ ```
56
+
57
+ ### `prbot ver <module>`
58
+
59
+ Bumps the version in `__manifest__.py` for `<module>` and commits.
60
+
61
+ ```bash
62
+ prbot ver config_wf_contestazione --bump patch
63
+ ```
64
+
65
+ ### `prbot init`
66
+
67
+ Interactive setup: writes `~/.config/prbot/config` and installs shell completion.
68
+
69
+ ## Tab completion
70
+
71
+ After `prbot init` and sourcing `~/.bashrc`, `<module>` arguments autocomplete from directories in `ADDONS_PATH/config/`.
72
+
73
+ ```
74
+ prbot pr config_wf_<TAB> # lists all workflow modules
75
+ ```
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
- if (process.argv[2] !== "completion") {
11
- configDotenv({ path: path.join(process.env.HOME, ".prbot") });
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
- if (!opts.module) {
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(opts.module, opts.bump);
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("completion [shell]")
259
- .description("Print shell completion script")
260
- .action((shell = "bash") => {
261
- if (shell === "bash") {
262
- process.stdout.write(`
263
- _prbot_completion() {
264
- local cur prev
265
- COMPREPLY=()
266
- cur="\${COMP_WORDS[COMP_CWORD]}"
267
- prev="\${COMP_WORDS[COMP_CWORD-1]}"
268
-
269
- if [[ "\$prev" == "-m" || "\$prev" == "--module" ]]; then
270
- local addons_path
271
- addons_path=\$(grep -E '^ADDONS_PATH=' ~/.prbot 2>/dev/null | head -1 | cut -d= -f2- | sed "s|~|\$HOME|g")
272
- if [[ -n "\$addons_path" && -d "\$addons_path/config" ]]; then
273
- local modules
274
- modules=\$(ls -1 "\$addons_path/config" 2>/dev/null)
275
- COMPREPLY=(\$(compgen -W "\$modules" -- "\$cur"))
276
- fi
277
- return 0
278
- fi
279
-
280
- COMPREPLY=(\$(compgen -W "pr ver completion" -- "\$cur"))
281
- }
282
- complete -F _prbot_completion prbot
283
- `);
284
- } else if (shell === "zsh") {
285
- process.stdout.write(`
286
- #compdef prbot
287
-
288
- _prbot() {
289
- local state
290
- _arguments \\
291
- '1: :->command' \\
292
- '*: :->args'
293
-
294
- case \$state in
295
- command)
296
- _values 'command' 'pr' 'ver' 'completion'
297
- ;;
298
- args)
299
- case \${words[2]} in
300
- pr|ver)
301
- case \${words[CURRENT-1]} in
302
- -m|--module)
303
- local addons_path
304
- addons_path=\$(grep -E '^ADDONS_PATH=' ~/.prbot 2>/dev/null | head -1 | cut -d= -f2- | sed "s|~|\$HOME|g")
305
- if [[ -n "\$addons_path" && -d "\$addons_path/config" ]]; then
306
- local -a modules
307
- modules=(\$(ls -1 "\$addons_path/config" 2>/dev/null))
308
- _describe 'module' modules
309
- fi
310
- ;;
311
- esac
312
- ;;
313
- esac
314
- ;;
315
- esac
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
- _prbot
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.error(`Unsupported shell: ${shell}. Use 'bash' or 'zsh'.`);
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.module || !opts.bump) {
332
- throw new Error("No module or level specified");
386
+ .action((module, opts) => {
387
+ if (!opts.bump) {
388
+ throw new Error("No bump level specified");
333
389
  }
334
- verbot(opts.module, opts.bump);
390
+ verbot(module, opts.bump);
335
391
  });
336
392
 
337
393
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waron97/prbot",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -14,7 +14,9 @@
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": {