@waron97/prbot 3.1.1 → 3.1.2
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/package.json +1 -1
- package/src/commands/autopr.js +77 -0
- package/src/commands/changelog.js +50 -0
- package/src/commands/exportEmailTemplates.js +5 -5
- package/src/index.js +1 -0
- package/src/lib/premigrate.js +13 -21
package/package.json
CHANGED
package/src/commands/autopr.js
CHANGED
|
@@ -8,10 +8,12 @@ import { fuzzyMatch } from '../lib/fuzzy.js';
|
|
|
8
8
|
import { execGit } from '../lib/git.js';
|
|
9
9
|
import {
|
|
10
10
|
appendPrToLine,
|
|
11
|
+
appendRefsToLine,
|
|
11
12
|
buildRefString,
|
|
12
13
|
detectIndentation,
|
|
13
14
|
extractSections,
|
|
14
15
|
findDuplicateLine,
|
|
16
|
+
findLineByPrNumber,
|
|
15
17
|
findSectionEndLine,
|
|
16
18
|
} from './changelog.js';
|
|
17
19
|
|
|
@@ -70,6 +72,28 @@ function buildPrDescription(taskIds, jiras) {
|
|
|
70
72
|
return lines.join('\n');
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
async function fetchActivePr(branch) {
|
|
76
|
+
const { DEVOPS_ORG, DEVOPS_PROJECT, DEVOPS_REPO } = process.env;
|
|
77
|
+
const apiUrl = `https://dev.azure.com/${DEVOPS_ORG}/${DEVOPS_PROJECT}/_apis/git/repositories/${DEVOPS_REPO}/pullrequests?searchCriteria.sourceRefName=refs/heads/${branch}&searchCriteria.status=active&api-version=7.0`;
|
|
78
|
+
const res = await fetch(apiUrl, { headers: devopsHeaders() });
|
|
79
|
+
const data = await res.json();
|
|
80
|
+
if (!res.ok) throw new Error(`DevOps PR fetch failed: ${JSON.stringify(data)}`);
|
|
81
|
+
if (!data.value || data.value.length === 0) throw new Error(`No active PR found for branch: ${branch}`);
|
|
82
|
+
return data.value[0];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function patchDevopsPrDescription(prId, description) {
|
|
86
|
+
const { DEVOPS_ORG, DEVOPS_PROJECT, DEVOPS_REPO } = process.env;
|
|
87
|
+
const apiUrl = `https://dev.azure.com/${DEVOPS_ORG}/${DEVOPS_PROJECT}/_apis/git/repositories/${DEVOPS_REPO}/pullrequests/${prId}?api-version=7.0`;
|
|
88
|
+
const res = await fetch(apiUrl, {
|
|
89
|
+
method: 'PATCH',
|
|
90
|
+
headers: devopsHeaders(),
|
|
91
|
+
body: JSON.stringify({ description }),
|
|
92
|
+
});
|
|
93
|
+
const data = await res.json();
|
|
94
|
+
if (!res.ok) throw new Error(`DevOps PR patch failed: ${JSON.stringify(data)}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
73
97
|
async function createDevopsPR(branch, title, description) {
|
|
74
98
|
const { DEVOPS_ORG, DEVOPS_PROJECT, DEVOPS_REPO, AUTOPR_TARGET_BRANCH } = process.env;
|
|
75
99
|
const apiUrl = `https://dev.azure.com/${DEVOPS_ORG}/${DEVOPS_PROJECT}/_apis/git/repositories/${DEVOPS_REPO}/pullrequests?api-version=7.0`;
|
|
@@ -190,7 +214,60 @@ async function selectSection(sections, candidates) {
|
|
|
190
214
|
return sections.find((s) => s.heading === selected);
|
|
191
215
|
}
|
|
192
216
|
|
|
217
|
+
async function autoprAmend(options) {
|
|
218
|
+
const ADDONS_PATH = resolveAddonsPath(process.env.ADDONS_PATH);
|
|
219
|
+
const changelogPath = `${ADDONS_PATH}/CHANGELOG.md`;
|
|
220
|
+
const { DEVOPS_ORG, DEVOPS_PROJECT, DEVOPS_REPO } = process.env;
|
|
221
|
+
|
|
222
|
+
const branch = (await execGit(['rev-parse', '--abbrev-ref', 'HEAD'], ADDONS_PATH)).trim();
|
|
223
|
+
const pr = await fetchActivePr(branch);
|
|
224
|
+
const prNumber = pr.pullRequestId;
|
|
225
|
+
const prUrl = `https://dev.azure.com/${DEVOPS_ORG}/${DEVOPS_PROJECT}/_git/${DEVOPS_REPO}/pullrequest/${prNumber}`;
|
|
226
|
+
console.log(`Found PR #${prNumber}: ${prUrl}`);
|
|
227
|
+
|
|
228
|
+
const ids = options.trident ?? [];
|
|
229
|
+
const jiras = options.jira ?? [];
|
|
230
|
+
|
|
231
|
+
const tasks = ids.length > 0 ? await Promise.all(ids.map(fetchTask)) : [];
|
|
232
|
+
tasks.forEach((t) => console.log(`Task: ${t.name}`));
|
|
233
|
+
|
|
234
|
+
const newLinks = [
|
|
235
|
+
...ids.map((id) => `${process.env.TRIDENT_URL}/odoo/my-tasks/${id}`),
|
|
236
|
+
...jiras.map((j) => `https://sorgenia.atlassian.net/browse/${j}`),
|
|
237
|
+
];
|
|
238
|
+
if (newLinks.length > 0) {
|
|
239
|
+
const updatedDescription = pr.description
|
|
240
|
+
? `${pr.description}\n${newLinks.join('\n')}`
|
|
241
|
+
: newLinks.join('\n');
|
|
242
|
+
await patchDevopsPrDescription(prNumber, updatedDescription);
|
|
243
|
+
console.log('PR description updated');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
for (let i = 0; i < ids.length; i++) {
|
|
247
|
+
await appendChecklistPrLink(ids[i], tasks[i].x_release_checklist, prUrl, prNumber);
|
|
248
|
+
}
|
|
249
|
+
if (ids.length > 0) console.log('Checklist updated');
|
|
250
|
+
|
|
251
|
+
const content = readFileSync(changelogPath, 'utf-8');
|
|
252
|
+
const existing = findLineByPrNumber(content, prNumber);
|
|
253
|
+
if (!existing) {
|
|
254
|
+
console.log(`Warning: no changelog line found for PR #${prNumber} — skipping changelog update`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const lines = content.split('\n');
|
|
258
|
+
lines[existing.lineNumber] = appendRefsToLine(existing.line, ids, jiras);
|
|
259
|
+
await fs.writeFile(changelogPath, lines.join('\n'));
|
|
260
|
+
|
|
261
|
+
await execGit(['add', 'CHANGELOG.md'], ADDONS_PATH);
|
|
262
|
+
await execGit(['commit', '-m', '[DOC][CHANGELOG] Changelog'], ADDONS_PATH);
|
|
263
|
+
await execGit(['push'], ADDONS_PATH);
|
|
264
|
+
console.log('Changelog updated and pushed');
|
|
265
|
+
console.log('\nReminder: squash the two changelog commits before merging the PR.');
|
|
266
|
+
}
|
|
267
|
+
|
|
193
268
|
async function autopr(options) {
|
|
269
|
+
if (options.amend) return autoprAmend(options);
|
|
270
|
+
|
|
194
271
|
const ADDONS_PATH = resolveAddonsPath(process.env.ADDONS_PATH);
|
|
195
272
|
const changelogPath = `${ADDONS_PATH}/CHANGELOG.md`;
|
|
196
273
|
|
|
@@ -179,6 +179,54 @@ async function changelog(prNumber, options) {
|
|
|
179
179
|
console.log('Changelog entry added');
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
function findLineByPrNumber(content, prNumber) {
|
|
183
|
+
const lines = content.split('\n');
|
|
184
|
+
for (let i = 0; i < lines.length; i++) {
|
|
185
|
+
if (lines[i].includes(`#${prNumber}ADO`)) {
|
|
186
|
+
return { lineNumber: i, line: lines[i] };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function appendRefsToLine(line, tridentIds, jiras) {
|
|
193
|
+
let result = line;
|
|
194
|
+
|
|
195
|
+
if (tridentIds && tridentIds.length > 0) {
|
|
196
|
+
const suffix = tridentIds.map((id) => `#${id}`).join(', ');
|
|
197
|
+
const tridentMatch = result.match(/Trident (#[\d, #]+)/);
|
|
198
|
+
if (tridentMatch) {
|
|
199
|
+
result = result.replace(/Trident (#[\d, #]+)/, `Trident ${tridentMatch[1]}, ${suffix}`);
|
|
200
|
+
} else {
|
|
201
|
+
const parenMatch = result.match(/\(([^)]*)\)\s*$/);
|
|
202
|
+
if (parenMatch) {
|
|
203
|
+
result = result.replace(/\(([^)]*)\)\s*$/, `(Trident ${suffix}, ${parenMatch[1]})`);
|
|
204
|
+
} else {
|
|
205
|
+
result = `${result.trimEnd()} (Trident ${suffix})`;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (jiras && jiras.length > 0) {
|
|
211
|
+
const suffix = jiras.join(', ');
|
|
212
|
+
const jiraMatch = result.match(/JIRA ([^,)]+)/);
|
|
213
|
+
if (jiraMatch) {
|
|
214
|
+
result = result.replace(/JIRA ([^,)]+)/, `JIRA ${jiraMatch[1].trimEnd()}, ${suffix}`);
|
|
215
|
+
} else if (result.includes('PR sorgenia_addons')) {
|
|
216
|
+
result = result.replace(/(PR sorgenia_addons)/, `JIRA ${suffix}, $1`);
|
|
217
|
+
} else {
|
|
218
|
+
const parenMatch = result.match(/\(([^)]*)\)\s*$/);
|
|
219
|
+
if (parenMatch) {
|
|
220
|
+
result = result.replace(/\(([^)]*)\)\s*$/, `(${parenMatch[1]}, JIRA ${suffix})`);
|
|
221
|
+
} else {
|
|
222
|
+
result = `${result.trimEnd()} (JIRA ${suffix})`;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
|
|
182
230
|
export {
|
|
183
231
|
changelog,
|
|
184
232
|
extractSections,
|
|
@@ -187,4 +235,6 @@ export {
|
|
|
187
235
|
buildRefString,
|
|
188
236
|
findDuplicateLine,
|
|
189
237
|
appendPrToLine,
|
|
238
|
+
findLineByPrNumber,
|
|
239
|
+
appendRefsToLine,
|
|
190
240
|
};
|
|
@@ -169,7 +169,7 @@ async function exportEmailTemplates(opts) {
|
|
|
169
169
|
log(`Written: ${outPath}`);
|
|
170
170
|
|
|
171
171
|
const newMappings = await readEmailTemplateMappings(dataDir);
|
|
172
|
-
const
|
|
172
|
+
const renames = detectEmailRenames(oldMappings, newMappings);
|
|
173
173
|
|
|
174
174
|
let bumpLevel = opts.bump;
|
|
175
175
|
if (!bumpLevel) {
|
|
@@ -186,13 +186,13 @@ async function exportEmailTemplates(opts) {
|
|
|
186
186
|
|
|
187
187
|
let preMigratePath = null;
|
|
188
188
|
|
|
189
|
-
if (
|
|
190
|
-
log(`Renamed
|
|
189
|
+
if (renames.length > 0) {
|
|
190
|
+
log(`Renamed XML IDs (${renames.length}): ${renames.map((r) => `${r.oldXmlId} → ${r.newXmlId}`).join(', ')}`);
|
|
191
191
|
|
|
192
192
|
let shouldGenerate = opts.autoPremigrate;
|
|
193
193
|
if (!shouldGenerate && !isSilent()) {
|
|
194
194
|
shouldGenerate = await confirm({
|
|
195
|
-
message: `Detected ${
|
|
195
|
+
message: `Detected ${renames.length} renamed template_code(s). Generate pre-migrate script?`,
|
|
196
196
|
default: true,
|
|
197
197
|
});
|
|
198
198
|
}
|
|
@@ -206,7 +206,7 @@ async function exportEmailTemplates(opts) {
|
|
|
206
206
|
const migrationDir = path.join(ADDONS_PATH, 'config', module, 'migrations', version);
|
|
207
207
|
preMigratePath = path.join(migrationDir, 'pre-migrate.py');
|
|
208
208
|
await fs.mkdir(migrationDir, { recursive: true });
|
|
209
|
-
await fs.writeFile(preMigratePath, generateEmailPreMigrateScript(
|
|
209
|
+
await fs.writeFile(preMigratePath, generateEmailPreMigrateScript(renames));
|
|
210
210
|
log(`Wrote pre-migrate: ${preMigratePath}`);
|
|
211
211
|
}
|
|
212
212
|
}
|
package/src/index.js
CHANGED
|
@@ -77,6 +77,7 @@ program
|
|
|
77
77
|
.option('-m, --message <text>', 'Changelog entry message')
|
|
78
78
|
.option('-b, --branch <name>', 'Branch name (default: autopr_<taskId>)')
|
|
79
79
|
.option('-n, --name <text>', 'PR title (default: task name from Odoo)')
|
|
80
|
+
.option('--amend', 'Amend existing PR on current branch with new trident/jira refs')
|
|
80
81
|
.action((opts) => {
|
|
81
82
|
autopr(opts).catch((err) => {
|
|
82
83
|
throw err;
|
package/src/lib/premigrate.js
CHANGED
|
@@ -167,15 +167,15 @@ async function readEmailTemplateMappings(dataDir) {
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
function detectEmailRenames(oldMap, newMap) {
|
|
170
|
-
const
|
|
170
|
+
const renames = [];
|
|
171
171
|
for (const [code, oldXmlId] of oldMap) {
|
|
172
172
|
const newXmlId = newMap.get(code);
|
|
173
|
-
if (newXmlId && newXmlId !== oldXmlId)
|
|
173
|
+
if (newXmlId && newXmlId !== oldXmlId) renames.push({ oldXmlId, newXmlId });
|
|
174
174
|
}
|
|
175
|
-
return
|
|
175
|
+
return renames;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
function generateEmailPreMigrateScript(
|
|
178
|
+
function generateEmailPreMigrateScript(renames) {
|
|
179
179
|
const lines = [
|
|
180
180
|
'# Copyright 2025-TODAY Symphonie Prime S.r.l. (www.symphonieprime.com)',
|
|
181
181
|
'# All rights reserved.',
|
|
@@ -185,28 +185,20 @@ function generateEmailPreMigrateScript(templateCodes) {
|
|
|
185
185
|
'',
|
|
186
186
|
'@openupgrade.migrate()',
|
|
187
187
|
'def migrate(env, version):',
|
|
188
|
-
'
|
|
189
|
-
' "SELECT 1 FROM information_schema.tables WHERE table_name = %s",',
|
|
190
|
-
' ("mail_template",),',
|
|
191
|
-
' )',
|
|
192
|
-
' if not env.cr.fetchone():',
|
|
193
|
-
' return',
|
|
194
|
-
'',
|
|
188
|
+
' renames = [',
|
|
195
189
|
];
|
|
196
190
|
|
|
197
|
-
|
|
198
|
-
lines.push(`
|
|
199
|
-
} else {
|
|
200
|
-
lines.push(' template_codes = (');
|
|
201
|
-
for (const code of templateCodes) lines.push(` "${code}",`);
|
|
202
|
-
lines.push(' )');
|
|
191
|
+
for (const { oldXmlId, newXmlId } of renames) {
|
|
192
|
+
lines.push(` ("${oldXmlId}", "${newXmlId}"),`);
|
|
203
193
|
}
|
|
204
194
|
|
|
205
195
|
lines.push(
|
|
206
|
-
'
|
|
207
|
-
'
|
|
208
|
-
' (
|
|
209
|
-
'
|
|
196
|
+
' ]',
|
|
197
|
+
' for old_name, new_name in renames:',
|
|
198
|
+
' env.cr.execute(',
|
|
199
|
+
' "UPDATE ir_model_data SET name = %s WHERE name = %s AND model = %s",',
|
|
200
|
+
' (new_name, old_name, "mail.template"),',
|
|
201
|
+
' )',
|
|
210
202
|
);
|
|
211
203
|
|
|
212
204
|
return lines.join('\n') + '\n';
|