@waron97/prbot 3.2.0 → 3.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/README.md +26 -18
- package/agrippa-pb.md +172 -74
- package/package.json +1 -1
- package/src/agrippa/commands/clone.js +7 -1
- package/src/agrippa/commands/cloneLrp.js +114 -0
- package/src/agrippa/commands/diff.js +23 -10
- package/src/agrippa/commands/initPhase.js +44 -34
- package/src/agrippa/commands/pb.js +61 -16
- package/src/agrippa/commands/pull.js +93 -17
- package/src/agrippa/commands/pullLrp.js +55 -0
- package/src/agrippa/commands/push.js +88 -19
- package/src/agrippa/commands/pushLrp.js +56 -0
- package/src/agrippa/index.js +32 -17
- package/src/agrippa/lib/lrpApi.js +153 -0
- package/src/agrippa/lib/pbEdit.js +103 -6
- package/src/agrippa/lib/pbLayout.js +23 -2
- package/src/agrippa/lib/pbModel.js +324 -90
- package/src/agrippa/lib/pbPreview.js +4 -2
- package/src/agrippa/lib/pbProject.js +34 -5
- package/src/agrippa/lib/pbScriptTemplate.js +29 -0
- package/src/commands/autopr.js +1 -1
|
@@ -47,19 +47,30 @@ async function diff(targetArg) {
|
|
|
47
47
|
const token = await getToken();
|
|
48
48
|
|
|
49
49
|
let diffCount = 0;
|
|
50
|
+
const chunks = [];
|
|
50
51
|
|
|
51
52
|
if (codeEntries.length) {
|
|
52
53
|
const remoteCodeMap = await fetchRemoteCode(token, process.env.RIP_URL, codeEntries);
|
|
53
|
-
diffCount
|
|
54
|
+
const { diffCount: c, chunks: cs } = diffCodeEntries(codeEntries, remoteCodeMap);
|
|
55
|
+
diffCount += c;
|
|
56
|
+
chunks.push(...cs);
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
for (const entry of pbEntries) {
|
|
57
|
-
|
|
60
|
+
const { hasDiff, chunk } = await diffPbEntry(token, entry);
|
|
61
|
+
if (hasDiff) {
|
|
62
|
+
diffCount++;
|
|
63
|
+
chunks.push(chunk);
|
|
64
|
+
}
|
|
58
65
|
}
|
|
59
66
|
|
|
60
67
|
if (diffCount === 0) {
|
|
61
68
|
console.log('No differences found — all tracked files match the remote.');
|
|
62
69
|
} else {
|
|
70
|
+
const combined = Buffer.concat(chunks);
|
|
71
|
+
const pager = process.env.PAGER || 'less';
|
|
72
|
+
const pagerArgs = pager === 'less' ? ['-R', '-F'] : [];
|
|
73
|
+
spawnSync(pager, pagerArgs, { input: combined, stdio: ['pipe', 'inherit', 'inherit'] });
|
|
63
74
|
console.log(`\n${diffCount} file(s) differ from remote.`);
|
|
64
75
|
}
|
|
65
76
|
}
|
|
@@ -68,6 +79,7 @@ async function diff(targetArg) {
|
|
|
68
79
|
// hand both sides to `git diff --no-index`.
|
|
69
80
|
function diffCodeEntries(entries, remoteCodeMap) {
|
|
70
81
|
let diffCount = 0;
|
|
82
|
+
const chunks = [];
|
|
71
83
|
const tmpFiles = [];
|
|
72
84
|
try {
|
|
73
85
|
for (const entry of entries) {
|
|
@@ -86,17 +98,17 @@ function diffCodeEntries(entries, remoteCodeMap) {
|
|
|
86
98
|
writeFileSync(tmpPath, (remoteCode ?? '').trim() + '\n', 'utf-8');
|
|
87
99
|
tmpFiles.push(tmpPath);
|
|
88
100
|
|
|
89
|
-
console.log(`\n=== ${entry.path} [${entry.name}] ===`);
|
|
90
|
-
|
|
91
101
|
const result = spawnSync(
|
|
92
102
|
'git',
|
|
93
103
|
['diff', '--no-index', '--color=always', tmpPath, entry.path],
|
|
94
|
-
{ stdio: ['ignore', '
|
|
104
|
+
{ stdio: ['ignore', 'pipe', 'pipe'] }
|
|
95
105
|
);
|
|
96
106
|
// exit code 1 means differences found (normal), 0 means identical, >1 means error
|
|
97
107
|
if (result.status !== null && result.status > 1) {
|
|
98
108
|
console.error(`git diff failed for ${entry.path}`);
|
|
99
109
|
}
|
|
110
|
+
const header = Buffer.from(`\n=== ${entry.path} [${entry.name}] ===\n`);
|
|
111
|
+
chunks.push(Buffer.concat([header, result.stdout ?? Buffer.alloc(0)]));
|
|
100
112
|
diffCount++;
|
|
101
113
|
}
|
|
102
114
|
} finally {
|
|
@@ -108,7 +120,7 @@ function diffCodeEntries(entries, remoteCodeMap) {
|
|
|
108
120
|
}
|
|
109
121
|
}
|
|
110
122
|
}
|
|
111
|
-
return diffCount;
|
|
123
|
+
return { diffCount, chunks };
|
|
112
124
|
}
|
|
113
125
|
|
|
114
126
|
// Process-builder wizard: decompose the upstream payload into a throwaway
|
|
@@ -136,18 +148,19 @@ async function diffPbEntry(token, entry) {
|
|
|
136
148
|
// and never matches the raw upstream xml byte-for-byte even when nothing
|
|
137
149
|
// structural changed, so it'd otherwise look like a permanent false diff.
|
|
138
150
|
if (localChecksum(projectReader(upstreamDir)) === localChecksum(projectReader(localDir)))
|
|
139
|
-
return false;
|
|
151
|
+
return { hasDiff: false };
|
|
140
152
|
|
|
141
|
-
console.log(`\n=== ${entry.path} [${entry.name}] (process-builder) ===`);
|
|
142
153
|
const result = spawnSync(
|
|
143
154
|
'git',
|
|
144
155
|
['diff', '--no-index', '--color=always', 'upstream', 'local'],
|
|
145
|
-
{ cwd: tmpRoot, stdio: ['ignore', '
|
|
156
|
+
{ cwd: tmpRoot, stdio: ['ignore', 'pipe', 'pipe'] }
|
|
146
157
|
);
|
|
147
158
|
if (result.status !== null && result.status > 1) {
|
|
148
159
|
console.error(`git diff failed for ${entry.path}`);
|
|
149
160
|
}
|
|
150
|
-
|
|
161
|
+
const header = Buffer.from(`\n=== ${entry.path} [${entry.name}] (process-builder) ===\n`);
|
|
162
|
+
const chunk = Buffer.concat([header, result.stdout ?? Buffer.alloc(0)]);
|
|
163
|
+
return { hasDiff: true, chunk };
|
|
151
164
|
} finally {
|
|
152
165
|
rmSync(tmpRoot, { recursive: true, force: true });
|
|
153
166
|
}
|
|
@@ -100,27 +100,26 @@ async function initPhase() {
|
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
const { selectedPhases } = await inquirer.prompt([
|
|
104
|
+
{
|
|
105
|
+
type: 'checkbox',
|
|
106
|
+
name: 'selectedPhases',
|
|
107
|
+
message: 'Select phases to initialize:',
|
|
108
|
+
choices: phases.map((p) => ({ name: p.name, value: p })),
|
|
109
|
+
loop: false,
|
|
108
110
|
},
|
|
109
|
-
|
|
111
|
+
]);
|
|
110
112
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
results = await getPhaseResults(token, ripUrl, results);
|
|
113
|
+
if (!selectedPhases.length) {
|
|
114
|
+
console.log('No phases selected. Aborted.');
|
|
115
|
+
return;
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
const code = generateCode(results);
|
|
118
|
-
|
|
119
118
|
const { confirm } = await inquirer.prompt([
|
|
120
119
|
{
|
|
121
120
|
type: 'confirm',
|
|
122
121
|
name: 'confirm',
|
|
123
|
-
message: `Initialize
|
|
122
|
+
message: `Initialize ${selectedPhases.length} phase(s)? This will overwrite existing code.`,
|
|
124
123
|
default: true,
|
|
125
124
|
},
|
|
126
125
|
]);
|
|
@@ -130,29 +129,40 @@ async function initPhase() {
|
|
|
130
129
|
return;
|
|
131
130
|
}
|
|
132
131
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
132
|
+
for (const phase of selectedPhases) {
|
|
133
|
+
// Resolve result objects — API may return IDs or full objects
|
|
134
|
+
let results = phase.allowed_phase_result_ids || [];
|
|
135
|
+
if (results.length > 0 && typeof results[0] === 'number') {
|
|
136
|
+
results = await getPhaseResults(token, ripUrl, results);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const code = generateCode(results);
|
|
140
|
+
|
|
141
|
+
// Delete existing configurator records for this phase
|
|
142
|
+
const existing = await getPhaseConfigurators(token, ripUrl, phase.id);
|
|
143
|
+
for (const cfg of existing) {
|
|
144
|
+
await deleteConfigurator(token, ripUrl, cfg.id);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Update phase code + set_result_automatically
|
|
148
|
+
await initPhaseRemote(token, ripUrl, phase.id, code);
|
|
149
|
+
|
|
150
|
+
// Create new configurator records for each result
|
|
151
|
+
const vars = results.map((_, i) => `RES${i + 1}`);
|
|
152
|
+
for (let i = 0; i < results.length; i++) {
|
|
153
|
+
await createConfigurator(token, ripUrl, {
|
|
154
|
+
result_value: vars[i],
|
|
155
|
+
code_phase_id: phase.id,
|
|
156
|
+
triplet_phase_result_id: results[i].id,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log(`Initialized phase "${phase.name}".`);
|
|
161
|
+
if (results.length > 0) {
|
|
162
|
+
console.log(` ${results.length} configurator(s) created: ${vars.join(', ')}`);
|
|
163
|
+
}
|
|
137
164
|
}
|
|
138
165
|
|
|
139
|
-
// Update phase code + set_result_automatically
|
|
140
|
-
await initPhaseRemote(token, ripUrl, phase.id, code);
|
|
141
|
-
|
|
142
|
-
// Create new configurator records for each result
|
|
143
|
-
const vars = results.map((_, i) => `RES${i + 1}`);
|
|
144
|
-
for (let i = 0; i < results.length; i++) {
|
|
145
|
-
await createConfigurator(token, ripUrl, {
|
|
146
|
-
result_value: vars[i],
|
|
147
|
-
code_phase_id: phase.id,
|
|
148
|
-
triplet_phase_result_id: results[i].id,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
console.log(`Initialized phase "${phase.name}".`);
|
|
153
|
-
if (results.length > 0) {
|
|
154
|
-
console.log(` ${results.length} configurator(s) created: ${vars.join(', ')}`);
|
|
155
|
-
}
|
|
156
166
|
console.log(
|
|
157
167
|
`Run 'agrippa pull' in workspaces that track this workflow to fetch the updated code.`
|
|
158
168
|
);
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
connect,
|
|
26
26
|
disconnect,
|
|
27
27
|
eachNode,
|
|
28
|
-
|
|
28
|
+
lintAll,
|
|
29
29
|
listGraph,
|
|
30
30
|
removeNode,
|
|
31
31
|
setDefault,
|
|
@@ -37,32 +37,49 @@ import { projectReader } from '../lib/pbWorkspace.js';
|
|
|
37
37
|
|
|
38
38
|
// ---------- project resolution ----------
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
// Resolves against both process-builder wizards and long-running processes —
|
|
41
|
+
// `structure.yaml`-level editing is identical for both (see pbEdit.js). PBs
|
|
42
|
+
// select by --pb/--name matched against document_id; LRPs have no document_id
|
|
43
|
+
// so the same flag matches against name instead.
|
|
44
|
+
async function resolveProjectEntry(opts) {
|
|
41
45
|
const config = readConfig();
|
|
42
|
-
const entries = (config.workspace || []).filter(
|
|
46
|
+
const entries = (config.workspace || []).filter(
|
|
47
|
+
(e) => e.object_type === 'process_builder' || e.object_type === 'long_running_process'
|
|
48
|
+
);
|
|
43
49
|
if (!entries.length) {
|
|
44
50
|
throw new Error(
|
|
45
|
-
'No process-builder wizards in this workspace.
|
|
51
|
+
'No process-builder wizards or long-running processes in this workspace. ' +
|
|
52
|
+
'Clone one with `agrippa clone --pb` or `agrippa clone --lrp`.'
|
|
46
53
|
);
|
|
47
54
|
}
|
|
48
55
|
const sel = opts.pb || opts.name;
|
|
49
56
|
if (sel) {
|
|
50
|
-
const entry = entries.find((e) => e.document_id === sel);
|
|
51
|
-
if (!entry) throw new Error(`No cloned
|
|
52
|
-
return entry
|
|
57
|
+
const entry = entries.find((e) => e.document_id === sel || e.name === sel);
|
|
58
|
+
if (!entry) throw new Error(`No cloned project with document_id/name "${sel}"`);
|
|
59
|
+
return entry;
|
|
53
60
|
}
|
|
54
|
-
if (entries.length === 1) return entries[0]
|
|
61
|
+
if (entries.length === 1) return entries[0];
|
|
55
62
|
const entry = await search({
|
|
56
|
-
message: 'Select a cloned
|
|
63
|
+
message: 'Select a cloned project:',
|
|
57
64
|
source: (input) => {
|
|
58
65
|
const list = input
|
|
59
66
|
? entries.filter(
|
|
60
|
-
(e) =>
|
|
67
|
+
(e) =>
|
|
68
|
+
fuzzyMatch(e.name, input) ||
|
|
69
|
+
(e.document_id && fuzzyMatch(e.document_id, input))
|
|
61
70
|
)
|
|
62
71
|
: entries;
|
|
63
|
-
return list.map((e) => ({
|
|
72
|
+
return list.map((e) => ({
|
|
73
|
+
name: e.document_id ? `${e.name} (${e.document_id})` : e.name,
|
|
74
|
+
value: e,
|
|
75
|
+
}));
|
|
64
76
|
},
|
|
65
77
|
});
|
|
78
|
+
return entry;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function resolveProjectPath(opts) {
|
|
82
|
+
const entry = await resolveProjectEntry(opts);
|
|
66
83
|
return entry.path;
|
|
67
84
|
}
|
|
68
85
|
|
|
@@ -125,9 +142,9 @@ async function pbFormat(opts) {
|
|
|
125
142
|
console.log(
|
|
126
143
|
`Formatted ${dir} (${nodes} node(s) laid out${missing ? `, ${missing} without layout` : ''}).`
|
|
127
144
|
);
|
|
128
|
-
const issues =
|
|
145
|
+
const issues = lintAll(structure);
|
|
129
146
|
if (issues.length) {
|
|
130
|
-
console.warn('
|
|
147
|
+
console.warn('Diagram issues:');
|
|
131
148
|
for (const w of issues) console.warn(` ! ${w}`);
|
|
132
149
|
}
|
|
133
150
|
}
|
|
@@ -135,14 +152,20 @@ async function pbFormat(opts) {
|
|
|
135
152
|
async function pbAdd(opts) {
|
|
136
153
|
if (!opts.type)
|
|
137
154
|
throw new Error(
|
|
138
|
-
'--type is required (e.g. scriptTask, serviceTask, userTask, exclusiveGateway, subProcess, endEvent...)'
|
|
155
|
+
'--type is required (e.g. scriptTask, serviceTask, userTask, exclusiveGateway, subProcess, endEvent, callActivity...)'
|
|
139
156
|
);
|
|
140
157
|
if ((opts.from || opts.to) && !(opts.from && opts.to))
|
|
141
158
|
throw new Error('--from and --to must be used together');
|
|
142
159
|
if (opts.from && opts.parent)
|
|
143
160
|
throw new Error('--parent is implied by --from/--to; pass only one');
|
|
144
161
|
|
|
145
|
-
const
|
|
162
|
+
const projectEntry = await resolveProjectEntry(opts);
|
|
163
|
+
if (opts.type === 'userTask' && projectEntry.object_type === 'long_running_process') {
|
|
164
|
+
throw new Error(
|
|
165
|
+
'userTask is not valid on a long-running process — LRPs have no pages (user tasks are a process-builder-only concept).'
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
const dir = projectEntry.path;
|
|
146
169
|
const { structure, manifest } = loadProject(dir);
|
|
147
170
|
const ctx = { existingScripts: listScriptFiles(dir), documentId: manifest.document_id };
|
|
148
171
|
|
|
@@ -276,4 +299,26 @@ async function pbPreview(opts) {
|
|
|
276
299
|
console.log(`Wrote ${out} (${svg.length} bytes).`);
|
|
277
300
|
}
|
|
278
301
|
|
|
279
|
-
|
|
302
|
+
async function pbLint(opts) {
|
|
303
|
+
const dir = await resolveProjectPath(opts);
|
|
304
|
+
const { structure } = loadProject(dir);
|
|
305
|
+
const issues = lintAll(structure);
|
|
306
|
+
if (!issues.length) {
|
|
307
|
+
console.log('No issues.');
|
|
308
|
+
} else {
|
|
309
|
+
for (const w of issues) console.warn(` ! ${w}`);
|
|
310
|
+
process.exitCode = 1;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export {
|
|
315
|
+
pbFormat,
|
|
316
|
+
pbAdd,
|
|
317
|
+
pbRemove,
|
|
318
|
+
pbConnect,
|
|
319
|
+
pbDisconnect,
|
|
320
|
+
pbSetDefault,
|
|
321
|
+
pbList,
|
|
322
|
+
pbPreview,
|
|
323
|
+
pbLint,
|
|
324
|
+
};
|
|
@@ -4,8 +4,9 @@ import { getToken } from '../../lib/auth.js';
|
|
|
4
4
|
import { describeWorkflow, getPhasesByIds, getPhasesByWorkflow, listMfas } from '../lib/api.js';
|
|
5
5
|
import { computeChecksum } from '../lib/checksum.js';
|
|
6
6
|
import { loadEffectiveEnv, readConfig, writeConfig } from '../lib/config.js';
|
|
7
|
+
import { fetchUpstream } from '../lib/lrpApi.js';
|
|
7
8
|
import { getProcess } from '../lib/pbApi.js';
|
|
8
|
-
import { localChecksum } from '../lib/pbProject.js';
|
|
9
|
+
import { localChecksum, remoteChecksumPb } from '../lib/pbProject.js';
|
|
9
10
|
import { projectReader } from '../lib/pbWorkspace.js';
|
|
10
11
|
import {
|
|
11
12
|
fileExists,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
writeCodeFile,
|
|
15
16
|
writeWorkflowDoc,
|
|
16
17
|
} from '../lib/workspace.js';
|
|
18
|
+
import { pullLrpEntry } from './pullLrp.js';
|
|
17
19
|
import { pullPbEntry } from './pullPb.js';
|
|
18
20
|
|
|
19
21
|
async function pull() {
|
|
@@ -50,9 +52,11 @@ async function pull() {
|
|
|
50
52
|
const changedWorkflowIds = new Set();
|
|
51
53
|
|
|
52
54
|
// ── pull existing tracked entries ─────────────────────────────────────────
|
|
53
|
-
// process_builder wizards are refreshed separately (different
|
|
54
|
-
// see pullPbEntries below.
|
|
55
|
-
const pullable = config.workspace.filter(
|
|
55
|
+
// process_builder wizards and LRPs are refreshed separately (different
|
|
56
|
+
// checksum/identity model); see pullPbEntries/pullLrpEntries below.
|
|
57
|
+
const pullable = config.workspace.filter(
|
|
58
|
+
(e) => e.object_type !== 'process_builder' && e.object_type !== 'long_running_process'
|
|
59
|
+
);
|
|
56
60
|
if (pullable.length) {
|
|
57
61
|
console.log('Fetching remote code...');
|
|
58
62
|
const remoteCodeMap = await fetchRemoteCode(token, ripUrl, pullable);
|
|
@@ -63,13 +67,13 @@ async function pull() {
|
|
|
63
67
|
const localCode = readCodeFile(entry.path);
|
|
64
68
|
|
|
65
69
|
const remoteChecksum = computeChecksum(remoteCode ?? '');
|
|
66
|
-
const
|
|
70
|
+
const localChecksumVal = computeChecksum(localCode ?? '');
|
|
67
71
|
const pullChecksum = entry.checksum_at_pull;
|
|
68
72
|
|
|
69
73
|
let status;
|
|
70
|
-
if (
|
|
74
|
+
if (localChecksumVal === remoteChecksum) {
|
|
71
75
|
status = 'unchanged';
|
|
72
|
-
} else if (pullChecksum ===
|
|
76
|
+
} else if (pullChecksum === localChecksumVal) {
|
|
73
77
|
status = 'fast-forward';
|
|
74
78
|
} else {
|
|
75
79
|
status = 'conflict';
|
|
@@ -111,16 +115,18 @@ async function pull() {
|
|
|
111
115
|
// ── refresh tracked process-builder wizards ───────────────────────────────
|
|
112
116
|
await pullPbEntries(token, config);
|
|
113
117
|
|
|
118
|
+
// ── refresh tracked long-running processes ────────────────────────────────
|
|
119
|
+
await pullLrpEntries(token, config);
|
|
120
|
+
|
|
114
121
|
// ── discover new phases on tracked workflows ──────────────────────────────
|
|
115
122
|
await discoverNewPhases(token, ripUrl, config, changedWorkflowIds);
|
|
116
123
|
}
|
|
117
124
|
|
|
118
125
|
// Refresh tracked wizards from upstream. Pull concern (inverted from push):
|
|
119
|
-
// overwriting *local* edits. Status per entry
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
// conflict remote advanced AND local diverged → overwrite loses local work
|
|
126
|
+
// overwriting *local* edits. Status per entry:
|
|
127
|
+
// unchanged local === remote semantically → nothing to do
|
|
128
|
+
// fast-forward remote changed, local untouched since pull → safe overwrite
|
|
129
|
+
// conflict local diverged from checksum_at_pull → overwrite loses local work
|
|
124
130
|
async function pullPbEntries(token, config) {
|
|
125
131
|
const entries = config.workspace.filter((e) => e.object_type === 'process_builder');
|
|
126
132
|
if (!entries.length) return;
|
|
@@ -140,12 +146,13 @@ async function pullPbEntries(token, config) {
|
|
|
140
146
|
console.warn(` ${entry.name}: could not fetch upstream, skipping`);
|
|
141
147
|
continue;
|
|
142
148
|
}
|
|
143
|
-
const
|
|
144
|
-
const
|
|
145
|
-
const
|
|
149
|
+
const localSemantic = localChecksum(projectReader(entry.path));
|
|
150
|
+
const remoteSemantic = remoteChecksumPb(upstream);
|
|
151
|
+
const pullChecksum = entry.checksum_at_pull;
|
|
146
152
|
let status;
|
|
147
|
-
if (
|
|
148
|
-
else
|
|
153
|
+
if (localSemantic === remoteSemantic) status = 'unchanged';
|
|
154
|
+
else if (pullChecksum === localSemantic) status = 'fast-forward';
|
|
155
|
+
else status = 'conflict';
|
|
149
156
|
classified.push({ ...entry, upstream, status });
|
|
150
157
|
}
|
|
151
158
|
|
|
@@ -179,6 +186,75 @@ async function pullPbEntries(token, config) {
|
|
|
179
186
|
console.log(`\nPulled ${selected.length} wizard(s). Local backups in .backup/${backupTs}/`);
|
|
180
187
|
}
|
|
181
188
|
|
|
189
|
+
// Refresh tracked long-running processes from upstream. Same classification
|
|
190
|
+
// concern as pullPbEntries, but entries are located by NAME (the tabulator id
|
|
191
|
+
// changes on every save/version — never a stable key).
|
|
192
|
+
async function pullLrpEntries(token, config) {
|
|
193
|
+
const entries = config.workspace.filter((e) => e.object_type === 'long_running_process');
|
|
194
|
+
if (!entries.length) return;
|
|
195
|
+
if (!process.env.IMPORTEXPORT_URL)
|
|
196
|
+
throw new Error(
|
|
197
|
+
'IMPORTEXPORT_URL is not configured. Run `prbot init` or set it in agrippa.yaml.'
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
console.log('Checking long-running processes...');
|
|
201
|
+
const classified = [];
|
|
202
|
+
for (const entry of entries) {
|
|
203
|
+
let upstream = null;
|
|
204
|
+
try {
|
|
205
|
+
upstream = await fetchUpstream(token, entry.name);
|
|
206
|
+
} catch {
|
|
207
|
+
upstream = null;
|
|
208
|
+
}
|
|
209
|
+
if (!upstream) {
|
|
210
|
+
console.warn(` ${entry.name}: could not fetch upstream, skipping`);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const localSemantic = localChecksum(projectReader(entry.path));
|
|
214
|
+
const remoteSemantic = remoteChecksumPb(upstream.payload);
|
|
215
|
+
const pullChecksum = entry.checksum_at_pull;
|
|
216
|
+
let status;
|
|
217
|
+
if (localSemantic === remoteSemantic) status = 'unchanged';
|
|
218
|
+
else if (pullChecksum === localSemantic) status = 'fast-forward';
|
|
219
|
+
else status = 'conflict';
|
|
220
|
+
classified.push({ ...entry, upstream, status });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const changed = classified.filter((e) => e.status !== 'unchanged');
|
|
224
|
+
if (!changed.length) {
|
|
225
|
+
console.log('Long-running processes are up to date.');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const selected = await selectEntries(changed, 'pull (overwrites local LRP files)');
|
|
230
|
+
if (!selected.length) {
|
|
231
|
+
console.log('No long-running processes selected.');
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const backupTs = new Date().toISOString().replace(/:/g, '-').replace('T', '_').slice(0, 19);
|
|
236
|
+
for (const entry of selected) {
|
|
237
|
+
const res = await pullLrpEntry(token, entry, '.backup', backupTs);
|
|
238
|
+
const idx = config.workspace.findIndex(
|
|
239
|
+
(e) => e.object_type === 'long_running_process' && e.name === entry.name
|
|
240
|
+
);
|
|
241
|
+
if (idx !== -1) {
|
|
242
|
+
config.workspace[idx].checksum_at_pull = res.newChecksum;
|
|
243
|
+
config.workspace[idx].tenant_id = res.newRow.tenantId;
|
|
244
|
+
config.workspace[idx].svg = res.newRow.bpmnFileSvg;
|
|
245
|
+
config.workspace[idx].description = res.newRow.description;
|
|
246
|
+
config.workspace[idx].version = res.newRow.version;
|
|
247
|
+
config.workspace[idx].status = res.newRow.status;
|
|
248
|
+
}
|
|
249
|
+
const note = res.diffs.length ? ` (WARNING: ${res.diffs.length} round-trip diff(s))` : '';
|
|
250
|
+
console.log(` ${entry.name} → refreshed${note}`);
|
|
251
|
+
}
|
|
252
|
+
writeConfig(config);
|
|
253
|
+
console.log(
|
|
254
|
+
`\nPulled ${selected.length} long-running process(es). Local backups in .backup/${backupTs}/`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
182
258
|
async function discoverNewPhases(token, ripUrl, config, changedWorkflowIds = new Set()) {
|
|
183
259
|
// Build map of workflow_id → { workflow_name, basePath } from existing phase entries
|
|
184
260
|
const workflows = new Map();
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from 'fs';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { computeChecksum } from '../lib/checksum.js';
|
|
4
|
+
import { fetchUpstream } from '../lib/lrpApi.js';
|
|
5
|
+
import { comparePayload, decompose, recompose, stableStringify } from '../lib/pbProject.js';
|
|
6
|
+
import { projectReader, writeProject } from '../lib/pbWorkspace.js';
|
|
7
|
+
|
|
8
|
+
// Delete files under <baseDir>/scripts that are not in the fresh decompose
|
|
9
|
+
// map, so a refresh that renames/removes scripts doesn't leave orphans behind
|
|
10
|
+
// (LRPs have no pages/ dir — no user tasks).
|
|
11
|
+
function pruneOrphans(baseDir, files) {
|
|
12
|
+
const dir = join(baseDir, 'scripts');
|
|
13
|
+
if (!existsSync(dir)) return;
|
|
14
|
+
for (const f of readdirSync(dir)) {
|
|
15
|
+
if (!(`scripts/${f}` in files)) rmSync(join(dir, f));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Refresh one cloned LRP from upstream (the pull counterpart of
|
|
20
|
+
// pushLrpEntry): back up the current local state, re-decompose the upstream
|
|
21
|
+
// XML, overwrite local files (pruning orphans), and verify the round-trip.
|
|
22
|
+
// `entry.upstream` is the {row, payload} fetched during classification;
|
|
23
|
+
// re-resolved by name if absent (id may have changed since the last pull).
|
|
24
|
+
async function pullLrpEntry(token, entry, backupDir, backupTs) {
|
|
25
|
+
const upstream = entry.upstream ?? (await fetchUpstream(token, entry.name));
|
|
26
|
+
if (!upstream) throw new Error(`could not fetch upstream LRP "${entry.name}"`);
|
|
27
|
+
|
|
28
|
+
// 1. Backup the current local state (recomposed payload) before overwriting.
|
|
29
|
+
try {
|
|
30
|
+
const localPayload = recompose(projectReader(entry.path));
|
|
31
|
+
const backupPath = join(backupDir, backupTs, entry.path, 'local.json');
|
|
32
|
+
mkdirSync(dirname(backupPath), { recursive: true });
|
|
33
|
+
writeFileSync(backupPath, JSON.stringify(localPayload, null, 2), 'utf-8');
|
|
34
|
+
} catch {
|
|
35
|
+
// local files unreadable (e.g. mid-edit) — nothing safe to back up, proceed.
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 2. Decompose upstream → fresh file map; prune orphans; write.
|
|
39
|
+
const { files } = decompose(upstream.payload);
|
|
40
|
+
pruneOrphans(entry.path, files);
|
|
41
|
+
writeProject(entry.path, files);
|
|
42
|
+
|
|
43
|
+
// 3. Verify the freshly-written project reconstructs the upstream XML.
|
|
44
|
+
// (pages diff is expected noise for LRPs — see cloneLrp.js.)
|
|
45
|
+
const rebuilt = recompose(projectReader(entry.path));
|
|
46
|
+
const diffs = comparePayload(upstream.payload, rebuilt).filter((d) => !d.startsWith('pages:'));
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
newChecksum: computeChecksum(stableStringify(rebuilt)),
|
|
50
|
+
newRow: upstream.row,
|
|
51
|
+
diffs,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { pullLrpEntry };
|