@waron97/prbot 2.6.1 → 2.8.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/README.md +24 -21
- package/package.json +1 -1
- package/src/commands/autopr.js +5 -0
- package/src/commands/export.js +2 -1
- package/src/commands/exportWorkflow.js +49 -0
- package/src/commands/init.js +3 -17
- package/src/commands/pr.js +8 -2
- package/src/config.js +1 -2
- package/src/index.js +11 -66
- package/.claude/settings.local.json +0 -11
- package/index.js +0 -376
package/README.md
CHANGED
|
@@ -14,13 +14,7 @@ npm install -g .
|
|
|
14
14
|
prbot init
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
Prompts for all required config values
|
|
18
|
-
|
|
19
|
-
After first run:
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
source ~/.bashrc
|
|
23
|
-
```
|
|
17
|
+
Prompts for all required config values and writes them to `~/.config/prbot/config`. Run once, re-run anytime to update config.
|
|
24
18
|
|
|
25
19
|
### Config keys
|
|
26
20
|
|
|
@@ -124,19 +118,20 @@ Interactive commit builder. Prompts for operation type (`[IMP]`, `[FIX]`, etc.),
|
|
|
124
118
|
prbot commit
|
|
125
119
|
```
|
|
126
120
|
|
|
127
|
-
### `prbot export workflow
|
|
121
|
+
### `prbot export workflow`
|
|
128
122
|
|
|
129
|
-
|
|
123
|
+
Fetches workflow XML for an interactively selected module from RIP and commits. Prompts to select the module (from `ADDONS_PATH/config/` directories) via fuzzy search.
|
|
130
124
|
|
|
131
125
|
```bash
|
|
132
|
-
prbot export workflow
|
|
126
|
+
prbot export workflow
|
|
127
|
+
prbot export workflow --no-commit
|
|
133
128
|
```
|
|
134
129
|
|
|
135
130
|
Options:
|
|
136
131
|
|
|
137
|
-
| Flag
|
|
138
|
-
|
|
|
139
|
-
|
|
|
132
|
+
| Flag | Description |
|
|
133
|
+
| ------------- | ------------------------ |
|
|
134
|
+
| `--no-commit` | Skip the git commit step |
|
|
140
135
|
|
|
141
136
|
### `prbot export pb`
|
|
142
137
|
|
|
@@ -168,9 +163,24 @@ Options:
|
|
|
168
163
|
| ------------- | ------------------------ |
|
|
169
164
|
| `--no-commit` | Skip the git commit step |
|
|
170
165
|
|
|
166
|
+
### `prbot export email-templates`
|
|
167
|
+
|
|
168
|
+
Fetches email templates for an interactively selected workflow from RIP and writes them as `ADDONS_PATH/config/<module>/data/mail_templates.xml`. Prompts first for the module (from `ADDONS_PATH/config/` directories), then for the workflow. Both prompts support fuzzy search.
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
prbot export email-templates
|
|
172
|
+
prbot export email-templates --no-commit
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Options:
|
|
176
|
+
|
|
177
|
+
| Flag | Description |
|
|
178
|
+
| ------------- | ------------------------ |
|
|
179
|
+
| `--no-commit` | Skip the git commit step |
|
|
180
|
+
|
|
171
181
|
### `prbot init`
|
|
172
182
|
|
|
173
|
-
Interactive setup: writes `~/.config/prbot/config
|
|
183
|
+
Interactive setup: writes `~/.config/prbot/config`.
|
|
174
184
|
|
|
175
185
|
### `prbot update`
|
|
176
186
|
|
|
@@ -180,10 +190,3 @@ Reinstalls the latest published version from npm.
|
|
|
180
190
|
prbot update
|
|
181
191
|
```
|
|
182
192
|
|
|
183
|
-
## Tab completion
|
|
184
|
-
|
|
185
|
-
After `prbot init` and sourcing `~/.bashrc`, `<module>` arguments autocomplete from directories in `ADDONS_PATH/config/`.
|
|
186
|
-
|
|
187
|
-
```
|
|
188
|
-
prbot pr config_wf_<TAB> # lists all workflow modules
|
|
189
|
-
```
|
package/package.json
CHANGED
package/src/commands/autopr.js
CHANGED
|
@@ -73,6 +73,10 @@ function buildPrDescription(taskIds, jiras) {
|
|
|
73
73
|
async function createDevopsPR(branch, title, description) {
|
|
74
74
|
const { DEVOPS_ORG, DEVOPS_PROJECT, DEVOPS_REPO, AUTOPR_TARGET_BRANCH } = process.env;
|
|
75
75
|
const apiUrl = `https://dev.azure.com/${DEVOPS_ORG}/${DEVOPS_PROJECT}/_apis/git/repositories/${DEVOPS_REPO}/pullrequests?api-version=7.0`;
|
|
76
|
+
const reviewers = process.env.AUTOPR_REQUIRED_REVIEWER_ID
|
|
77
|
+
? [{ id: process.env.AUTOPR_REQUIRED_REVIEWER_ID, isRequired: true }]
|
|
78
|
+
: [];
|
|
79
|
+
|
|
76
80
|
const res = await fetch(apiUrl, {
|
|
77
81
|
method: 'POST',
|
|
78
82
|
headers: devopsHeaders(),
|
|
@@ -82,6 +86,7 @@ async function createDevopsPR(branch, title, description) {
|
|
|
82
86
|
isDraft: true,
|
|
83
87
|
sourceRefName: `refs/heads/${branch}`,
|
|
84
88
|
targetRefName: `refs/heads/${AUTOPR_TARGET_BRANCH}`,
|
|
89
|
+
reviewers,
|
|
85
90
|
}),
|
|
86
91
|
});
|
|
87
92
|
const data = await res.json();
|
package/src/commands/export.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { exportPb } from './exportPb.js';
|
|
2
2
|
import { exportImperex } from './exportImperex.js';
|
|
3
3
|
import { exportEmailTemplates } from './exportEmailTemplates.js';
|
|
4
|
+
import { exportWorkflow } from './exportWorkflow.js';
|
|
4
5
|
|
|
5
6
|
function exportRip() {
|
|
6
7
|
console.log('Not implemented yet.');
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
export { exportPb, exportRip, exportImperex, exportEmailTemplates };
|
|
10
|
+
export { exportPb, exportRip, exportImperex, exportEmailTemplates, exportWorkflow };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import search from '@inquirer/search';
|
|
4
|
+
import { select } from '@inquirer/prompts';
|
|
5
|
+
import { resolveAddonsPath } from '../lib/addons.js';
|
|
6
|
+
import { fuzzyMatch } from '../lib/fuzzy.js';
|
|
7
|
+
import { runPr } from './pr.js';
|
|
8
|
+
import { verbot } from './ver.js';
|
|
9
|
+
|
|
10
|
+
async function getModuleChoices() {
|
|
11
|
+
const ADDONS_PATH = resolveAddonsPath(process.env.ADDONS_PATH);
|
|
12
|
+
const configDir = path.join(ADDONS_PATH, 'config');
|
|
13
|
+
const entries = await fs.readdir(configDir, { withFileTypes: true });
|
|
14
|
+
return entries
|
|
15
|
+
.filter((e) => e.isDirectory())
|
|
16
|
+
.map((e) => ({ name: e.name, value: e.name }));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function exportWorkflow(opts) {
|
|
20
|
+
const moduleChoices = await getModuleChoices();
|
|
21
|
+
const module = await search({
|
|
22
|
+
message: 'Select module:',
|
|
23
|
+
source: async (input) => {
|
|
24
|
+
if (!input) return moduleChoices;
|
|
25
|
+
return moduleChoices.filter((c) => fuzzyMatch(c.name, input));
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
await runPr(module, opts);
|
|
30
|
+
|
|
31
|
+
let bumpLevel = opts.bump;
|
|
32
|
+
if (!bumpLevel) {
|
|
33
|
+
bumpLevel = await select({
|
|
34
|
+
message: 'Bump version?',
|
|
35
|
+
choices: [
|
|
36
|
+
{ name: 'No bump', value: 'none' },
|
|
37
|
+
{ name: 'Patch', value: 'patch' },
|
|
38
|
+
{ name: 'Minor', value: 'minor' },
|
|
39
|
+
{ name: 'Major', value: 'major' },
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (bumpLevel && bumpLevel !== 'none') {
|
|
45
|
+
await verbot(module, bumpLevel);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { exportWorkflow };
|
package/src/commands/init.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import path from 'path';
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
3
2
|
import inquirer from 'inquirer';
|
|
4
|
-
import {
|
|
3
|
+
import { CONFIG_DIR, CONFIG_FILE } from '../config.js';
|
|
5
4
|
|
|
6
|
-
async function init(
|
|
5
|
+
async function init() {
|
|
7
6
|
if (!existsSync(CONFIG_DIR)) {
|
|
8
7
|
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
9
8
|
}
|
|
@@ -137,19 +136,6 @@ async function init(completion) {
|
|
|
137
136
|
.join('\n') + '\n'
|
|
138
137
|
);
|
|
139
138
|
console.log(`Config written to ${CONFIG_FILE}`);
|
|
140
|
-
|
|
141
|
-
writeFileSync(COMPLETION_SCRIPT, completion.generateCompletionCode());
|
|
142
|
-
console.log(`Completion script written to ${COMPLETION_SCRIPT}`);
|
|
143
|
-
|
|
144
|
-
const rcFile = path.join(process.env.HOME || '', '.bashrc');
|
|
145
|
-
const sourceLine = `source ${COMPLETION_SCRIPT}`;
|
|
146
|
-
const rcContent = existsSync(rcFile) ? readFileSync(rcFile, 'utf-8') : '';
|
|
147
|
-
if (!rcContent.includes(sourceLine)) {
|
|
148
|
-
appendFileSync(rcFile, `\n# prbot completion\n${sourceLine}\n`);
|
|
149
|
-
console.log(`Registered completion in ${rcFile} — run: source ~/.bashrc`);
|
|
150
|
-
} else {
|
|
151
|
-
console.log('Completion already registered in ~/.bashrc');
|
|
152
|
-
}
|
|
153
139
|
}
|
|
154
140
|
|
|
155
141
|
export { init };
|
package/src/commands/pr.js
CHANGED
|
@@ -20,7 +20,7 @@ async function getFiles(module_name, token) {
|
|
|
20
20
|
return await response.json();
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
async function
|
|
23
|
+
async function runPr(module_name, opts = {}) {
|
|
24
24
|
const token = await getToken();
|
|
25
25
|
const files = await getFiles(module_name, token);
|
|
26
26
|
|
|
@@ -69,6 +69,8 @@ async function main(module_name) {
|
|
|
69
69
|
console.log(`Processed: ${file.name} -> ${destPath}`);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
if (opts.commit === false) return;
|
|
73
|
+
|
|
72
74
|
const workflowDir = path.join(ADDONS_PATH, 'config', module_name, 'data');
|
|
73
75
|
const filesToAdd = [
|
|
74
76
|
path.join(workflowDir, 'workflow_missing_relations.xml'),
|
|
@@ -95,4 +97,8 @@ async function main(module_name) {
|
|
|
95
97
|
console.log(`Committed with message: ${commitMessage}`);
|
|
96
98
|
}
|
|
97
99
|
|
|
98
|
-
|
|
100
|
+
async function main(module_name) {
|
|
101
|
+
return runPr(module_name);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export { main, runPr };
|
package/src/config.js
CHANGED
|
@@ -2,6 +2,5 @@ import path from 'path';
|
|
|
2
2
|
|
|
3
3
|
const CONFIG_DIR = path.join(process.env.HOME || '', '.config', 'prbot');
|
|
4
4
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config');
|
|
5
|
-
const COMPLETION_SCRIPT = path.join(CONFIG_DIR, 'completion.sh');
|
|
6
5
|
|
|
7
|
-
export { CONFIG_DIR, CONFIG_FILE
|
|
6
|
+
export { CONFIG_DIR, CONFIG_FILE };
|
package/src/index.js
CHANGED
|
@@ -1,67 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readdirSync, readFileSync } from 'fs';
|
|
3
2
|
import { execFile } from 'child_process';
|
|
4
|
-
import path from 'path';
|
|
5
3
|
import { program } from 'commander';
|
|
6
4
|
import { configDotenv } from 'dotenv';
|
|
7
|
-
import omelette from 'omelette';
|
|
8
5
|
import { autopr } from './commands/autopr.js';
|
|
9
6
|
import { changelog } from './commands/changelog.js';
|
|
10
7
|
import { commit } from './commands/commit.js';
|
|
11
|
-
import { exportPb, exportRip, exportImperex, exportEmailTemplates } from './commands/export.js';
|
|
8
|
+
import { exportPb, exportRip, exportImperex, exportEmailTemplates, exportWorkflow } from './commands/export.js';
|
|
12
9
|
import { init } from './commands/init.js';
|
|
13
10
|
import { main as prMain } from './commands/pr.js';
|
|
14
11
|
import { verbot } from './commands/ver.js';
|
|
15
12
|
import { CONFIG_FILE } from './config.js';
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
function replyModules(reply) {
|
|
20
|
-
try {
|
|
21
|
-
const raw = readFileSync(CONFIG_FILE, 'utf-8');
|
|
22
|
-
const match = raw.match(/^ADDONS_PATH=(.+)$/m);
|
|
23
|
-
if (!match) { reply([]); return; }
|
|
24
|
-
const addonsPath = match[1].trim().replace(/^~/, process.env.HOME || '');
|
|
25
|
-
reply(readdirSync(path.join(addonsPath, 'config')));
|
|
26
|
-
} catch {
|
|
27
|
-
reply([]);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const completion = omelette('prbot <command> <subOrModule> <module>');
|
|
32
|
-
completion.on('command', ({ reply }) => {
|
|
33
|
-
reply(['pr', 'ver', 'init', 'changelog', 'autopr', 'commit', 'export']);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// 2nd token: export subcommands or module (for non-export commands)
|
|
37
|
-
completion.on('subOrModule', ({ before, reply }) => {
|
|
38
|
-
if (before === 'export') {
|
|
39
|
-
reply(EXPORT_SUBCOMMANDS);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
if (['init', 'changelog', 'autopr'].includes(before)) {
|
|
43
|
-
reply([]);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
replyModules(reply);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// 3rd token: module (only relevant for export subcommands that take one)
|
|
50
|
-
completion.on('module', ({ before, reply }) => {
|
|
51
|
-
if (['workflow'].includes(before)) {
|
|
52
|
-
replyModules(reply);
|
|
53
|
-
} else {
|
|
54
|
-
reply([]);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
completion.init();
|
|
59
|
-
|
|
60
|
-
const isCompletionMode = process.argv.includes('--compbash') || process.argv.includes('--compzsh');
|
|
61
|
-
|
|
62
|
-
if (!isCompletionMode) {
|
|
63
|
-
configDotenv({ path: CONFIG_FILE });
|
|
64
|
-
}
|
|
14
|
+
configDotenv({ path: CONFIG_FILE });
|
|
65
15
|
|
|
66
16
|
program
|
|
67
17
|
.command('pr <module>')
|
|
@@ -90,9 +40,9 @@ program
|
|
|
90
40
|
|
|
91
41
|
program
|
|
92
42
|
.command('init')
|
|
93
|
-
.description('Create config file
|
|
43
|
+
.description('Create config file')
|
|
94
44
|
.action(() => {
|
|
95
|
-
init(
|
|
45
|
+
init();
|
|
96
46
|
});
|
|
97
47
|
|
|
98
48
|
const collect = (val, prev) => [...(prev ?? []), val];
|
|
@@ -130,18 +80,13 @@ program.command('commit').action((opts) => {
|
|
|
130
80
|
const exportCmd = program.command('export');
|
|
131
81
|
|
|
132
82
|
exportCmd
|
|
133
|
-
.command('workflow
|
|
134
|
-
.option('-
|
|
135
|
-
.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
})
|
|
142
|
-
.catch((err) => {
|
|
143
|
-
throw err;
|
|
144
|
-
});
|
|
83
|
+
.command('workflow')
|
|
84
|
+
.option('--no-commit')
|
|
85
|
+
.option('-b, --bump <level>', 'Version bump level (patch, minor, major)')
|
|
86
|
+
.action((opts) => {
|
|
87
|
+
exportWorkflow(opts).catch((err) => {
|
|
88
|
+
throw err;
|
|
89
|
+
});
|
|
145
90
|
});
|
|
146
91
|
|
|
147
92
|
exportCmd.command('rip').action(() => exportRip());
|
package/index.js
DELETED
|
@@ -1,376 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { execFile } from 'child_process';
|
|
3
|
-
import {
|
|
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']);
|
|
26
|
-
});
|
|
27
|
-
|
|
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([]);
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
completion.init();
|
|
48
|
-
|
|
49
|
-
const isCompletionMode = process.argv.includes('--compbash') || process.argv.includes('--compzsh');
|
|
50
|
-
|
|
51
|
-
if (!isCompletionMode) {
|
|
52
|
-
configDotenv({ path: CONFIG_FILE });
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function getToken() {
|
|
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;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async function getFiles(module_name, token) {
|
|
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();
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function main(module_name) {
|
|
93
|
-
const token = await getToken();
|
|
94
|
-
const files = await getFiles(module_name, token);
|
|
95
|
-
|
|
96
|
-
let ADDONS_PATH = process.env.ADDONS_PATH;
|
|
97
|
-
if (ADDONS_PATH.startsWith('~')) {
|
|
98
|
-
ADDONS_PATH = ADDONS_PATH.replace('~', process.env.HOME);
|
|
99
|
-
}
|
|
100
|
-
|
|
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
|
-
}
|
|
130
|
-
|
|
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
|
-
);
|
|
141
|
-
|
|
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
|
-
}
|
|
149
|
-
|
|
150
|
-
// Create destination directory if needed
|
|
151
|
-
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
152
|
-
|
|
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
|
-
}
|
|
174
|
-
|
|
175
|
-
// Commit changes
|
|
176
|
-
const commitMessage = `[IMP][${module_name}] Update workflow`;
|
|
177
|
-
await new Promise((resolve, reject) => {
|
|
178
|
-
execFile('git', ['commit', '-m', commitMessage], { cwd: ADDONS_PATH }, (error) => {
|
|
179
|
-
if (error) reject(error);
|
|
180
|
-
else resolve();
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
console.log(`Committed with message: ${commitMessage}`);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async function verbot(module_name, level) {
|
|
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');
|
|
199
|
-
try {
|
|
200
|
-
await fs.access(manifestPath);
|
|
201
|
-
} catch {
|
|
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
|
-
}
|
|
208
|
-
}
|
|
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}"`
|
|
239
|
-
);
|
|
240
|
-
|
|
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}`);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
program
|
|
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
|
-
});
|
|
278
|
-
|
|
279
|
-
program
|
|
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
|
-
}
|
|
286
|
-
|
|
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
|
-
});
|
|
365
|
-
|
|
366
|
-
program
|
|
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
|
-
});
|
|
375
|
-
|
|
376
|
-
program.parse();
|