@waron97/prbot 2.1.0 → 2.3.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/index.js CHANGED
@@ -1,393 +1,376 @@
1
1
  #!/usr/bin/env node
2
-
3
- import { configDotenv } from "dotenv";
4
- import fetch from "node-fetch";
5
- import fs from "fs/promises";
2
+ import { execFile } from 'child_process';
6
3
  import {
7
- readdirSync,
8
- readFileSync,
9
- appendFileSync,
10
- existsSync,
11
- mkdirSync,
12
- writeFileSync,
13
- } from "fs";
14
- import path from "path";
15
- import { execFile } from "child_process";
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"]);
4
+ appendFileSync,
5
+ existsSync,
6
+ mkdirSync,
7
+ readdirSync,
8
+ readFileSync,
9
+ writeFileSync,
10
+ } from 'fs';
11
+ import fs from 'fs/promises';
12
+ import path from 'path';
13
+ import { program } from 'commander';
14
+ import { configDotenv } from 'dotenv';
15
+ import inquirer from 'inquirer';
16
+ import fetch from 'node-fetch';
17
+ import omelette from 'omelette';
18
+
19
+ const CONFIG_DIR = path.join(process.env.HOME || '', '.config', 'prbot');
20
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config');
21
+ const COMPLETION_SCRIPT = path.join(CONFIG_DIR, 'completion.sh');
22
+
23
+ const completion = omelette('prbot <command> <module>');
24
+ completion.on('command', ({ reply }) => {
25
+ reply(['pr', 'ver', 'init']);
27
26
  });
28
27
 
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;
28
+ completion.on('module', ({ before, reply }) => {
29
+ if (before === 'init') {
30
+ reply([]);
31
+ return;
32
+ }
33
+ try {
34
+ const raw = readFileSync(CONFIG_FILE, 'utf-8');
35
+ const match = raw.match(/^ADDONS_PATH=(.+)$/m);
36
+ if (!match) {
37
+ reply([]);
38
+ return;
39
+ }
40
+ const addonsPath = match[1].trim().replace(/^~/, process.env.HOME || '');
41
+ reply(readdirSync(path.join(addonsPath, 'config')));
42
+ } catch {
43
+ reply([]);
40
44
  }
41
- const addonsPath = match[1].trim().replace(/^~/, process.env.HOME || "");
42
- reply(readdirSync(path.join(addonsPath, "config")));
43
- } catch {
44
- reply([]);
45
- }
46
45
  });
47
46
 
48
47
  completion.init();
49
48
 
50
- const isCompletionMode =
51
- process.argv.includes("--compbash") || process.argv.includes("--compzsh");
49
+ const isCompletionMode = process.argv.includes('--compbash') || process.argv.includes('--compzsh');
52
50
 
53
51
  if (!isCompletionMode) {
54
- configDotenv({ path: CONFIG_FILE });
52
+ configDotenv({ path: CONFIG_FILE });
55
53
  }
56
54
 
57
55
  async function getToken() {
58
- const url = process.env.KC_URL;
59
- const payload = new URLSearchParams();
60
-
61
- payload.append("username", process.env.KC_USER);
62
- payload.append("password", process.env.KC_PASSWORD);
63
- payload.append("client_id", process.env.KC_ID);
64
- payload.append("client_secret", process.env.KC_SECRET);
65
- payload.append("grant_type", "password");
66
-
67
- const response = await fetch(url, {
68
- method: "POST",
69
- headers: {
70
- "Content-Type": "application/x-www-form-urlencoded",
71
- },
72
- body: payload.toString(),
73
- });
74
-
75
- const json = await response.json();
76
- return json.access_token;
56
+ const url = process.env.KC_URL;
57
+ const payload = new URLSearchParams();
58
+
59
+ payload.append('username', process.env.KC_USER);
60
+ payload.append('password', process.env.KC_PASSWORD);
61
+ payload.append('client_id', process.env.KC_ID);
62
+ payload.append('client_secret', process.env.KC_SECRET);
63
+ payload.append('grant_type', 'password');
64
+
65
+ const response = await fetch(url, {
66
+ method: 'POST',
67
+ headers: {
68
+ 'Content-Type': 'application/x-www-form-urlencoded',
69
+ },
70
+ body: payload.toString(),
71
+ });
72
+
73
+ const json = await response.json();
74
+ return json.access_token;
77
75
  }
78
76
 
79
77
  async function getFiles(module_name, token) {
80
- const url = `${process.env.RIP_URL}/ir.model/xml_prbot`;
81
- const body = JSON.stringify({ module_name });
82
- const headers = {
83
- Authorization: `Bearer ${token}`,
84
- "Content-Type": "application/json",
85
- };
86
-
87
- const response = await fetch(url, { method: "POST", body, headers });
88
- if (!response.ok) {
89
- throw new Error(await response.text());
90
- }
91
- return await response.json();
78
+ const url = `${process.env.RIP_URL}/ir.model/xml_prbot`;
79
+ const body = JSON.stringify({ module_name });
80
+ const headers = {
81
+ Authorization: `Bearer ${token}`,
82
+ 'Content-Type': 'application/json',
83
+ };
84
+
85
+ const response = await fetch(url, { method: 'POST', body, headers });
86
+ if (!response.ok) {
87
+ throw new Error(await response.text());
88
+ }
89
+ return await response.json();
92
90
  }
93
91
 
94
92
  async function main(module_name) {
95
- const token = await getToken();
96
- const files = await getFiles(module_name, token);
97
-
98
- let ADDONS_PATH = process.env.ADDONS_PATH;
99
- if (ADDONS_PATH.startsWith("~")) {
100
- ADDONS_PATH = ADDONS_PATH.replace("~", process.env.HOME);
101
- }
102
-
103
- // Create out directory
104
-
105
- // Write files temporarily and process them
106
- for (const file of files) {
107
- const buffer = Buffer.from(file.data, "base64");
108
-
109
- // Read and process the file
110
- let content = buffer.toString();
111
-
112
- // Remove the last two lines
113
- const lines = content.split("\n");
114
-
115
- // Check for skipped records section appended after </odoo>
116
- const odooCloseIndex = lines.findIndex((l) => l.trim() === "</odoo>");
117
- if (odooCloseIndex !== -1 && odooCloseIndex < lines.length - 1) {
118
- const footer = lines.slice(odooCloseIndex + 1).join("\n");
119
- const skippedMatch = footer.match(/Skipped records:\s*(\d+)/);
120
- if (skippedMatch && parseInt(skippedMatch[1]) > 0) {
121
- throw new Error(
122
- `[${file.name}] Export contains skipped records:\n${footer.trim()}`,
123
- );
124
- }
125
- const duplicatedMatch = footer.match(/Duplicated records:\s*(\d+)/);
126
- if (duplicatedMatch && parseInt(duplicatedMatch[1]) > 0) {
127
- throw new Error(
128
- `[${file.name}] Export contains duplicated records:\n${footer.trim()}`,
129
- );
130
- }
131
- }
93
+ const token = await getToken();
94
+ const files = await getFiles(module_name, token);
132
95
 
133
- if (lines.length > 2) {
134
- lines.splice(-2);
96
+ let ADDONS_PATH = process.env.ADDONS_PATH;
97
+ if (ADDONS_PATH.startsWith('~')) {
98
+ ADDONS_PATH = ADDONS_PATH.replace('~', process.env.HOME);
135
99
  }
136
- content = lines.join("\n");
137
100
 
138
- // Remove the bpmn_diagram field pattern
139
- content = content.replace(
140
- /<field name="bpmn_diagram"><!\[CDATA\[[\s\S]*?\]\]><\/field>/g,
141
- "",
142
- );
101
+ // Create out directory
102
+
103
+ // Write files temporarily and process them
104
+ for (const file of files) {
105
+ const buffer = Buffer.from(file.data, 'base64');
106
+
107
+ // Read and process the file
108
+ let content = buffer.toString();
109
+
110
+ // Remove the last two lines
111
+ const lines = content.split('\n');
112
+
113
+ // Check for skipped records section appended after </odoo>
114
+ const odooCloseIndex = lines.findIndex((l) => l.trim() === '</odoo>');
115
+ if (odooCloseIndex !== -1 && odooCloseIndex < lines.length - 1) {
116
+ const footer = lines.slice(odooCloseIndex + 1).join('\n');
117
+ const skippedMatch = footer.match(/Skipped records:\s*(\d+)/);
118
+ if (skippedMatch && parseInt(skippedMatch[1]) > 0) {
119
+ throw new Error(
120
+ `[${file.name}] Export contains skipped records:\n${footer.trim()}`
121
+ );
122
+ }
123
+ const duplicatedMatch = footer.match(/Duplicated records:\s*(\d+)/);
124
+ if (duplicatedMatch && parseInt(duplicatedMatch[1]) > 0) {
125
+ throw new Error(
126
+ `[${file.name}] Export contains duplicated records:\n${footer.trim()}`
127
+ );
128
+ }
129
+ }
143
130
 
144
- // Determine the destination path based on filename
145
- let destPath;
146
- if (file.name.includes("Relazioni mancanti")) {
147
- destPath = `${ADDONS_PATH}/config/${module_name}/data/workflow_missing_relations.xml`;
148
- } else {
149
- destPath = `${ADDONS_PATH}/config/${module_name}/data/workflow_configuration.xml`;
150
- }
131
+ if (lines.length > 2) {
132
+ lines.splice(-2);
133
+ }
134
+ content = lines.join('\n');
135
+
136
+ // Remove the bpmn_diagram field pattern
137
+ content = content.replace(
138
+ /<field name="bpmn_diagram"><!\[CDATA\[[\s\S]*?\]\]><\/field>/g,
139
+ ''
140
+ );
151
141
 
152
- // Create destination directory if needed
153
- await fs.mkdir(path.dirname(destPath), { recursive: true });
142
+ // Determine the destination path based on filename
143
+ let destPath;
144
+ if (file.name.includes('Relazioni mancanti')) {
145
+ destPath = `${ADDONS_PATH}/config/${module_name}/data/workflow_missing_relations.xml`;
146
+ } else {
147
+ destPath = `${ADDONS_PATH}/config/${module_name}/data/workflow_configuration.xml`;
148
+ }
154
149
 
155
- // Write the processed file
156
- await fs.writeFile(destPath, content);
157
- console.log(`Processed: ${file.name} -> ${destPath}`);
158
- }
150
+ // Create destination directory if needed
151
+ await fs.mkdir(path.dirname(destPath), { recursive: true });
159
152
 
160
- // Git operations
161
- const workflowDir = path.join(ADDONS_PATH, "config", module_name, "data");
162
- const filesToAdd = [
163
- path.join(workflowDir, "workflow_missing_relations.xml"),
164
- path.join(workflowDir, "workflow_configuration.xml"),
165
- ];
153
+ // Write the processed file
154
+ await fs.writeFile(destPath, content);
155
+ console.log(`Processed: ${file.name} -> ${destPath}`);
156
+ }
157
+
158
+ // Git operations
159
+ const workflowDir = path.join(ADDONS_PATH, 'config', module_name, 'data');
160
+ const filesToAdd = [
161
+ path.join(workflowDir, 'workflow_missing_relations.xml'),
162
+ path.join(workflowDir, 'workflow_configuration.xml'),
163
+ ];
164
+
165
+ // Add files to git
166
+ for (const filePath of filesToAdd) {
167
+ await new Promise((resolve, reject) => {
168
+ execFile('git', ['add', filePath], { cwd: ADDONS_PATH }, (error) => {
169
+ if (error) reject(error);
170
+ else resolve();
171
+ });
172
+ });
173
+ }
166
174
 
167
- // Add files to git
168
- for (const filePath of filesToAdd) {
175
+ // Commit changes
176
+ const commitMessage = `[IMP][${module_name}] Update workflow`;
169
177
  await new Promise((resolve, reject) => {
170
- execFile("git", ["add", filePath], { cwd: ADDONS_PATH }, (error) => {
171
- if (error) reject(error);
172
- else resolve();
173
- });
178
+ execFile('git', ['commit', '-m', commitMessage], { cwd: ADDONS_PATH }, (error) => {
179
+ if (error) reject(error);
180
+ else resolve();
181
+ });
174
182
  });
175
- }
176
-
177
- // Commit changes
178
- const commitMessage = `[IMP][${module_name}] Update workflow`;
179
- await new Promise((resolve, reject) => {
180
- execFile(
181
- "git",
182
- ["commit", "-m", commitMessage],
183
- { cwd: ADDONS_PATH },
184
- (error) => {
185
- if (error) reject(error);
186
- else resolve();
187
- },
188
- );
189
- });
190
183
 
191
- console.log(`Committed with message: ${commitMessage}`);
184
+ console.log(`Committed with message: ${commitMessage}`);
192
185
  }
193
186
 
194
187
  async function verbot(module_name, level) {
195
- if (!["major", "minor", "patch"].includes(level)) {
196
- throw new Error("Level must be one of major, minor, patch");
197
- }
198
-
199
- let ADDONS_PATH = process.env.ADDONS_PATH;
200
- if (ADDONS_PATH.startsWith("~")) {
201
- ADDONS_PATH = ADDONS_PATH.replace("~", process.env.HOME);
202
- }
203
-
204
- // Try to find manifest file in either location
205
- let manifestPath = path.join(ADDONS_PATH, module_name, "__manifest__.py");
206
- try {
207
- await fs.access(manifestPath);
208
- } catch {
209
- manifestPath = path.join(
210
- ADDONS_PATH,
211
- "config",
212
- module_name,
213
- "__manifest__.py",
214
- );
188
+ if (!['major', 'minor', 'patch'].includes(level)) {
189
+ throw new Error('Level must be one of major, minor, patch');
190
+ }
191
+
192
+ let ADDONS_PATH = process.env.ADDONS_PATH;
193
+ if (ADDONS_PATH.startsWith('~')) {
194
+ ADDONS_PATH = ADDONS_PATH.replace('~', process.env.HOME);
195
+ }
196
+
197
+ // Try to find manifest file in either location
198
+ let manifestPath = path.join(ADDONS_PATH, module_name, '__manifest__.py');
215
199
  try {
216
- await fs.access(manifestPath);
200
+ await fs.access(manifestPath);
217
201
  } catch {
218
- throw new Error(`__manifest__.py not found for module ${module_name}`);
202
+ manifestPath = path.join(ADDONS_PATH, 'config', module_name, '__manifest__.py');
203
+ try {
204
+ await fs.access(manifestPath);
205
+ } catch {
206
+ throw new Error(`__manifest__.py not found for module ${module_name}`);
207
+ }
219
208
  }
220
- }
221
-
222
- // Read the manifest file
223
- const content = await fs.readFile(manifestPath, "utf-8");
224
-
225
- // Find and increment version
226
- const versionMatch = content.match(/"version":\s*"(15\.0\.\d+\.\d+\.\d+)"/);
227
- if (!versionMatch) {
228
- throw new Error("Version not found in manifest");
229
- }
230
-
231
- const currentVersion = versionMatch[1];
232
- const parts = currentVersion.split(".");
233
- const base = `${parts[0]}.${parts[1]}`;
234
- const major = parseInt(parts[2]);
235
- const minor = parseInt(parts[3]);
236
- const patch = parseInt(parts[4]);
237
-
238
- let newVersion;
239
- if (level === "patch") {
240
- newVersion = `${base}.${major}.${minor}.${patch + 1}`;
241
- } else if (level === "minor") {
242
- newVersion = `${base}.${major}.${minor + 1}.0`;
243
- } else if (level === "major") {
244
- newVersion = `${base}.${major + 1}.0.0`;
245
- }
246
-
247
- // Replace only the version line
248
- const newContent = content.replace(
249
- `"version": "${currentVersion}"`,
250
- `"version": "${newVersion}"`,
251
- );
252
-
253
- // Write back the manifest
254
- await fs.writeFile(manifestPath, newContent);
255
- console.log(`Updated version: ${currentVersion} -> ${newVersion}`);
256
-
257
- // Git add and commit
258
- await new Promise((resolve, reject) => {
259
- execFile("git", ["add", manifestPath], { cwd: ADDONS_PATH }, (error) => {
260
- if (error) reject(error);
261
- else resolve();
262
- });
263
- });
264
-
265
- const commitMessage = `[VER][${module_name}] Bump`;
266
- await new Promise((resolve, reject) => {
267
- execFile(
268
- "git",
269
- ["commit", "-m", commitMessage],
270
- { cwd: ADDONS_PATH },
271
- (error) => {
272
- if (error) reject(error);
273
- else resolve();
274
- },
209
+
210
+ // Read the manifest file
211
+ const content = await fs.readFile(manifestPath, 'utf-8');
212
+
213
+ // Find and increment version
214
+ const versionMatch = content.match(/"version":\s*"(15\.0\.\d+\.\d+\.\d+)"/);
215
+ if (!versionMatch) {
216
+ throw new Error('Version not found in manifest');
217
+ }
218
+
219
+ const currentVersion = versionMatch[1];
220
+ const parts = currentVersion.split('.');
221
+ const base = `${parts[0]}.${parts[1]}`;
222
+ const major = parseInt(parts[2]);
223
+ const minor = parseInt(parts[3]);
224
+ const patch = parseInt(parts[4]);
225
+
226
+ let newVersion;
227
+ if (level === 'patch') {
228
+ newVersion = `${base}.${major}.${minor}.${patch + 1}`;
229
+ } else if (level === 'minor') {
230
+ newVersion = `${base}.${major}.${minor + 1}.0`;
231
+ } else if (level === 'major') {
232
+ newVersion = `${base}.${major + 1}.0.0`;
233
+ }
234
+
235
+ // Replace only the version line
236
+ const newContent = content.replace(
237
+ `"version": "${currentVersion}"`,
238
+ `"version": "${newVersion}"`
275
239
  );
276
- });
277
240
 
278
- console.log(`Committed with message: ${commitMessage}`);
241
+ // Write back the manifest
242
+ await fs.writeFile(manifestPath, newContent);
243
+ console.log(`Updated version: ${currentVersion} -> ${newVersion}`);
244
+
245
+ // Git add and commit
246
+ await new Promise((resolve, reject) => {
247
+ execFile('git', ['add', manifestPath], { cwd: ADDONS_PATH }, (error) => {
248
+ if (error) reject(error);
249
+ else resolve();
250
+ });
251
+ });
252
+
253
+ const commitMessage = `[VER][${module_name}] Bump`;
254
+ await new Promise((resolve, reject) => {
255
+ execFile('git', ['commit', '-m', commitMessage], { cwd: ADDONS_PATH }, (error) => {
256
+ if (error) reject(error);
257
+ else resolve();
258
+ });
259
+ });
260
+
261
+ console.log(`Committed with message: ${commitMessage}`);
279
262
  }
280
263
 
281
264
  program
282
- .command("pr <module>")
283
- .option("-b, --bump <level>")
284
- .action((module, opts) => {
285
- main(module)
286
- .then(() => {
287
- if (opts.bump) {
288
- return verbot(module, opts.bump);
289
- }
290
- })
291
- .catch((err) => {
292
- throw err;
293
- });
294
- });
265
+ .command('pr <module>')
266
+ .option('-b, --bump <level>')
267
+ .action((module, opts) => {
268
+ main(module)
269
+ .then(() => {
270
+ if (opts.bump) {
271
+ return verbot(module, opts.bump);
272
+ }
273
+ })
274
+ .catch((err) => {
275
+ throw err;
276
+ });
277
+ });
295
278
 
296
279
  program
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
- }
280
+ .command('init')
281
+ .description('Create config file and install shell completion')
282
+ .action(async () => {
283
+ if (!existsSync(CONFIG_DIR)) {
284
+ mkdirSync(CONFIG_DIR, { recursive: true });
285
+ }
303
286
 
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}`);
371
-
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`);
378
- } else {
379
- console.log("Completion already registered in ~/.bashrc");
380
- }
381
- });
287
+ const existing = existsSync(CONFIG_FILE)
288
+ ? Object.fromEntries(
289
+ readFileSync(CONFIG_FILE, 'utf-8')
290
+ .split('\n')
291
+ .flatMap((line) => {
292
+ const m = line.match(/^([A-Z_]+)=(.*)$/);
293
+ return m ? [[m[1], m[2]]] : [];
294
+ })
295
+ )
296
+ : {};
297
+
298
+ const answers = await inquirer.prompt([
299
+ {
300
+ type: 'input',
301
+ name: 'ADDONS_PATH',
302
+ message: 'Addons path:',
303
+ default: existing.ADDONS_PATH ?? '~/codebase/sorgenia/addons',
304
+ },
305
+ {
306
+ type: 'input',
307
+ name: 'KC_URL',
308
+ message: 'Keycloak URL:',
309
+ default: existing.KC_URL ?? '',
310
+ },
311
+ {
312
+ type: 'input',
313
+ name: 'KC_USER',
314
+ message: 'Keycloak user:',
315
+ default: existing.KC_USER ?? '',
316
+ },
317
+ {
318
+ type: 'password',
319
+ name: 'KC_PASSWORD',
320
+ message: 'Keycloak password:',
321
+ default: existing.KC_PASSWORD ?? '',
322
+ mask: '*',
323
+ },
324
+ {
325
+ type: 'input',
326
+ name: 'KC_ID',
327
+ message: 'Keycloak client ID:',
328
+ default: existing.KC_ID ?? '',
329
+ },
330
+ {
331
+ type: 'input',
332
+ name: 'KC_SECRET',
333
+ message: 'Keycloak client secret:',
334
+ default: existing.KC_SECRET ?? '',
335
+ },
336
+ {
337
+ type: 'input',
338
+ name: 'RIP_URL',
339
+ message: 'RIP URL:',
340
+ default: existing.RIP_URL ?? '',
341
+ },
342
+ ]);
343
+
344
+ writeFileSync(
345
+ CONFIG_FILE,
346
+ Object.entries(answers)
347
+ .map(([k, v]) => `${k}=${v}`)
348
+ .join('\n') + '\n'
349
+ );
350
+ console.log(`Config written to ${CONFIG_FILE}`);
351
+
352
+ writeFileSync(COMPLETION_SCRIPT, completion.generateCompletionCode());
353
+ console.log(`Completion script written to ${COMPLETION_SCRIPT}`);
354
+
355
+ const rcFile = path.join(process.env.HOME || '', '.bashrc');
356
+ const sourceLine = `source ${COMPLETION_SCRIPT}`;
357
+ const rcContent = existsSync(rcFile) ? readFileSync(rcFile, 'utf-8') : '';
358
+ if (!rcContent.includes(sourceLine)) {
359
+ appendFileSync(rcFile, `\n# prbot completion\n${sourceLine}\n`);
360
+ console.log(`Registered completion in ${rcFile} — run: source ~/.bashrc`);
361
+ } else {
362
+ console.log('Completion already registered in ~/.bashrc');
363
+ }
364
+ });
382
365
 
383
366
  program
384
- .command("ver <module>")
385
- .option("-b, --bump <level>")
386
- .action((module, opts) => {
387
- if (!opts.bump) {
388
- throw new Error("No bump level specified");
389
- }
390
- verbot(module, opts.bump);
391
- });
367
+ .command('ver <module>')
368
+ .option('-b, --bump <level>')
369
+ .action((module, opts) => {
370
+ if (!opts.bump) {
371
+ throw new Error('No bump level specified');
372
+ }
373
+ verbot(module, opts.bump);
374
+ });
392
375
 
393
376
  program.parse();