@waron97/prbot 3.2.1 → 3.4.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 +26 -18
- package/agrippa-pb.md +141 -68
- package/package.json +5 -1
- package/src/agrippa/commands/clone.js +17 -10
- package/src/agrippa/commands/cloneLrp.js +114 -0
- package/src/agrippa/commands/clonePb.js +10 -10
- package/src/agrippa/commands/diff.js +9 -8
- package/src/agrippa/commands/init.js +14 -13
- package/src/agrippa/commands/initPhase.js +51 -42
- package/src/agrippa/commands/pb.js +71 -39
- package/src/agrippa/commands/pull.js +137 -42
- package/src/agrippa/commands/pullLrp.js +54 -0
- package/src/agrippa/commands/pullPb.js +2 -3
- package/src/agrippa/commands/push.js +112 -33
- package/src/agrippa/commands/pushLrp.js +56 -0
- package/src/agrippa/commands/repair.js +4 -3
- package/src/agrippa/index.js +48 -30
- package/src/agrippa/lib/lrpApi.js +153 -0
- package/src/agrippa/lib/pbEdit.js +31 -10
- package/src/agrippa/lib/pbLayout.js +8 -1
- package/src/agrippa/lib/pbModel.js +318 -86
- package/src/agrippa/lib/pbPreview.js +6 -2
- package/src/agrippa/lib/pbProject.js +93 -7
- package/src/agrippa/lib/pbScriptTemplate.js +29 -0
- package/src/commands/autopr.js +21 -22
- package/src/commands/changelog.js +4 -3
- package/src/commands/commit.js +11 -10
- package/src/commands/export.js +2 -1
- package/src/commands/exportPb.js +19 -2
- package/src/commands/init.js +2 -1
- package/src/commands/routine.js +19 -8
- package/src/index.js +137 -112
- package/src/lib/auth.js +19 -1
- package/src/lib/logger.js +12 -1
|
@@ -18,7 +18,15 @@
|
|
|
18
18
|
import slugify from 'slugify';
|
|
19
19
|
import { Document, visit, parse as yamlParse, stringify as yamlStringify } from 'yaml';
|
|
20
20
|
import { computeChecksum } from './checksum.js';
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
buildProcess,
|
|
23
|
+
compareDiagram,
|
|
24
|
+
compareProcess,
|
|
25
|
+
diagramGeometry,
|
|
26
|
+
diff,
|
|
27
|
+
normalizeProcessTree,
|
|
28
|
+
parseProcess,
|
|
29
|
+
} from './pbModel.js';
|
|
22
30
|
|
|
23
31
|
const MANIFEST_FILE = '.agrippa-pb.json';
|
|
24
32
|
const STRUCTURE_FILE = 'structure.yaml';
|
|
@@ -52,7 +60,9 @@ const NODE_FIELDS = [
|
|
|
52
60
|
'fields',
|
|
53
61
|
'formKey',
|
|
54
62
|
'attachedToRef',
|
|
55
|
-
'
|
|
63
|
+
'eventDefinitions',
|
|
64
|
+
'calledElement',
|
|
65
|
+
'dataIO',
|
|
56
66
|
'multiInstance',
|
|
57
67
|
'documentation',
|
|
58
68
|
];
|
|
@@ -107,9 +117,12 @@ function geometryMaps(diagram) {
|
|
|
107
117
|
if (s.attrs.isExpanded !== undefined)
|
|
108
118
|
expanded[s.attrs.bpmnElement] = s.attrs.isExpanded;
|
|
109
119
|
}
|
|
120
|
+
// Always record the edge, even with zero waypoints (rare, but real —
|
|
121
|
+
// some exporters emit an empty <...:BPMNEdge>). Skipping on `.length`
|
|
122
|
+
// would drop the entry, so buildDiagram doesn't recreate the edge shape
|
|
123
|
+
// at all, losing a real (if degenerate) diagram element.
|
|
110
124
|
for (const e of diagram.edges)
|
|
111
|
-
|
|
112
|
-
waypoints[e.attrs.bpmnElement] = e.waypoints.map((w) => [w.x, w.y]);
|
|
125
|
+
waypoints[e.attrs.bpmnElement] = (e.waypoints || []).map((w) => [w.x, w.y]);
|
|
113
126
|
}
|
|
114
127
|
return { bounds, waypoints, expanded };
|
|
115
128
|
}
|
|
@@ -121,6 +134,7 @@ function structEdge(e, geo) {
|
|
|
121
134
|
if (e.name !== undefined) o.name = e.name;
|
|
122
135
|
if (e.condition !== undefined) o.condition = e.condition;
|
|
123
136
|
if (e.conditionType !== undefined) o.conditionType = e.conditionType;
|
|
137
|
+
if (e.conditionAttrs !== undefined) o.conditionAttrs = e.conditionAttrs;
|
|
124
138
|
if (e.documentation !== undefined) o.documentation = e.documentation;
|
|
125
139
|
if (geo.waypoints[e.id]) o.waypoints = geo.waypoints[e.id];
|
|
126
140
|
return o;
|
|
@@ -166,6 +180,7 @@ function flattenNodes(structNodes, parentId, model, read, geo) {
|
|
|
166
180
|
if (se.name !== undefined) e.name = se.name;
|
|
167
181
|
if (se.condition !== undefined) e.condition = se.condition;
|
|
168
182
|
if (se.conditionType !== undefined) e.conditionType = se.conditionType;
|
|
183
|
+
if (se.conditionAttrs !== undefined) e.conditionAttrs = se.conditionAttrs;
|
|
169
184
|
if (se.documentation !== undefined) e.documentation = se.documentation;
|
|
170
185
|
if (parentId) e.parent = parentId;
|
|
171
186
|
if (se.waypoints) geo.waypoints[se.id] = se.waypoints;
|
|
@@ -219,7 +234,10 @@ function decompose(payload) {
|
|
|
219
234
|
// back for the diagram).
|
|
220
235
|
const structure = {
|
|
221
236
|
process: model.process,
|
|
237
|
+
messages: model.messages,
|
|
222
238
|
errors: model.errors,
|
|
239
|
+
signals: model.signals,
|
|
240
|
+
extraDefs: model.extraDefs,
|
|
223
241
|
nodes: nestNodes(model, null, scriptsMap, geo),
|
|
224
242
|
annotations: model.annotations.map((a) =>
|
|
225
243
|
geo.bounds[a.id] ? { ...a, layout: geo.bounds[a.id] } : a
|
|
@@ -227,6 +245,9 @@ function decompose(payload) {
|
|
|
227
245
|
associations: model.associations.map((a) =>
|
|
228
246
|
geo.waypoints[a.id] ? { ...a, waypoints: geo.waypoints[a.id] } : a
|
|
229
247
|
),
|
|
248
|
+
groups: (model.groups || []).map((g) =>
|
|
249
|
+
geo.bounds[g.id] ? { ...g, layout: geo.bounds[g.id] } : g
|
|
250
|
+
),
|
|
230
251
|
};
|
|
231
252
|
files[STRUCTURE_FILE] = stringifyStructure(structure);
|
|
232
253
|
|
|
@@ -259,17 +280,22 @@ function recompose(read) {
|
|
|
259
280
|
|
|
260
281
|
const model = {
|
|
261
282
|
process: structure.process,
|
|
283
|
+
messages: structure.messages || [],
|
|
262
284
|
errors: structure.errors || [],
|
|
285
|
+
signals: structure.signals || [],
|
|
286
|
+
extraDefs: structure.extraDefs || [],
|
|
263
287
|
nodes: [],
|
|
264
288
|
edges: [],
|
|
265
289
|
annotations: structure.annotations || [],
|
|
266
290
|
associations: structure.associations || [],
|
|
291
|
+
groups: structure.groups || [],
|
|
267
292
|
};
|
|
268
293
|
const geo = { bounds: {}, waypoints: {}, expanded: {}, labelPos: {} };
|
|
269
294
|
flattenNodes(structure.nodes, null, model, read, geo);
|
|
270
|
-
// annotation/association geometry (top-level lists carry it inline)
|
|
295
|
+
// annotation/association/group geometry (top-level lists carry it inline)
|
|
271
296
|
for (const a of model.annotations) if (a.layout) geo.bounds[a.id] = a.layout;
|
|
272
297
|
for (const a of model.associations) if (a.waypoints) geo.waypoints[a.id] = a.waypoints;
|
|
298
|
+
for (const g of model.groups) if (g.layout) geo.bounds[g.id] = g.layout;
|
|
273
299
|
|
|
274
300
|
const built_page = buildProcess({ ns: manifest.ns, model, geo });
|
|
275
301
|
|
|
@@ -337,10 +363,69 @@ function stableStringify(value) {
|
|
|
337
363
|
return JSON.stringify(value ?? null);
|
|
338
364
|
}
|
|
339
365
|
|
|
366
|
+
// Canonical form of a recomposed payload used ONLY for change-detection
|
|
367
|
+
// checksums — never for the actual push payload (recompose() itself, and
|
|
368
|
+
// what gets sent on push, are untouched). Routes built_page through the same
|
|
369
|
+
// formatting/order-insensitive canonicalization already used by
|
|
370
|
+
// comparePayload()'s 0-loss round-trip verification:
|
|
371
|
+
// - normalizeProcessTree drops insignificant whitespace text nodes (incl.
|
|
372
|
+
// the extraDefs `#text` padding between <definitions>-root siblings) and
|
|
373
|
+
// sorts declarations by id, so cosmetic reformatting doesn't count;
|
|
374
|
+
// - diagramGeometry keeps only x/y/width/height/isExpanded/waypoints, so
|
|
375
|
+
// `format`-only artifacts like labelPos never enter the comparison.
|
|
376
|
+
// Without this, a push followed by an unmodified re-fetch could classify as
|
|
377
|
+
// a phantom `conflict` instead of `unchanged` — see
|
|
378
|
+
// ai_tasks/2026-07-16-lrp-clone/deferred_work.md items 1, 3, 4.
|
|
379
|
+
//
|
|
380
|
+
// `updated_date`/`modified_by` are server-managed bookkeeping, not process
|
|
381
|
+
// content — Odoo/Symple bump them on any touch of the record, including ones
|
|
382
|
+
// with no semantic effect (observed live: an integration service account
|
|
383
|
+
// re-saving the wizard moved `updated_date`/`modified_by` on both the
|
|
384
|
+
// top-level payload and a page wrapper with zero content diff otherwise, via
|
|
385
|
+
// comparePayload). They exist at two levels: the top-level payload scalars,
|
|
386
|
+
// and per-entry inside `payload.pages` (each page's audit wrapper, distinct
|
|
387
|
+
// from the page *content* under `.page`, which is left untouched). Left in,
|
|
388
|
+
// they permanently pin the object to `conflict` the moment anything server-
|
|
389
|
+
// side touches it, regardless of actual content — worse than the cosmetic
|
|
390
|
+
// built_page diffs above, since nothing local or an agrippa push causes them
|
|
391
|
+
// to resync.
|
|
392
|
+
const VOLATILE_AUDIT_FIELDS = ['updated_date', 'modified_by'];
|
|
393
|
+
|
|
394
|
+
function canonicalForChecksum(payload) {
|
|
395
|
+
const { process, decls } = normalizeProcessTree(payload.built_page);
|
|
396
|
+
const rest = omit(payload, ['built_page', 'pages', ...VOLATILE_AUDIT_FIELDS]);
|
|
397
|
+
// Page order is not a stable identity — locally it's whatever order the
|
|
398
|
+
// manifest happened to record at last decompose, while a live upstream
|
|
399
|
+
// fetch can return the same set of pages in a different order (observed
|
|
400
|
+
// live: a fresh fetch came back with pages 1-3 cyclically rotated versus
|
|
401
|
+
// the local manifest, same guids, zero content difference). Sort by the
|
|
402
|
+
// page's own guid — stable, unlike array position — before hashing, the
|
|
403
|
+
// same trick normalizeProcessTree already uses for extraDefs decls.
|
|
404
|
+
const pages = [...(payload.pages || [])]
|
|
405
|
+
.sort((a, b) => (a.guid ?? '').localeCompare(b.guid ?? ''))
|
|
406
|
+
.map((p) => omit(p, VOLATILE_AUDIT_FIELDS));
|
|
407
|
+
return {
|
|
408
|
+
...rest,
|
|
409
|
+
pages,
|
|
410
|
+
built_page_process: process,
|
|
411
|
+
built_page_decls: decls,
|
|
412
|
+
built_page_diagram: diagramGeometry(payload.built_page),
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Checksum of a recomposed payload, canonicalized so cosmetic round-trip
|
|
417
|
+
// differences don't register as a change. Used for checksum_at_pull baselines
|
|
418
|
+
// (clone/pull) and for the push/pull classifier (localChecksum/
|
|
419
|
+
// remoteChecksumPb below) — keep every PB/LRP checksum call site on this one
|
|
420
|
+
// function so baselines and comparisons stay on the same normalization.
|
|
421
|
+
function checksumOfPayload(payload) {
|
|
422
|
+
return computeChecksum(stableStringify(canonicalForChecksum(payload)));
|
|
423
|
+
}
|
|
424
|
+
|
|
340
425
|
// Checksum of the recomposed payload — stable across runs, changes only when the
|
|
341
426
|
// local files change. clonePb stores this as checksum_at_pull; push recomputes it.
|
|
342
427
|
function localChecksum(read) {
|
|
343
|
-
return
|
|
428
|
+
return checksumOfPayload(recompose(read));
|
|
344
429
|
}
|
|
345
430
|
|
|
346
431
|
// Semantic checksum of a remote payload. Decompose → recompose normalises field
|
|
@@ -349,7 +434,7 @@ function localChecksum(read) {
|
|
|
349
434
|
function remoteChecksumPb(payload) {
|
|
350
435
|
const { files } = decompose(payload);
|
|
351
436
|
const read = (p) => files[p] ?? '';
|
|
352
|
-
return
|
|
437
|
+
return checksumOfPayload(recompose(read));
|
|
353
438
|
}
|
|
354
439
|
|
|
355
440
|
// Enumerate the wizard's pages from the pages/*.yml files (the authoritative
|
|
@@ -388,6 +473,7 @@ export {
|
|
|
388
473
|
comparePayload,
|
|
389
474
|
verifyRoundTrip,
|
|
390
475
|
stableStringify,
|
|
476
|
+
checksumOfPayload,
|
|
391
477
|
localChecksum,
|
|
392
478
|
remoteChecksumPb,
|
|
393
479
|
enumeratePages,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Initial body scaffolded into a new scriptTask's .js file (mirrors the
|
|
2
|
+
// `pbtemplate` editor snippet) so created scripts aren't empty.
|
|
3
|
+
export const SCRIPT_TEMPLATE = `try {
|
|
4
|
+
// ----------------------------
|
|
5
|
+
// Input gathering
|
|
6
|
+
// ----------------------------
|
|
7
|
+
|
|
8
|
+
// ----------------------------
|
|
9
|
+
// Output variable initialization
|
|
10
|
+
// ----------------------------
|
|
11
|
+
|
|
12
|
+
// ----------------------------
|
|
13
|
+
// Logical Helpers
|
|
14
|
+
// ----------------------------
|
|
15
|
+
|
|
16
|
+
// ----------------------------
|
|
17
|
+
// Main Execution
|
|
18
|
+
// ----------------------------
|
|
19
|
+
|
|
20
|
+
// ----------------------------
|
|
21
|
+
// Output
|
|
22
|
+
// ----------------------------
|
|
23
|
+
|
|
24
|
+
} catch (err) {
|
|
25
|
+
execution.setVariable('isAlive', false);
|
|
26
|
+
execution.setVariable('errorCode', '');
|
|
27
|
+
execution.setVariable('errorMessage', err.message);
|
|
28
|
+
}
|
|
29
|
+
`;
|
package/src/commands/autopr.js
CHANGED
|
@@ -6,6 +6,7 @@ import fetch from 'node-fetch';
|
|
|
6
6
|
import { resolveAddonsPath } from '../lib/addons.js';
|
|
7
7
|
import { fuzzyMatch } from '../lib/fuzzy.js';
|
|
8
8
|
import { execGit } from '../lib/git.js';
|
|
9
|
+
import { log } from '../lib/logger.js';
|
|
9
10
|
import {
|
|
10
11
|
appendPrToLine,
|
|
11
12
|
appendRefsToLine,
|
|
@@ -120,7 +121,7 @@ async function createDevopsPR(branch, title, description) {
|
|
|
120
121
|
return { id: data.pullRequestId, url: prUrl };
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
async function appendChecklistPrLink(taskId, currentChecklist, prUrl
|
|
124
|
+
async function appendChecklistPrLink(taskId, currentChecklist, prUrl) {
|
|
124
125
|
const link = `<a href="${prUrl}">${prUrl}</a><br/>`;
|
|
125
126
|
const updated = currentChecklist ? `${currentChecklist}\n${link}` : link;
|
|
126
127
|
const url = `${process.env.TRIDENT_URL}/jsonrpc`;
|
|
@@ -191,8 +192,8 @@ async function selectSection(sections, candidates) {
|
|
|
191
192
|
const scored = candidates.length > 0 ? scoreSections(sections, candidates) : [];
|
|
192
193
|
|
|
193
194
|
if (candidates.length > 0) {
|
|
194
|
-
|
|
195
|
-
if (scored.length === 0)
|
|
195
|
+
log(`\nTask fields: ${candidates.join(' | ')}`);
|
|
196
|
+
if (scored.length === 0) log('No matching sections found.');
|
|
196
197
|
}
|
|
197
198
|
|
|
198
199
|
const scoredHeadings = new Set(scored.map((s) => s.heading));
|
|
@@ -224,13 +225,13 @@ async function autoprAmend(options) {
|
|
|
224
225
|
const pr = await fetchActivePr(branch);
|
|
225
226
|
const prNumber = pr.pullRequestId;
|
|
226
227
|
const prUrl = `https://dev.azure.com/${DEVOPS_ORG}/${DEVOPS_PROJECT}/_git/${DEVOPS_REPO}/pullrequest/${prNumber}`;
|
|
227
|
-
|
|
228
|
+
log(`Found PR #${prNumber}: ${prUrl}`);
|
|
228
229
|
|
|
229
230
|
const ids = options.trident ?? [];
|
|
230
231
|
const jiras = options.jira ?? [];
|
|
231
232
|
|
|
232
233
|
const tasks = ids.length > 0 ? await Promise.all(ids.map(fetchTask)) : [];
|
|
233
|
-
tasks.forEach((t) =>
|
|
234
|
+
tasks.forEach((t) => log(`Task: ${t.name}`));
|
|
234
235
|
|
|
235
236
|
const newLinks = [
|
|
236
237
|
...ids.map((id) => `${process.env.TRIDENT_URL}/odoo/my-tasks/${id}`),
|
|
@@ -241,20 +242,18 @@ async function autoprAmend(options) {
|
|
|
241
242
|
? `${pr.description}\n${newLinks.join('\n')}`
|
|
242
243
|
: newLinks.join('\n');
|
|
243
244
|
await patchDevopsPrDescription(prNumber, updatedDescription);
|
|
244
|
-
|
|
245
|
+
log('PR description updated');
|
|
245
246
|
}
|
|
246
247
|
|
|
247
248
|
for (let i = 0; i < ids.length; i++) {
|
|
248
|
-
await appendChecklistPrLink(ids[i], tasks[i].x_release_checklist, prUrl
|
|
249
|
+
await appendChecklistPrLink(ids[i], tasks[i].x_release_checklist, prUrl);
|
|
249
250
|
}
|
|
250
|
-
if (ids.length > 0)
|
|
251
|
+
if (ids.length > 0) log('Checklist updated');
|
|
251
252
|
|
|
252
253
|
const content = readFileSync(changelogPath, 'utf-8');
|
|
253
254
|
const existing = findLineByPrNumber(content, prNumber);
|
|
254
255
|
if (!existing) {
|
|
255
|
-
|
|
256
|
-
`Warning: no changelog line found for PR #${prNumber} — skipping changelog update`
|
|
257
|
-
);
|
|
256
|
+
log(`Warning: no changelog line found for PR #${prNumber} — skipping changelog update`);
|
|
258
257
|
return;
|
|
259
258
|
}
|
|
260
259
|
const lines = content.split('\n');
|
|
@@ -264,8 +263,8 @@ async function autoprAmend(options) {
|
|
|
264
263
|
await execGit(['add', 'CHANGELOG.md'], ADDONS_PATH);
|
|
265
264
|
await execGit(['commit', '-m', '[DOC][CHANGELOG] Changelog'], ADDONS_PATH);
|
|
266
265
|
await execGit(['push'], ADDONS_PATH);
|
|
267
|
-
|
|
268
|
-
|
|
266
|
+
log('Changelog updated and pushed');
|
|
267
|
+
log('\nReminder: squash the two changelog commits before merging the PR.');
|
|
269
268
|
}
|
|
270
269
|
|
|
271
270
|
async function autopr(options) {
|
|
@@ -278,15 +277,15 @@ async function autopr(options) {
|
|
|
278
277
|
const hasTridents = ids.length > 0;
|
|
279
278
|
|
|
280
279
|
const tasks = hasTridents ? await Promise.all(ids.map((id) => fetchTask(id))) : [];
|
|
281
|
-
tasks.forEach((t) =>
|
|
280
|
+
tasks.forEach((t) => log(`Task: ${t.name}`));
|
|
282
281
|
|
|
283
282
|
const content = readFileSync(changelogPath, 'utf-8');
|
|
284
283
|
|
|
285
|
-
const duplicate =
|
|
284
|
+
const duplicate = findDuplicateLine(content, ids, options.jira ?? []);
|
|
286
285
|
|
|
287
286
|
let appendMode = false;
|
|
288
287
|
if (duplicate) {
|
|
289
|
-
|
|
288
|
+
log(`\nExisting entry (line ${duplicate.lineNumber + 1}):\n ${duplicate.line}`);
|
|
290
289
|
const { confirm } = await inquirer.prompt([
|
|
291
290
|
{
|
|
292
291
|
type: 'confirm',
|
|
@@ -315,19 +314,19 @@ async function autopr(options) {
|
|
|
315
314
|
}
|
|
316
315
|
|
|
317
316
|
await execGit(['checkout', '-b', branch], ADDONS_PATH);
|
|
318
|
-
|
|
317
|
+
log(`Branch created: ${branch}`);
|
|
319
318
|
await execGit(['push', '-u', 'origin', branch], ADDONS_PATH);
|
|
320
319
|
|
|
321
320
|
const prTitle = options.name ?? tasks[0]?.name ?? branch;
|
|
322
321
|
const prDescription = buildPrDescription(ids, options.jira ?? []);
|
|
323
322
|
const { id: prNumber, url: prUrl } = await createDevopsPR(branch, prTitle, prDescription);
|
|
324
|
-
|
|
323
|
+
log(`PR opened: #${prNumber} — ${prUrl}`);
|
|
325
324
|
|
|
326
325
|
if (hasTridents) {
|
|
327
326
|
for (let i = 0; i < ids.length; i++) {
|
|
328
|
-
await appendChecklistPrLink(ids[i], tasks[i].x_release_checklist, prUrl
|
|
327
|
+
await appendChecklistPrLink(ids[i], tasks[i].x_release_checklist, prUrl);
|
|
329
328
|
}
|
|
330
|
-
|
|
329
|
+
log('Checklist updated');
|
|
331
330
|
}
|
|
332
331
|
|
|
333
332
|
const lines = content.split('\n');
|
|
@@ -365,12 +364,12 @@ async function autopr(options) {
|
|
|
365
364
|
}
|
|
366
365
|
|
|
367
366
|
await fs.writeFile(changelogPath, lines.join('\n'));
|
|
368
|
-
|
|
367
|
+
log('Changelog entry written');
|
|
369
368
|
|
|
370
369
|
await execGit(['add', 'CHANGELOG.md'], ADDONS_PATH);
|
|
371
370
|
await execGit(['commit', '-m', '[DOC][CHANGELOG] Changelog'], ADDONS_PATH);
|
|
372
371
|
await execGit(['push'], ADDONS_PATH);
|
|
373
|
-
|
|
372
|
+
log('Changelog committed and pushed');
|
|
374
373
|
}
|
|
375
374
|
|
|
376
375
|
export { autopr };
|
|
@@ -4,6 +4,7 @@ import search from '@inquirer/search';
|
|
|
4
4
|
import inquirer from 'inquirer';
|
|
5
5
|
import { resolveAddonsPath } from '../lib/addons.js';
|
|
6
6
|
import { fuzzyMatch } from '../lib/fuzzy.js';
|
|
7
|
+
import { log } from '../lib/logger.js';
|
|
7
8
|
|
|
8
9
|
function buildRefString(tridents, jiras, prNumber) {
|
|
9
10
|
const refs = [];
|
|
@@ -116,7 +117,7 @@ async function changelog(prNumber, options) {
|
|
|
116
117
|
const duplicate = findDuplicateLine(content, tridents, jiras);
|
|
117
118
|
let appendMode = false;
|
|
118
119
|
if (duplicate) {
|
|
119
|
-
|
|
120
|
+
log(`\nExisting entry (line ${duplicate.lineNumber + 1}):\n ${duplicate.line}`);
|
|
120
121
|
const { confirm } = await inquirer.prompt([
|
|
121
122
|
{
|
|
122
123
|
type: 'confirm',
|
|
@@ -132,7 +133,7 @@ async function changelog(prNumber, options) {
|
|
|
132
133
|
const lines = content.split('\n');
|
|
133
134
|
lines[duplicate.lineNumber] = appendPrToLine(lines[duplicate.lineNumber], prNumber);
|
|
134
135
|
await fs.writeFile(changelogPath, lines.join('\n'));
|
|
135
|
-
|
|
136
|
+
log('Updated existing line');
|
|
136
137
|
return;
|
|
137
138
|
}
|
|
138
139
|
|
|
@@ -176,7 +177,7 @@ async function changelog(prNumber, options) {
|
|
|
176
177
|
lines.splice(endLine + 1, 0, newEntry);
|
|
177
178
|
|
|
178
179
|
await fs.writeFile(changelogPath, lines.join('\n'));
|
|
179
|
-
|
|
180
|
+
log('Changelog entry added');
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
function findLineByPrNumber(content, prNumber) {
|
package/src/commands/commit.js
CHANGED
|
@@ -3,6 +3,7 @@ import inquirer from 'inquirer';
|
|
|
3
3
|
import searchList from 'inquirer-search-list';
|
|
4
4
|
import { resolveAddonsPath } from '../lib/addons.js';
|
|
5
5
|
import { execGit } from '../lib/git.js';
|
|
6
|
+
import { log } from '../lib/logger.js';
|
|
6
7
|
|
|
7
8
|
inquirer.registerPrompt('search-list', searchList);
|
|
8
9
|
|
|
@@ -50,7 +51,7 @@ function validateSameModule(files) {
|
|
|
50
51
|
const allSameModule = modules.every((module) => `[${module}]` === currentModule);
|
|
51
52
|
|
|
52
53
|
if (!allSameModule) {
|
|
53
|
-
|
|
54
|
+
log(chalk.red('Selected files are not of the same module'));
|
|
54
55
|
return null;
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -59,8 +60,8 @@ function validateSameModule(files) {
|
|
|
59
60
|
|
|
60
61
|
async function getFilesToCommit(stagedChanges, unstagedChanges) {
|
|
61
62
|
if (stagedChanges.trim()) {
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
log(chalk.green('Staged changes:'));
|
|
64
|
+
log(stagedChanges);
|
|
64
65
|
|
|
65
66
|
return {
|
|
66
67
|
filesToCheck: stagedChanges.trim().split('\n'),
|
|
@@ -71,9 +72,9 @@ async function getFilesToCommit(stagedChanges, unstagedChanges) {
|
|
|
71
72
|
const unstagedFiles = unstagedChanges.trim().split('\n');
|
|
72
73
|
|
|
73
74
|
while (true) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
log(chalk.yellow('No staged changes found.'));
|
|
76
|
+
log(chalk.yellow('Unstaged changes:'));
|
|
77
|
+
log(unstagedChanges);
|
|
77
78
|
|
|
78
79
|
const answers = await inquirer.prompt([
|
|
79
80
|
{
|
|
@@ -104,7 +105,7 @@ async function commit() {
|
|
|
104
105
|
const stagedChanges = await execGit(['diff', '--cached', '--name-only'], ADDONS_PATH);
|
|
105
106
|
|
|
106
107
|
if (!unstagedChanges.trim() && !stagedChanges.trim()) {
|
|
107
|
-
|
|
108
|
+
log(chalk.red('No changes found to commit.'));
|
|
108
109
|
return;
|
|
109
110
|
}
|
|
110
111
|
|
|
@@ -115,7 +116,7 @@ async function commit() {
|
|
|
115
116
|
} = await getFilesToCommit(stagedChanges, unstagedChanges);
|
|
116
117
|
|
|
117
118
|
if (filesToCheck.length === 0) {
|
|
118
|
-
|
|
119
|
+
log(chalk.red('No files selected.'));
|
|
119
120
|
return;
|
|
120
121
|
}
|
|
121
122
|
|
|
@@ -167,13 +168,13 @@ async function commit() {
|
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
await execGit(['commit', '-m', commitMessage], ADDONS_PATH);
|
|
170
|
-
|
|
171
|
+
log(chalk.green('Commit created successfully!'));
|
|
171
172
|
|
|
172
173
|
const remainingUnstaged = await execGit(['diff', '--name-only'], ADDONS_PATH);
|
|
173
174
|
const remainingStaged = await execGit(['diff', '--cached', '--name-only'], ADDONS_PATH);
|
|
174
175
|
|
|
175
176
|
if (!remainingUnstaged.trim() && !remainingStaged.trim()) {
|
|
176
|
-
|
|
177
|
+
log(chalk.green('No more changes to commit.'));
|
|
177
178
|
break;
|
|
178
179
|
}
|
|
179
180
|
|
package/src/commands/export.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { log } from '../lib/logger.js';
|
|
1
2
|
import { exportEmailTemplates } from './exportEmailTemplates.js';
|
|
2
3
|
import { exportImperex } from './exportImperex.js';
|
|
3
4
|
import { exportLrp } from './exportLrp.js';
|
|
@@ -5,7 +6,7 @@ import { exportPb } from './exportPb.js';
|
|
|
5
6
|
import { exportWorkflow } from './exportWorkflow.js';
|
|
6
7
|
|
|
7
8
|
function exportRip() {
|
|
8
|
-
|
|
9
|
+
log('Not implemented yet.');
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export { exportPb, exportRip, exportImperex, exportEmailTemplates, exportWorkflow, exportLrp };
|
package/src/commands/exportPb.js
CHANGED
|
@@ -36,13 +36,30 @@ async function initiateExport(guid, token) {
|
|
|
36
36
|
if (!response.ok) throw new Error(await response.text());
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// No job/request id is returned by initiateExport, so the poll still
|
|
40
|
+
// correlates by GUID + a corrected date window over the last few results —
|
|
41
|
+
// a heuristic, not a stable identifier (see REL-ADP-PB-001; a real fix
|
|
42
|
+
// depends on the ImportExport API exposing a request id, EXT-002). What we
|
|
43
|
+
// can and do own locally is not hanging forever: an overall deadline.
|
|
44
|
+
const PB_EXPORT_POLL_INTERVAL_MS = 3_000;
|
|
45
|
+
const PB_EXPORT_POLL_TIMEOUT_MS = 120_000;
|
|
46
|
+
|
|
39
47
|
async function pollExportResult(guid, requestTime, token) {
|
|
40
48
|
const url = `${process.env.IMPORTEXPORT_URL}/export/info/processKey=ExportElement&subProcess=true&status=FAILED,COMPLETED&referenceId=process_builder`;
|
|
41
49
|
// Server createDate is offset -1hr from system time; subtract 1hr+5s buffer
|
|
42
50
|
const cutoff = requestTime - 3_605_000;
|
|
51
|
+
const deadline = requestTime + PB_EXPORT_POLL_TIMEOUT_MS;
|
|
43
52
|
|
|
44
53
|
while (true) {
|
|
45
|
-
|
|
54
|
+
if (Date.now() > deadline) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`REMOTE_TIMEOUT: Process Builder export for guid ${guid} did not complete within ${
|
|
57
|
+
PB_EXPORT_POLL_TIMEOUT_MS / 1000
|
|
58
|
+
}s`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await new Promise((r) => setTimeout(r, PB_EXPORT_POLL_INTERVAL_MS));
|
|
46
63
|
|
|
47
64
|
const response = await fetch(url, {
|
|
48
65
|
method: 'POST',
|
|
@@ -153,4 +170,4 @@ async function exportPb(opts) {
|
|
|
153
170
|
}
|
|
154
171
|
}
|
|
155
172
|
|
|
156
|
-
export { exportPb };
|
|
173
|
+
export { exportPb, pollExportResult };
|
package/src/commands/init.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
3
|
import { CONFIG_DIR, CONFIG_FILE } from '../config.js';
|
|
4
|
+
import { log } from '../lib/logger.js';
|
|
4
5
|
|
|
5
6
|
async function init() {
|
|
6
7
|
if (!existsSync(CONFIG_DIR)) {
|
|
@@ -142,7 +143,7 @@ async function init() {
|
|
|
142
143
|
.map(([k, v]) => `${k}=${v}`)
|
|
143
144
|
.join('\n') + '\n'
|
|
144
145
|
);
|
|
145
|
-
|
|
146
|
+
log(`Config written to ${CONFIG_FILE}`);
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
export { init };
|
package/src/commands/routine.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { select } from '@inquirer/prompts';
|
|
4
4
|
import { parse } from 'yaml';
|
|
5
5
|
import { CONFIG_DIR } from '../config.js';
|
|
6
|
-
import { setSilent } from '../lib/logger.js';
|
|
6
|
+
import { log, setSilent } from '../lib/logger.js';
|
|
7
7
|
import { exportEmailTemplates } from './exportEmailTemplates.js';
|
|
8
8
|
import { exportImperex } from './exportImperex.js';
|
|
9
9
|
import { exportLrp } from './exportLrp.js';
|
|
@@ -52,39 +52,50 @@ function isNothingToCommit(err) {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
async function runRoutine(routine) {
|
|
55
|
-
|
|
55
|
+
log(`Running Routine: ${routine.name}`);
|
|
56
|
+
|
|
57
|
+
const failures = [];
|
|
56
58
|
|
|
57
59
|
for (const step of routine.steps) {
|
|
58
60
|
const label = `[${step.name}]`;
|
|
59
61
|
const fn = COMMAND_MAP[step.command];
|
|
60
62
|
if (!fn) {
|
|
61
|
-
|
|
63
|
+
log(`${label} Unknown command: ${step.command}`);
|
|
64
|
+
failures.push(`${step.name}: unknown command "${step.command}"`);
|
|
62
65
|
continue;
|
|
63
66
|
}
|
|
64
67
|
|
|
65
|
-
|
|
68
|
+
log(`${label} Job started`);
|
|
66
69
|
setSilent(true);
|
|
67
70
|
|
|
68
71
|
try {
|
|
69
72
|
await fn(stepOpts(step));
|
|
70
|
-
|
|
73
|
+
log(`${label} Done (committed)`);
|
|
71
74
|
} catch (err) {
|
|
72
75
|
if (isNothingToCommit(err)) {
|
|
73
|
-
|
|
76
|
+
log(`${label} Done (nothing to commit)`);
|
|
74
77
|
} else {
|
|
75
|
-
|
|
78
|
+
log(`${label} Failed: ${err.message}`);
|
|
79
|
+
failures.push(`${step.name}: ${err.message}`);
|
|
76
80
|
}
|
|
77
81
|
} finally {
|
|
78
82
|
setSilent(false);
|
|
79
83
|
}
|
|
80
84
|
}
|
|
85
|
+
|
|
86
|
+
if (failures.length) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Routine "${routine.name}" completed with ${failures.length} failed step(s):\n` +
|
|
89
|
+
failures.map((f) => ` - ${f}`).join('\n')
|
|
90
|
+
);
|
|
91
|
+
}
|
|
81
92
|
}
|
|
82
93
|
|
|
83
94
|
async function routine() {
|
|
84
95
|
const routines = loadRoutines();
|
|
85
96
|
|
|
86
97
|
if (!routines.length) {
|
|
87
|
-
|
|
98
|
+
log(
|
|
88
99
|
'No routines defined. Create ~/.config/prbot/routines.yaml or add routines: to agrippa.yaml.'
|
|
89
100
|
);
|
|
90
101
|
return;
|