@waron97/prbot 3.2.1 → 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 +141 -68
- 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/initPhase.js +44 -34
- package/src/agrippa/commands/pb.js +46 -13
- package/src/agrippa/commands/pull.js +79 -3
- package/src/agrippa/commands/pullLrp.js +55 -0
- package/src/agrippa/commands/push.js +81 -11
- package/src/agrippa/commands/pushLrp.js +56 -0
- package/src/agrippa/index.js +28 -19
- package/src/agrippa/lib/lrpApi.js +153 -0
- package/src/agrippa/lib/pbEdit.js +31 -10
- package/src/agrippa/lib/pbModel.js +317 -86
- package/src/agrippa/lib/pbProject.js +22 -4
- package/src/agrippa/lib/pbScriptTemplate.js +29 -0
- package/src/commands/autopr.js +1 -1
|
@@ -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
|
);
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -288,4 +311,14 @@ async function pbLint(opts) {
|
|
|
288
311
|
}
|
|
289
312
|
}
|
|
290
313
|
|
|
291
|
-
export {
|
|
314
|
+
export {
|
|
315
|
+
pbFormat,
|
|
316
|
+
pbAdd,
|
|
317
|
+
pbRemove,
|
|
318
|
+
pbConnect,
|
|
319
|
+
pbDisconnect,
|
|
320
|
+
pbSetDefault,
|
|
321
|
+
pbList,
|
|
322
|
+
pbPreview,
|
|
323
|
+
pbLint,
|
|
324
|
+
};
|
|
@@ -4,6 +4,7 @@ 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
9
|
import { localChecksum, remoteChecksumPb } from '../lib/pbProject.js';
|
|
9
10
|
import { projectReader } from '../lib/pbWorkspace.js';
|
|
@@ -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);
|
|
@@ -111,6 +115,9 @@ 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
|
}
|
|
@@ -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 };
|
|
@@ -5,11 +5,13 @@ import { getToken } from '../../lib/auth.js';
|
|
|
5
5
|
import { updateMfa, updatePhase } from '../lib/api.js';
|
|
6
6
|
import { computeChecksum } from '../lib/checksum.js';
|
|
7
7
|
import { loadEffectiveEnv, readConfig, writeConfig } from '../lib/config.js';
|
|
8
|
+
import { fetchUpstream } from '../lib/lrpApi.js';
|
|
8
9
|
import { getProcess } from '../lib/pbApi.js';
|
|
9
10
|
import { localChecksum, remoteChecksumPb } from '../lib/pbProject.js';
|
|
10
11
|
import { projectReader } from '../lib/pbWorkspace.js';
|
|
11
12
|
import { fileExists, readCodeFile } from '../lib/workspace.js';
|
|
12
13
|
import { fetchRemoteCode, selectEntries } from './pull.js';
|
|
14
|
+
import { deploy, pushLrpEntry } from './pushLrp.js';
|
|
13
15
|
import { publish, pushPbEntry } from './pushPb.js';
|
|
14
16
|
|
|
15
17
|
const BACKUP_DIR = '.backup';
|
|
@@ -46,11 +48,16 @@ async function push(opts = {}) {
|
|
|
46
48
|
(e) => e.object_type === 'phase' || e.object_type === 'mfa'
|
|
47
49
|
);
|
|
48
50
|
const hasPb = config.workspace.some((e) => e.object_type === 'process_builder');
|
|
51
|
+
const hasLrp = config.workspace.some((e) => e.object_type === 'long_running_process');
|
|
49
52
|
const ripUrl = process.env.RIP_URL;
|
|
50
53
|
if (hasCode && !ripUrl)
|
|
51
54
|
throw new Error('RIP_URL is not configured. Run `prbot init` or set it in agrippa.yaml.');
|
|
52
55
|
if (hasPb && !process.env.PB_URL)
|
|
53
56
|
throw new Error('PB_URL is not configured. Run `prbot init` or set it in agrippa.yaml.');
|
|
57
|
+
if (hasLrp && !process.env.IMPORTEXPORT_URL)
|
|
58
|
+
throw new Error(
|
|
59
|
+
'IMPORTEXPORT_URL is not configured. Run `prbot init` or set it in agrippa.yaml.'
|
|
60
|
+
);
|
|
54
61
|
|
|
55
62
|
console.log('Fetching remote state...');
|
|
56
63
|
const token = await getToken();
|
|
@@ -66,6 +73,14 @@ async function push(opts = {}) {
|
|
|
66
73
|
upstreamMap.set(e.guid, null);
|
|
67
74
|
}
|
|
68
75
|
}
|
|
76
|
+
const lrpUpstreamMap = new Map();
|
|
77
|
+
for (const e of config.workspace.filter((x) => x.object_type === 'long_running_process')) {
|
|
78
|
+
try {
|
|
79
|
+
lrpUpstreamMap.set(e.name, await fetchUpstream(token, e.name));
|
|
80
|
+
} catch {
|
|
81
|
+
lrpUpstreamMap.set(e.name, null);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
69
84
|
|
|
70
85
|
// Classify every entry to a status badge — concern: overwriting remote work.
|
|
71
86
|
const classified = config.workspace.map((entry) => {
|
|
@@ -81,6 +96,18 @@ async function push(opts = {}) {
|
|
|
81
96
|
return { ...entry, upstream, status };
|
|
82
97
|
}
|
|
83
98
|
|
|
99
|
+
if (entry.object_type === 'long_running_process') {
|
|
100
|
+
const upstream = lrpUpstreamMap.get(entry.name) ?? null;
|
|
101
|
+
const localSemantic = localChecksum(projectReader(entry.path));
|
|
102
|
+
const remoteSemantic = upstream ? remoteChecksumPb(upstream.payload) : null;
|
|
103
|
+
const pullChecksum = entry.checksum_at_pull;
|
|
104
|
+
let status;
|
|
105
|
+
if (localSemantic === remoteSemantic) status = 'unchanged';
|
|
106
|
+
else if (pullChecksum === remoteSemantic) status = 'fast-forward';
|
|
107
|
+
else status = 'conflict';
|
|
108
|
+
return { ...entry, upstream, status };
|
|
109
|
+
}
|
|
110
|
+
|
|
84
111
|
const key = `${entry.object_type}:${entry.id}`;
|
|
85
112
|
const remoteCode = remoteCodeMap.get(key) ?? null;
|
|
86
113
|
const localCode = readCodeFile(entry.path);
|
|
@@ -108,10 +135,14 @@ async function push(opts = {}) {
|
|
|
108
135
|
|
|
109
136
|
const backupTs = new Date().toISOString().replace(/:/g, '-').replace('T', '_').slice(0, 19);
|
|
110
137
|
|
|
111
|
-
// Back up remote code for phase/mfa before overwriting (pb
|
|
112
|
-
// full upstream payload inside pushPbEntry).
|
|
138
|
+
// Back up remote code for phase/mfa before overwriting (pb/lrp back up
|
|
139
|
+
// their own full upstream payload inside pushPbEntry/pushLrpEntry).
|
|
113
140
|
for (const entry of selected) {
|
|
114
|
-
if (
|
|
141
|
+
if (
|
|
142
|
+
entry.object_type !== 'process_builder' &&
|
|
143
|
+
entry.object_type !== 'long_running_process' &&
|
|
144
|
+
entry.remoteCode != null
|
|
145
|
+
) {
|
|
115
146
|
const backupPath = join(BACKUP_DIR, backupTs, entry.path);
|
|
116
147
|
mkdirSync(dirname(backupPath), { recursive: true });
|
|
117
148
|
writeFileSync(backupPath, (entry.remoteCode ?? '').trim() + '\n', 'utf-8');
|
|
@@ -119,15 +150,15 @@ async function push(opts = {}) {
|
|
|
119
150
|
}
|
|
120
151
|
|
|
121
152
|
const pushedPb = [];
|
|
153
|
+
const pushedLrp = [];
|
|
122
154
|
let pushed = 0;
|
|
123
155
|
for (const entry of selected) {
|
|
124
|
-
const idx = config.workspace.findIndex(
|
|
125
|
-
(e)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
);
|
|
156
|
+
const idx = config.workspace.findIndex((e) => {
|
|
157
|
+
if (e.object_type !== entry.object_type) return false;
|
|
158
|
+
if (entry.object_type === 'process_builder') return e.guid === entry.guid;
|
|
159
|
+
if (entry.object_type === 'long_running_process') return e.name === entry.name;
|
|
160
|
+
return e.id === entry.id;
|
|
161
|
+
});
|
|
131
162
|
|
|
132
163
|
if (entry.object_type === 'process_builder') {
|
|
133
164
|
const res = await pushPbEntry(token, entry, BACKUP_DIR, backupTs);
|
|
@@ -144,6 +175,19 @@ async function push(opts = {}) {
|
|
|
144
175
|
config.workspace[idx].status = res.newStatus || 'draft';
|
|
145
176
|
}
|
|
146
177
|
pushedPb.push({ entry, idx });
|
|
178
|
+
} else if (entry.object_type === 'long_running_process') {
|
|
179
|
+
const res = await pushLrpEntry(token, entry, BACKUP_DIR, backupTs);
|
|
180
|
+
console.log(` ${entry.name} → saved`);
|
|
181
|
+
if (idx !== -1) {
|
|
182
|
+
config.workspace[idx].checksum_at_pull = res.newChecksum;
|
|
183
|
+
config.workspace[idx].tenant_id = res.newRow.tenantId;
|
|
184
|
+
config.workspace[idx].description = res.newRow.description;
|
|
185
|
+
config.workspace[idx].version = res.newRow.version;
|
|
186
|
+
config.workspace[idx].status = res.newRow.status;
|
|
187
|
+
}
|
|
188
|
+
// deployId is the fresh post-save id (re-resolved by name in
|
|
189
|
+
// pushLrpEntry); the pre-save id is dead once the save returns.
|
|
190
|
+
pushedLrp.push({ entry, idx, deployId: res.deployId });
|
|
147
191
|
} else {
|
|
148
192
|
const code = entry.localCode ?? '';
|
|
149
193
|
if (entry.object_type === 'phase') await updatePhase(token, ripUrl, entry.id, code);
|
|
@@ -155,10 +199,13 @@ async function push(opts = {}) {
|
|
|
155
199
|
|
|
156
200
|
console.log(`\nRemote backups written to ${BACKUP_DIR}/${backupTs}/`);
|
|
157
201
|
|
|
158
|
-
// Publish step for pushed wizards
|
|
202
|
+
// Publish/deploy step for pushed wizards and LRPs.
|
|
159
203
|
if (pushedPb.length) {
|
|
160
204
|
await handlePublish(token, pushedPb, config, opts);
|
|
161
205
|
}
|
|
206
|
+
if (pushedLrp.length) {
|
|
207
|
+
await handleDeploy(token, pushedLrp, config, opts);
|
|
208
|
+
}
|
|
162
209
|
|
|
163
210
|
writeConfig(config);
|
|
164
211
|
console.log(`\nPushed ${pushed} record(s).`);
|
|
@@ -187,4 +234,27 @@ async function handlePublish(token, pushedPb, config, opts) {
|
|
|
187
234
|
}
|
|
188
235
|
}
|
|
189
236
|
|
|
237
|
+
async function handleDeploy(token, pushedLrp, config, opts) {
|
|
238
|
+
for (const { entry, idx, deployId } of pushedLrp) {
|
|
239
|
+
let doDeploy;
|
|
240
|
+
if (opts.skipPublish) doDeploy = false;
|
|
241
|
+
else if (opts.publish) doDeploy = true;
|
|
242
|
+
else {
|
|
243
|
+
({ doDeploy } = await inquirer.prompt([
|
|
244
|
+
{
|
|
245
|
+
type: 'confirm',
|
|
246
|
+
name: 'doDeploy',
|
|
247
|
+
message: `Deploy "${entry.name}" now?`,
|
|
248
|
+
default: false,
|
|
249
|
+
},
|
|
250
|
+
]));
|
|
251
|
+
}
|
|
252
|
+
if (doDeploy) {
|
|
253
|
+
await deploy(token, deployId);
|
|
254
|
+
if (idx !== -1) config.workspace[idx].status = 'deployed';
|
|
255
|
+
console.log(` deployed ${entry.name}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
190
260
|
export { push };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'fs';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { deployLrp, resolveLrpByName, saveLrp } from '../lib/lrpApi.js';
|
|
4
|
+
import { localChecksum, recompose } from '../lib/pbProject.js';
|
|
5
|
+
import { projectReader } from '../lib/pbWorkspace.js';
|
|
6
|
+
|
|
7
|
+
// Push one LRP entry: back up upstream, re-resolve the CURRENT id/tenantId by
|
|
8
|
+
// name (never trust a stored/stale id — it changes on every save/version),
|
|
9
|
+
// and PATCH the recomposed XML back. Returns a summary; deploy is a separate
|
|
10
|
+
// step (see deploy()), mirroring PB's push-then-publish split.
|
|
11
|
+
//
|
|
12
|
+
// The save bumps the version and mints a NEW id, so the pre-save id is stale
|
|
13
|
+
// the moment saveLrp returns. Both the deploy target and the post-save
|
|
14
|
+
// version/status must come from a fresh resolve-by-name AFTER the save — never
|
|
15
|
+
// from the pre-save row or the PATCH response (which does not echo the new id).
|
|
16
|
+
async function pushLrpEntry(token, entry, backupDir, backupTs) {
|
|
17
|
+
const read = projectReader(entry.path);
|
|
18
|
+
const localPayload = recompose(read);
|
|
19
|
+
const upstream = entry.upstream; // { row, payload } from push.js's classification
|
|
20
|
+
|
|
21
|
+
if (upstream) {
|
|
22
|
+
const backupPath = join(backupDir, backupTs, entry.path, 'upstream.xml');
|
|
23
|
+
mkdirSync(dirname(backupPath), { recursive: true });
|
|
24
|
+
writeFileSync(backupPath, upstream.payload.built_page, 'utf-8');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Re-resolve immediately before saving — the row captured during
|
|
28
|
+
// classification may be stale if something else saved in between.
|
|
29
|
+
const row = await resolveLrpByName(token, entry.name);
|
|
30
|
+
const description = upstream?.row?.description ?? entry.description ?? '';
|
|
31
|
+
const saved = await saveLrp(
|
|
32
|
+
token,
|
|
33
|
+
{ ...row, bpmnFileSvg: entry.svg ?? row.bpmnFileSvg },
|
|
34
|
+
localPayload.built_page,
|
|
35
|
+
description
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// The save minted a new id and bumped the version — re-resolve by name to
|
|
39
|
+
// get the fresh row. deployBpmn must target this new id (the pre-save id is
|
|
40
|
+
// now dead: deploying it 200s but activates nothing), and the workspace
|
|
41
|
+
// entry's version/status must come from here, not the stale pre-save row.
|
|
42
|
+
const deployRow = await resolveLrpByName(token, entry.name);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
newChecksum: localChecksum(read),
|
|
46
|
+
newRow: { ...deployRow, description },
|
|
47
|
+
saved,
|
|
48
|
+
deployId: deployRow.id,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function deploy(token, id) {
|
|
53
|
+
return deployLrp(token, id);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { pushLrpEntry, deploy };
|