@waron97/prbot 3.3.0 → 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.
@@ -1,6 +1,7 @@
1
1
  import { dirname } from 'path';
2
2
  import inquirer from 'inquirer';
3
3
  import { getToken } from '../../lib/auth.js';
4
+ import { log, warn } from '../../lib/logger.js';
4
5
  import { describeWorkflow, getPhasesByIds, getPhasesByWorkflow, listMfas } from '../lib/api.js';
5
6
  import { computeChecksum } from '../lib/checksum.js';
6
7
  import { loadEffectiveEnv, readConfig, writeConfig } from '../lib/config.js';
@@ -18,7 +19,7 @@ import {
18
19
  import { pullLrpEntry } from './pullLrp.js';
19
20
  import { pullPbEntry } from './pullPb.js';
20
21
 
21
- async function pull() {
22
+ async function pull(opts = {}) {
22
23
  const config = readConfig();
23
24
  loadEffectiveEnv(config);
24
25
 
@@ -29,19 +30,23 @@ async function pull() {
29
30
  // Stale-file check
30
31
  const stale = config.workspace.filter((e) => !fileExists(e.path));
31
32
  if (stale.length) {
32
- console.log('\nThe following tracked files no longer exist on disk:');
33
- stale.forEach((e) => console.log(` - ${e.path} (${e.name})`));
34
- const { cleanup } = await inquirer.prompt([
35
- {
36
- type: 'confirm',
37
- name: 'cleanup',
38
- message: 'Remove these entries from the workspace config?',
39
- default: true,
40
- },
41
- ]);
42
- if (cleanup) {
43
- config.workspace = config.workspace.filter((e) => fileExists(e.path));
44
- writeConfig(config);
33
+ log('\nThe following tracked files no longer exist on disk:');
34
+ stale.forEach((e) => log(` - ${e.path} (${e.name})`));
35
+ if (opts.nonInteractive) {
36
+ log(' (non-interactive: leaving them tracked; run interactively to clean up)');
37
+ } else {
38
+ const { cleanup } = await inquirer.prompt([
39
+ {
40
+ type: 'confirm',
41
+ name: 'cleanup',
42
+ message: 'Remove these entries from the workspace config?',
43
+ default: true,
44
+ },
45
+ ]);
46
+ if (cleanup) {
47
+ config.workspace = config.workspace.filter((e) => fileExists(e.path));
48
+ writeConfig(config);
49
+ }
45
50
  }
46
51
  }
47
52
 
@@ -58,7 +63,7 @@ async function pull() {
58
63
  (e) => e.object_type !== 'process_builder' && e.object_type !== 'long_running_process'
59
64
  );
60
65
  if (pullable.length) {
61
- console.log('Fetching remote code...');
66
+ log('Fetching remote code...');
62
67
  const remoteCodeMap = await fetchRemoteCode(token, ripUrl, pullable);
63
68
 
64
69
  const classified = pullable.map((entry) => {
@@ -85,12 +90,12 @@ async function pull() {
85
90
  const changed = classified.filter((e) => e.status !== 'unchanged');
86
91
 
87
92
  if (!changed.length) {
88
- console.log('Everything is up to date.');
93
+ log('Everything is up to date.');
89
94
  } else {
90
- const selected = await selectEntries(changed, 'pull (overwrites local files)');
95
+ const selected = await selectEntries(changed, 'pull (overwrites local files)', opts);
91
96
 
92
97
  if (!selected.length) {
93
- console.log('Nothing selected. No changes made.');
98
+ log('Nothing selected. No changes made.');
94
99
  } else {
95
100
  for (const entry of selected) {
96
101
  writeCodeFile(entry.path, entry.remoteCode);
@@ -105,18 +110,18 @@ async function pull() {
105
110
  }
106
111
  }
107
112
  writeConfig(config);
108
- console.log(`\nPulled ${selected.length} record(s).`);
113
+ log(`\nPulled ${selected.length} record(s).`);
109
114
  }
110
115
  }
111
116
  } else {
112
- console.log('No tracked resources. Run `agrippa clone` first.');
117
+ log('No tracked resources. Run `agrippa clone` first.');
113
118
  }
114
119
 
115
120
  // ── refresh tracked process-builder wizards ───────────────────────────────
116
- await pullPbEntries(token, config);
121
+ await pullPbEntries(token, config, opts);
117
122
 
118
123
  // ── refresh tracked long-running processes ────────────────────────────────
119
- await pullLrpEntries(token, config);
124
+ await pullLrpEntries(token, config, opts);
120
125
 
121
126
  // ── discover new phases on tracked workflows ──────────────────────────────
122
127
  await discoverNewPhases(token, ripUrl, config, changedWorkflowIds);
@@ -127,13 +132,13 @@ async function pull() {
127
132
  // unchanged local === remote semantically → nothing to do
128
133
  // fast-forward remote changed, local untouched since pull → safe overwrite
129
134
  // conflict local diverged from checksum_at_pull → overwrite loses local work
130
- async function pullPbEntries(token, config) {
135
+ async function pullPbEntries(token, config, opts = {}) {
131
136
  const entries = config.workspace.filter((e) => e.object_type === 'process_builder');
132
137
  if (!entries.length) return;
133
138
  if (!process.env.PB_URL)
134
139
  throw new Error('PB_URL is not configured. Run `prbot init` or set it in agrippa.yaml.');
135
140
 
136
- console.log('Checking process-builder wizards...');
141
+ log('Checking process-builder wizards...');
137
142
  const classified = [];
138
143
  for (const entry of entries) {
139
144
  let upstream = null;
@@ -143,7 +148,7 @@ async function pullPbEntries(token, config) {
143
148
  upstream = null;
144
149
  }
145
150
  if (!upstream) {
146
- console.warn(` ${entry.name}: could not fetch upstream, skipping`);
151
+ warn(` ${entry.name}: could not fetch upstream, skipping`);
147
152
  continue;
148
153
  }
149
154
  const localSemantic = localChecksum(projectReader(entry.path));
@@ -158,13 +163,13 @@ async function pullPbEntries(token, config) {
158
163
 
159
164
  const changed = classified.filter((e) => e.status !== 'unchanged');
160
165
  if (!changed.length) {
161
- console.log('Wizards are up to date.');
166
+ log('Wizards are up to date.');
162
167
  return;
163
168
  }
164
169
 
165
- const selected = await selectEntries(changed, 'pull (overwrites local wizard files)');
170
+ const selected = await selectEntries(changed, 'pull (overwrites local wizard files)', opts);
166
171
  if (!selected.length) {
167
- console.log('No wizards selected.');
172
+ log('No wizards selected.');
168
173
  return;
169
174
  }
170
175
 
@@ -180,16 +185,16 @@ async function pullPbEntries(token, config) {
180
185
  if (res.newStatus) config.workspace[idx].status = res.newStatus;
181
186
  }
182
187
  const note = res.diffs.length ? ` (WARNING: ${res.diffs.length} round-trip diff(s))` : '';
183
- console.log(` ${entry.name} → refreshed${note}`);
188
+ log(` ${entry.name} → refreshed${note}`);
184
189
  }
185
190
  writeConfig(config);
186
- console.log(`\nPulled ${selected.length} wizard(s). Local backups in .backup/${backupTs}/`);
191
+ log(`\nPulled ${selected.length} wizard(s). Local backups in .backup/${backupTs}/`);
187
192
  }
188
193
 
189
194
  // Refresh tracked long-running processes from upstream. Same classification
190
195
  // concern as pullPbEntries, but entries are located by NAME (the tabulator id
191
196
  // changes on every save/version — never a stable key).
192
- async function pullLrpEntries(token, config) {
197
+ async function pullLrpEntries(token, config, opts = {}) {
193
198
  const entries = config.workspace.filter((e) => e.object_type === 'long_running_process');
194
199
  if (!entries.length) return;
195
200
  if (!process.env.IMPORTEXPORT_URL)
@@ -197,7 +202,7 @@ async function pullLrpEntries(token, config) {
197
202
  'IMPORTEXPORT_URL is not configured. Run `prbot init` or set it in agrippa.yaml.'
198
203
  );
199
204
 
200
- console.log('Checking long-running processes...');
205
+ log('Checking long-running processes...');
201
206
  const classified = [];
202
207
  for (const entry of entries) {
203
208
  let upstream = null;
@@ -207,7 +212,7 @@ async function pullLrpEntries(token, config) {
207
212
  upstream = null;
208
213
  }
209
214
  if (!upstream) {
210
- console.warn(` ${entry.name}: could not fetch upstream, skipping`);
215
+ warn(` ${entry.name}: could not fetch upstream, skipping`);
211
216
  continue;
212
217
  }
213
218
  const localSemantic = localChecksum(projectReader(entry.path));
@@ -222,13 +227,13 @@ async function pullLrpEntries(token, config) {
222
227
 
223
228
  const changed = classified.filter((e) => e.status !== 'unchanged');
224
229
  if (!changed.length) {
225
- console.log('Long-running processes are up to date.');
230
+ log('Long-running processes are up to date.');
226
231
  return;
227
232
  }
228
233
 
229
- const selected = await selectEntries(changed, 'pull (overwrites local LRP files)');
234
+ const selected = await selectEntries(changed, 'pull (overwrites local LRP files)', opts);
230
235
  if (!selected.length) {
231
- console.log('No long-running processes selected.');
236
+ log('No long-running processes selected.');
232
237
  return;
233
238
  }
234
239
 
@@ -247,10 +252,10 @@ async function pullLrpEntries(token, config) {
247
252
  config.workspace[idx].status = res.newRow.status;
248
253
  }
249
254
  const note = res.diffs.length ? ` (WARNING: ${res.diffs.length} round-trip diff(s))` : '';
250
- console.log(` ${entry.name} → refreshed${note}`);
255
+ log(` ${entry.name} → refreshed${note}`);
251
256
  }
252
257
  writeConfig(config);
253
- console.log(
258
+ log(
254
259
  `\nPulled ${selected.length} long-running process(es). Local backups in .backup/${backupTs}/`
255
260
  );
256
261
  }
@@ -294,7 +299,7 @@ async function discoverNewPhases(token, ripUrl, config, changedWorkflowIds = new
294
299
  checksum_at_pull: computeChecksum(phase.code),
295
300
  name: `${wfName} / ${phase.name}`,
296
301
  });
297
- console.log(` new phase: ${filePath}`);
302
+ log(` new phase: ${filePath}`);
298
303
  newCount++;
299
304
  wfChanged = true;
300
305
  }
@@ -307,14 +312,14 @@ async function discoverNewPhases(token, ripUrl, config, changedWorkflowIds = new
307
312
  const structure = await describeWorkflow(token, ripUrl, wfId);
308
313
  writeWorkflowDoc(basePath, structure);
309
314
  } catch (err) {
310
- console.warn(` could not refresh workflow.yml for ${wfName}: ${err.message}`);
315
+ warn(` could not refresh workflow.yml for ${wfName}: ${err.message}`);
311
316
  }
312
317
  }
313
318
  }
314
319
 
315
320
  if (newCount) {
316
321
  writeConfig(config);
317
- console.log(`Added ${newCount} new phase(s) from tracked workflows.`);
322
+ log(`Added ${newCount} new phase(s) from tracked workflows.`);
318
323
  }
319
324
  }
320
325
 
@@ -343,18 +348,32 @@ async function fetchRemoteCode(token, ripUrl, workspace) {
343
348
  return map;
344
349
  }
345
350
 
346
- async function selectEntries(changed, verb, mode = 'pull') {
347
- const badgeFor = (status) => {
348
- if (mode === 'push') {
349
- return status === 'fast-forward' ? ' safe' : '⚠ conflict';
351
+ // `changed` only ever contains `fast-forward` and `conflict` entries (the
352
+ // caller already filtered out `unchanged`). `conflict` means local and remote
353
+ // have both diverged from the last-known-good baseline — applying it blindly
354
+ // can silently lose either side's work, so it is never preselected and is
355
+ // refused outright in `--non-interactive` mode instead of being silently
356
+ // skipped or applied.
357
+ async function selectEntries(changed, verb, opts = {}) {
358
+ const conflicts = changed.filter((e) => e.status === 'conflict');
359
+
360
+ if (opts.nonInteractive) {
361
+ if (conflicts.length) {
362
+ throw new Error(
363
+ `CONFLICT: refusing to ${verb} non-interactively — ${conflicts.length} resource(s) ` +
364
+ `in conflict: ${conflicts.map((e) => e.name).join(', ')}. Resolve interactively ` +
365
+ `or re-run once the conflicting side is reconciled.`
366
+ );
350
367
  }
351
- return status === 'fast-forward' ? '↑ safe' : '⚠ conflict';
352
- };
368
+ return changed;
369
+ }
370
+
371
+ const badgeFor = (status) => (status === 'fast-forward' ? '↑ safe' : '⚠ conflict');
353
372
 
354
373
  const choices = changed.map((e) => ({
355
374
  name: `${e.name} [${badgeFor(e.status)}]`,
356
375
  value: e,
357
- checked: true,
376
+ checked: e.status !== 'conflict',
358
377
  }));
359
378
 
360
379
  const { selected } = await inquirer.prompt([
@@ -1,8 +1,7 @@
1
1
  import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from 'fs';
2
2
  import { dirname, join } from 'path';
3
- import { computeChecksum } from '../lib/checksum.js';
4
3
  import { fetchUpstream } from '../lib/lrpApi.js';
5
- import { comparePayload, decompose, recompose, stableStringify } from '../lib/pbProject.js';
4
+ import { checksumOfPayload, comparePayload, decompose, recompose } from '../lib/pbProject.js';
6
5
  import { projectReader, writeProject } from '../lib/pbWorkspace.js';
7
6
 
8
7
  // Delete files under <baseDir>/scripts that are not in the fresh decompose
@@ -46,7 +45,7 @@ async function pullLrpEntry(token, entry, backupDir, backupTs) {
46
45
  const diffs = comparePayload(upstream.payload, rebuilt).filter((d) => !d.startsWith('pages:'));
47
46
 
48
47
  return {
49
- newChecksum: computeChecksum(stableStringify(rebuilt)),
48
+ newChecksum: checksumOfPayload(rebuilt),
50
49
  newRow: upstream.row,
51
50
  diffs,
52
51
  };
@@ -1,8 +1,7 @@
1
1
  import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from 'fs';
2
2
  import { dirname, join } from 'path';
3
- import { computeChecksum } from '../lib/checksum.js';
4
3
  import { getProcess } from '../lib/pbApi.js';
5
- import { comparePayload, decompose, recompose, stableStringify } from '../lib/pbProject.js';
4
+ import { checksumOfPayload, comparePayload, decompose, recompose } from '../lib/pbProject.js';
6
5
  import { projectReader, writeProject } from '../lib/pbWorkspace.js';
7
6
 
8
7
  // Delete files under <baseDir>/<sub> that are not in the fresh decompose map, so
@@ -44,7 +43,7 @@ async function pullPbEntry(token, entry, backupDir, backupTs) {
44
43
  const diffs = comparePayload(upstream, rebuilt);
45
44
 
46
45
  return {
47
- newChecksum: computeChecksum(stableStringify(rebuilt)),
46
+ newChecksum: checksumOfPayload(rebuilt),
48
47
  newUpdatedDate: upstream.updated_date,
49
48
  newStatus: upstream.status,
50
49
  diffs,
@@ -2,6 +2,7 @@ import { mkdirSync, writeFileSync } from 'fs';
2
2
  import { dirname, join } from 'path';
3
3
  import inquirer from 'inquirer';
4
4
  import { getToken } from '../../lib/auth.js';
5
+ import { log } from '../../lib/logger.js';
5
6
  import { updateMfa, updatePhase } from '../lib/api.js';
6
7
  import { computeChecksum } from '../lib/checksum.js';
7
8
  import { loadEffectiveEnv, readConfig, writeConfig } from '../lib/config.js';
@@ -23,24 +24,28 @@ async function push(opts = {}) {
23
24
  // Stale-entry check (a path may be a file for phase/mfa or a dir for pb).
24
25
  const stale = config.workspace.filter((e) => !fileExists(e.path));
25
26
  if (stale.length) {
26
- console.log('\nThe following tracked resources no longer exist on disk:');
27
- stale.forEach((e) => console.log(` - ${e.path} (${e.name})`));
28
- const { cleanup } = await inquirer.prompt([
29
- {
30
- type: 'confirm',
31
- name: 'cleanup',
32
- message: 'Remove these entries from the workspace config?',
33
- default: true,
34
- },
35
- ]);
36
- if (cleanup) {
37
- config.workspace = config.workspace.filter((e) => fileExists(e.path));
38
- writeConfig(config);
27
+ log('\nThe following tracked resources no longer exist on disk:');
28
+ stale.forEach((e) => log(` - ${e.path} (${e.name})`));
29
+ if (opts.nonInteractive) {
30
+ log(' (non-interactive: leaving them tracked; run interactively to clean up)');
31
+ } else {
32
+ const { cleanup } = await inquirer.prompt([
33
+ {
34
+ type: 'confirm',
35
+ name: 'cleanup',
36
+ message: 'Remove these entries from the workspace config?',
37
+ default: true,
38
+ },
39
+ ]);
40
+ if (cleanup) {
41
+ config.workspace = config.workspace.filter((e) => fileExists(e.path));
42
+ writeConfig(config);
43
+ }
39
44
  }
40
45
  }
41
46
 
42
47
  if (!config.workspace.length) {
43
- console.log('No tracked resources. Run `agrippa clone` first.');
48
+ log('No tracked resources. Run `agrippa clone` first.');
44
49
  return;
45
50
  }
46
51
 
@@ -59,7 +64,7 @@ async function push(opts = {}) {
59
64
  'IMPORTEXPORT_URL is not configured. Run `prbot init` or set it in agrippa.yaml.'
60
65
  );
61
66
 
62
- console.log('Fetching remote state...');
67
+ log('Fetching remote state...');
63
68
  const token = await getToken();
64
69
 
65
70
  const remoteCodeMap = hasCode
@@ -123,13 +128,13 @@ async function push(opts = {}) {
123
128
 
124
129
  const changed = classified.filter((e) => e.status !== 'unchanged');
125
130
  if (!changed.length) {
126
- console.log('Nothing to push — everything matches the last-pulled state.');
131
+ log('Nothing to push — everything matches the last-pulled state.');
127
132
  return;
128
133
  }
129
134
 
130
- const selected = await selectEntries(changed, 'push (overwrites remote)', 'push');
135
+ const selected = await selectEntries(changed, 'push (overwrites remote)', opts);
131
136
  if (!selected.length) {
132
- console.log('Nothing selected. No changes made.');
137
+ log('Nothing selected. No changes made.');
133
138
  return;
134
139
  }
135
140
 
@@ -168,7 +173,7 @@ async function push(opts = {}) {
168
173
  ]
169
174
  .filter(Boolean)
170
175
  .join(', ');
171
- console.log(` ${entry.name} → saved (draft)${note ? ` [${note}]` : ''}`);
176
+ log(` ${entry.name} → saved (draft)${note ? ` [${note}]` : ''}`);
172
177
  if (idx !== -1) {
173
178
  config.workspace[idx].checksum_at_pull = res.newChecksum;
174
179
  if (res.newUpdatedDate) config.workspace[idx].updated_date = res.newUpdatedDate;
@@ -177,7 +182,7 @@ async function push(opts = {}) {
177
182
  pushedPb.push({ entry, idx });
178
183
  } else if (entry.object_type === 'long_running_process') {
179
184
  const res = await pushLrpEntry(token, entry, BACKUP_DIR, backupTs);
180
- console.log(` ${entry.name} → saved`);
185
+ log(` ${entry.name} → saved`);
181
186
  if (idx !== -1) {
182
187
  config.workspace[idx].checksum_at_pull = res.newChecksum;
183
188
  config.workspace[idx].tenant_id = res.newRow.tenantId;
@@ -197,7 +202,7 @@ async function push(opts = {}) {
197
202
  pushed++;
198
203
  }
199
204
 
200
- console.log(`\nRemote backups written to ${BACKUP_DIR}/${backupTs}/`);
205
+ log(`\nRemote backups written to ${BACKUP_DIR}/${backupTs}/`);
201
206
 
202
207
  // Publish/deploy step for pushed wizards and LRPs.
203
208
  if (pushedPb.length) {
@@ -208,7 +213,7 @@ async function push(opts = {}) {
208
213
  }
209
214
 
210
215
  writeConfig(config);
211
- console.log(`\nPushed ${pushed} record(s).`);
216
+ log(`\nPushed ${pushed} record(s).`);
212
217
  }
213
218
 
214
219
  async function handlePublish(token, pushedPb, config, opts) {
@@ -216,6 +221,8 @@ async function handlePublish(token, pushedPb, config, opts) {
216
221
  let doPublish;
217
222
  if (opts.skipPublish) doPublish = false;
218
223
  else if (opts.publish) doPublish = true;
224
+ else if (opts.nonInteractive)
225
+ doPublish = false; // no stdin to prompt on; default to safe
219
226
  else {
220
227
  ({ doPublish } = await inquirer.prompt([
221
228
  {
@@ -229,7 +236,7 @@ async function handlePublish(token, pushedPb, config, opts) {
229
236
  if (doPublish) {
230
237
  await publish(token, entry.guid);
231
238
  if (idx !== -1) config.workspace[idx].status = 'published';
232
- console.log(` published ${entry.name}`);
239
+ log(` published ${entry.name}`);
233
240
  }
234
241
  }
235
242
  }
@@ -239,6 +246,8 @@ async function handleDeploy(token, pushedLrp, config, opts) {
239
246
  let doDeploy;
240
247
  if (opts.skipPublish) doDeploy = false;
241
248
  else if (opts.publish) doDeploy = true;
249
+ else if (opts.nonInteractive)
250
+ doDeploy = false; // no stdin to prompt on; default to safe
242
251
  else {
243
252
  ({ doDeploy } = await inquirer.prompt([
244
253
  {
@@ -252,7 +261,7 @@ async function handleDeploy(token, pushedLrp, config, opts) {
252
261
  if (doDeploy) {
253
262
  await deploy(token, deployId);
254
263
  if (idx !== -1) config.workspace[idx].status = 'deployed';
255
- console.log(` deployed ${entry.name}`);
264
+ log(` deployed ${entry.name}`);
256
265
  }
257
266
  }
258
267
  }
@@ -1,3 +1,4 @@
1
+ import { log } from '../../lib/logger.js';
1
2
  import { readConfig, writeConfig } from '../lib/config.js';
2
3
  import { fileExists } from '../lib/workspace.js';
3
4
 
@@ -9,16 +10,16 @@ async function repair() {
9
10
  config.workspace = config.workspace.filter((entry) => fileExists(entry.path));
10
11
 
11
12
  if (!stale.length) {
12
- console.log('No stale entries found.');
13
+ log('No stale entries found.');
13
14
  return;
14
15
  }
15
16
 
16
17
  for (const entry of stale) {
17
- console.log(` removed: ${entry.path} (${entry.name})`);
18
+ log(` removed: ${entry.path} (${entry.name})`);
18
19
  }
19
20
 
20
21
  writeConfig(config);
21
- console.log(
22
+ log(
22
23
  `Removed ${stale.length} stale entr${stale.length === 1 ? 'y' : 'ies'} (${before} → ${config.workspace.length}).`
23
24
  );
24
25
  }
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
3
  import { program } from 'commander';
4
+ import { error } from '../lib/logger.js';
4
5
  import { clone } from './commands/clone.js';
5
6
  import { diff } from './commands/diff.js';
6
7
  import { init } from './commands/init.js';
@@ -24,7 +25,7 @@ const require = createRequire(import.meta.url);
24
25
  const { version } = require('../../package.json');
25
26
 
26
27
  process.on('unhandledRejection', (err) => {
27
- console.error(`Error: ${err.message}`);
28
+ error(`Error: ${err.message}`);
28
29
  process.exit(1);
29
30
  });
30
31
 
@@ -35,7 +36,7 @@ program
35
36
  .description('Create agrippa.yaml workspace config in the current directory')
36
37
  .action(() =>
37
38
  init().catch((err) => {
38
- console.error(`Error: ${err.message}`);
39
+ error(`Error: ${err.message}`);
39
40
  process.exit(1);
40
41
  })
41
42
  );
@@ -54,7 +55,7 @@ program
54
55
  .option('--path <path>', 'Destination path (file for MFA, base dir for workflow/pb/lrp)')
55
56
  .action((opts) =>
56
57
  clone(opts).catch((err) => {
57
- console.error(`Error: ${err.message}`);
58
+ error(`Error: ${err.message}`);
58
59
  process.exit(1);
59
60
  })
60
61
  );
@@ -62,9 +63,13 @@ program
62
63
  program
63
64
  .command('pull')
64
65
  .description('Pull remote changes into local files')
65
- .action(() =>
66
- pull().catch((err) => {
67
- console.error(`Error: ${err.message}`);
66
+ .option(
67
+ '--non-interactive',
68
+ 'No prompts; auto-select safe (fast-forward) entries and fail if any is in conflict'
69
+ )
70
+ .action((opts) =>
71
+ pull(opts).catch((err) => {
72
+ error(`Error: ${err.message}`);
68
73
  process.exit(1);
69
74
  })
70
75
  );
@@ -74,9 +79,13 @@ program
74
79
  .description('Push local changes to RIP / Process Builder / LRP (backs up remote first)')
75
80
  .option('--publish', 'Auto-publish pushed wizards and auto-deploy pushed LRPs')
76
81
  .option('--skip-publish', 'Skip publishing/deploying pushed wizards and LRPs (no prompt)')
82
+ .option(
83
+ '--non-interactive',
84
+ 'No prompts; auto-select safe (fast-forward) entries and fail if any is in conflict'
85
+ )
77
86
  .action((opts) =>
78
87
  push(opts).catch((err) => {
79
- console.error(`Error: ${err.message}`);
88
+ error(`Error: ${err.message}`);
80
89
  process.exit(1);
81
90
  })
82
91
  );
@@ -86,7 +95,7 @@ program
86
95
  .description('Show differences between local files and remote code')
87
96
  .action((path) =>
88
97
  diff(path).catch((err) => {
89
- console.error(`Error: ${err.message}`);
98
+ error(`Error: ${err.message}`);
90
99
  process.exit(1);
91
100
  })
92
101
  );
@@ -96,7 +105,7 @@ program
96
105
  .description('Initialize a phase with default code template and result vars')
97
106
  .action(() =>
98
107
  initPhase().catch((err) => {
99
- console.error(`Error: ${err.message}`);
108
+ error(`Error: ${err.message}`);
100
109
  process.exit(1);
101
110
  })
102
111
  );
@@ -106,14 +115,14 @@ program
106
115
  .description('Remove stale workspace entries where local file no longer exists')
107
116
  .action(() =>
108
117
  repair().catch((err) => {
109
- console.error(`Error: ${err.message}`);
118
+ error(`Error: ${err.message}`);
110
119
  process.exit(1);
111
120
  })
112
121
  );
113
122
 
114
123
  // ---- pb: local editing helpers for a cloned process-builder wizard or LRP ----
115
124
  const die = (err) => {
116
- console.error(`Error: ${err.message}`);
125
+ error(`Error: ${err.message}`);
117
126
  process.exit(1);
118
127
  };
119
128
  const pb = program
@@ -154,7 +154,14 @@ async function autoLayout(structure) {
154
154
  targets: [e.target],
155
155
  sourcePort,
156
156
  labels: e.name
157
- ? [{ id: `${e.id}__lbl`, text: e.name, width: Math.min(e.name.length * 6, 140), height: 14 }]
157
+ ? [
158
+ {
159
+ id: `${e.id}__lbl`,
160
+ text: e.name,
161
+ width: Math.min(e.name.length * 6, 140),
162
+ height: 14,
163
+ },
164
+ ]
158
165
  : [],
159
166
  layoutOptions: {
160
167
  'elk.layered.priority.straightness': isHappy ? '10' : 1,
@@ -926,6 +926,7 @@ export {
926
926
  buildProcess,
927
927
  parseDiagram,
928
928
  normalizeProcessTree,
929
+ diagramGeometry,
929
930
  compareProcess,
930
931
  compareDiagram,
931
932
  diff,
@@ -92,8 +92,12 @@ function toSvg(structure) {
92
92
  );
93
93
  if (e.name) {
94
94
  const lp = e.labelPos;
95
- const lx = lp ? lp.x + lp.width / 2 : e.waypoints[Math.floor(e.waypoints.length / 2)][0];
96
- const ly = lp ? lp.y + lp.height / 2 : e.waypoints[Math.floor(e.waypoints.length / 2)][1] - 4;
95
+ const lx = lp
96
+ ? lp.x + lp.width / 2
97
+ : e.waypoints[Math.floor(e.waypoints.length / 2)][0];
98
+ const ly = lp
99
+ ? lp.y + lp.height / 2
100
+ : e.waypoints[Math.floor(e.waypoints.length / 2)][1] - 4;
97
101
  out.push(
98
102
  `<text x="${lx + ox}" y="${ly + oy}" text-anchor="middle" fill="#666" font-size="9">${esc(e.name)}</text>`
99
103
  );