@waron97/prbot 3.3.0 → 3.5.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 +53 -37
- package/package.json +5 -1
- package/src/agrippa/commands/clone.js +10 -9
- package/src/agrippa/commands/cloneLrp.js +9 -9
- package/src/agrippa/commands/clonePb.js +10 -10
- package/src/agrippa/commands/diff.js +64 -25
- package/src/agrippa/commands/init.js +14 -13
- package/src/agrippa/commands/initPhase.js +10 -11
- package/src/agrippa/commands/pb.js +29 -26
- package/src/agrippa/commands/pull.js +67 -48
- package/src/agrippa/commands/pullLrp.js +2 -3
- package/src/agrippa/commands/pullPb.js +2 -3
- package/src/agrippa/commands/push.js +33 -24
- package/src/agrippa/commands/repair.js +4 -3
- package/src/agrippa/index.js +25 -13
- package/src/agrippa/lib/pbEdit.js +49 -2
- package/src/agrippa/lib/pbLayout.js +8 -1
- package/src/agrippa/lib/pbModel.js +1 -0
- package/src/agrippa/lib/pbPreview.js +6 -2
- package/src/agrippa/lib/pbProject.js +91 -3
- package/src/commands/autopr.js +20 -21
- 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
|
@@ -2,6 +2,7 @@ import search from '@inquirer/search';
|
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
3
|
import { getToken } from '../../lib/auth.js';
|
|
4
4
|
import { fuzzyMatch } from '../../lib/fuzzy.js';
|
|
5
|
+
import { log } from '../../lib/logger.js';
|
|
5
6
|
import {
|
|
6
7
|
createConfigurator,
|
|
7
8
|
deleteConfigurator,
|
|
@@ -75,12 +76,12 @@ async function initPhase() {
|
|
|
75
76
|
if (!ripUrl)
|
|
76
77
|
throw new Error('RIP_URL is not configured. Run `prbot init` or set it in agrippa.yaml.');
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
log('Fetching workflows...');
|
|
79
80
|
const token = await getToken();
|
|
80
81
|
const workflows = await listWorkflows(token, ripUrl);
|
|
81
82
|
|
|
82
83
|
if (!workflows.length) {
|
|
83
|
-
|
|
84
|
+
log('No workflows found.');
|
|
84
85
|
return;
|
|
85
86
|
}
|
|
86
87
|
|
|
@@ -92,11 +93,11 @@ async function initPhase() {
|
|
|
92
93
|
},
|
|
93
94
|
});
|
|
94
95
|
|
|
95
|
-
|
|
96
|
+
log(`Fetching phases for "${workflow.name}"...`);
|
|
96
97
|
const phases = await getPhasesByWorkflow(token, ripUrl, workflow.id);
|
|
97
98
|
|
|
98
99
|
if (!phases.length) {
|
|
99
|
-
|
|
100
|
+
log('No phases found for this workflow.');
|
|
100
101
|
return;
|
|
101
102
|
}
|
|
102
103
|
|
|
@@ -111,7 +112,7 @@ async function initPhase() {
|
|
|
111
112
|
]);
|
|
112
113
|
|
|
113
114
|
if (!selectedPhases.length) {
|
|
114
|
-
|
|
115
|
+
log('No phases selected. Aborted.');
|
|
115
116
|
return;
|
|
116
117
|
}
|
|
117
118
|
|
|
@@ -125,7 +126,7 @@ async function initPhase() {
|
|
|
125
126
|
]);
|
|
126
127
|
|
|
127
128
|
if (!confirm) {
|
|
128
|
-
|
|
129
|
+
log('Aborted.');
|
|
129
130
|
return;
|
|
130
131
|
}
|
|
131
132
|
|
|
@@ -157,15 +158,13 @@ async function initPhase() {
|
|
|
157
158
|
});
|
|
158
159
|
}
|
|
159
160
|
|
|
160
|
-
|
|
161
|
+
log(`Initialized phase "${phase.name}".`);
|
|
161
162
|
if (results.length > 0) {
|
|
162
|
-
|
|
163
|
+
log(` ${results.length} configurator(s) created: ${vars.join(', ')}`);
|
|
163
164
|
}
|
|
164
165
|
}
|
|
165
166
|
|
|
166
|
-
|
|
167
|
-
`Run 'agrippa pull' in workspaces that track this workflow to fetch the updated code.`
|
|
168
|
-
);
|
|
167
|
+
log(`Run 'agrippa pull' in workspaces that track this workflow to fetch the updated code.`);
|
|
169
168
|
}
|
|
170
169
|
|
|
171
170
|
export { initPhase };
|
|
@@ -18,6 +18,7 @@ import { dirname, join } from 'path';
|
|
|
18
18
|
import search from '@inquirer/search';
|
|
19
19
|
import { parse as yamlParse } from 'yaml';
|
|
20
20
|
import { fuzzyMatch } from '../../lib/fuzzy.js';
|
|
21
|
+
import { log, warn } from '../../lib/logger.js';
|
|
21
22
|
import { readConfig } from '../lib/config.js';
|
|
22
23
|
import {
|
|
23
24
|
addNode,
|
|
@@ -120,7 +121,7 @@ function validate(dir) {
|
|
|
120
121
|
try {
|
|
121
122
|
recompose(projectReader(dir));
|
|
122
123
|
} catch (e) {
|
|
123
|
-
|
|
124
|
+
warn(`WARNING: project no longer recomposes cleanly: ${e.message}`);
|
|
124
125
|
}
|
|
125
126
|
}
|
|
126
127
|
|
|
@@ -139,13 +140,13 @@ async function pbFormat(opts) {
|
|
|
139
140
|
if (!n.layout) missing++;
|
|
140
141
|
});
|
|
141
142
|
validate(dir);
|
|
142
|
-
|
|
143
|
+
log(
|
|
143
144
|
`Formatted ${dir} (${nodes} node(s) laid out${missing ? `, ${missing} without layout` : ''}).`
|
|
144
145
|
);
|
|
145
146
|
const issues = lintAll(structure);
|
|
146
147
|
if (issues.length) {
|
|
147
|
-
|
|
148
|
-
for (const w of issues)
|
|
148
|
+
warn('Diagram issues:');
|
|
149
|
+
for (const w of issues) warn(` ! ${w}`);
|
|
149
150
|
}
|
|
150
151
|
}
|
|
151
152
|
|
|
@@ -180,13 +181,13 @@ async function pbAdd(opts) {
|
|
|
180
181
|
saveStructure(dir, structure);
|
|
181
182
|
saveManifest(dir, manifest);
|
|
182
183
|
validate(dir);
|
|
183
|
-
|
|
184
|
+
log(
|
|
184
185
|
`Added ${result.type} ${result.id}${result.file ? ` (${result.file})` : ''} between ${opts.from} → ${opts.to}.`
|
|
185
186
|
);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
for (const w of result.warnings || [])
|
|
189
|
-
|
|
187
|
+
log(` ${opts.from} → ${result.id} (${result.edgeId}, retargeted)`);
|
|
188
|
+
log(` ${result.id} → ${opts.to} (${result.newEdgeId})`);
|
|
189
|
+
for (const w of result.warnings || []) warn(` ! ${w}`);
|
|
190
|
+
log('Run `agrippa pb format` to lay it out.');
|
|
190
191
|
return;
|
|
191
192
|
}
|
|
192
193
|
|
|
@@ -200,10 +201,8 @@ async function pbAdd(opts) {
|
|
|
200
201
|
saveStructure(dir, structure);
|
|
201
202
|
saveManifest(dir, manifest);
|
|
202
203
|
validate(dir);
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
'Connect it with `agrippa pb connect`, then run `agrippa pb format` to lay it out.'
|
|
206
|
-
);
|
|
204
|
+
log(`Added ${result.type} ${result.id}${result.file ? ` (${result.file})` : ''}.`);
|
|
205
|
+
log('Connect it with `agrippa pb connect`, then run `agrippa pb format` to lay it out.');
|
|
207
206
|
}
|
|
208
207
|
|
|
209
208
|
async function pbRemove(opts) {
|
|
@@ -215,10 +214,12 @@ async function pbRemove(opts) {
|
|
|
215
214
|
saveStructure(dir, structure);
|
|
216
215
|
saveManifest(dir, manifest);
|
|
217
216
|
validate(dir);
|
|
218
|
-
|
|
217
|
+
log(
|
|
219
218
|
`Removed ${result.removed.length} node(s) [${result.removed.join(', ')}], ` +
|
|
220
219
|
`${result.removedEdges} dangling edge(s), ${deletes.length} file(s).`
|
|
221
220
|
);
|
|
221
|
+
for (const c of result.clearedDefaults || [])
|
|
222
|
+
log(` cleared dangling default ${c.edge} on ${c.node}.`);
|
|
222
223
|
}
|
|
223
224
|
|
|
224
225
|
async function pbConnect(opts) {
|
|
@@ -235,11 +236,11 @@ async function pbConnect(opts) {
|
|
|
235
236
|
});
|
|
236
237
|
saveStructure(dir, structure);
|
|
237
238
|
validate(dir);
|
|
238
|
-
|
|
239
|
+
log(
|
|
239
240
|
`Connected ${result.from} → ${result.to} (${result.id})${opts.default ? ' [default]' : ''}.`
|
|
240
241
|
);
|
|
241
|
-
for (const w of result.warnings || [])
|
|
242
|
-
|
|
242
|
+
for (const w of result.warnings || []) warn(` ! ${w}`);
|
|
243
|
+
log('Run `agrippa pb format` to route it.');
|
|
243
244
|
}
|
|
244
245
|
|
|
245
246
|
async function pbDisconnect(opts) {
|
|
@@ -250,7 +251,9 @@ async function pbDisconnect(opts) {
|
|
|
250
251
|
const { result } = disconnect(structure, { id: opts.id, from: opts.from, to: opts.to });
|
|
251
252
|
saveStructure(dir, structure);
|
|
252
253
|
validate(dir);
|
|
253
|
-
|
|
254
|
+
log(`Removed ${result.removed} edge(s)${result.id ? ` (${result.id})` : ''}.`);
|
|
255
|
+
for (const c of result.clearedDefaults || [])
|
|
256
|
+
log(` cleared dangling default ${c.edge} on ${c.node}.`);
|
|
254
257
|
}
|
|
255
258
|
|
|
256
259
|
async function pbSetDefault(opts) {
|
|
@@ -261,11 +264,11 @@ async function pbSetDefault(opts) {
|
|
|
261
264
|
const { result } = setDefault(structure, { id: opts.id, from: opts.from, to: opts.to });
|
|
262
265
|
saveStructure(dir, structure);
|
|
263
266
|
validate(dir);
|
|
264
|
-
|
|
267
|
+
log(
|
|
265
268
|
`Default flow on ${result.from} is now ${result.id} (→ ${result.to})` +
|
|
266
269
|
`${result.prev && result.prev !== result.id ? `, was ${result.prev}` : ''}.`
|
|
267
270
|
);
|
|
268
|
-
for (const w of result.warnings || [])
|
|
271
|
+
for (const w of result.warnings || []) warn(` ! ${w}`);
|
|
269
272
|
}
|
|
270
273
|
|
|
271
274
|
async function pbList(opts) {
|
|
@@ -275,7 +278,7 @@ async function pbList(opts) {
|
|
|
275
278
|
for (const r of rows) {
|
|
276
279
|
const where = r.parent ? ` [in ${r.parent}]` : '';
|
|
277
280
|
const label = r.name ? ` "${r.name}"` : '';
|
|
278
|
-
|
|
281
|
+
log(`${r.id} (${r.type})${label}${where}`);
|
|
279
282
|
for (const e of r.edges) {
|
|
280
283
|
const tag = [
|
|
281
284
|
e.isDefault && '[default]',
|
|
@@ -284,10 +287,10 @@ async function pbList(opts) {
|
|
|
284
287
|
]
|
|
285
288
|
.filter(Boolean)
|
|
286
289
|
.join(' ');
|
|
287
|
-
|
|
290
|
+
log(` → ${e.target} (${e.id})${tag ? ` ${tag}` : ''}`);
|
|
288
291
|
}
|
|
289
292
|
}
|
|
290
|
-
|
|
293
|
+
log(`\n${rows.length} node(s).`);
|
|
291
294
|
}
|
|
292
295
|
|
|
293
296
|
async function pbPreview(opts) {
|
|
@@ -296,7 +299,7 @@ async function pbPreview(opts) {
|
|
|
296
299
|
const svg = toSvg(structure);
|
|
297
300
|
const out = opts.out || join(dir, 'preview.svg');
|
|
298
301
|
writeFileSync(out, svg, 'utf-8');
|
|
299
|
-
|
|
302
|
+
log(`Wrote ${out} (${svg.length} bytes).`);
|
|
300
303
|
}
|
|
301
304
|
|
|
302
305
|
async function pbLint(opts) {
|
|
@@ -304,9 +307,9 @@ async function pbLint(opts) {
|
|
|
304
307
|
const { structure } = loadProject(dir);
|
|
305
308
|
const issues = lintAll(structure);
|
|
306
309
|
if (!issues.length) {
|
|
307
|
-
|
|
310
|
+
log('No issues.');
|
|
308
311
|
} else {
|
|
309
|
-
for (const w of issues)
|
|
312
|
+
for (const w of issues) warn(` ! ${w}`);
|
|
310
313
|
process.exitCode = 1;
|
|
311
314
|
}
|
|
312
315
|
}
|
|
@@ -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
|
-
|
|
33
|
-
stale.forEach((e) =>
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
+
log(`\nPulled ${selected.length} record(s).`);
|
|
109
114
|
}
|
|
110
115
|
}
|
|
111
116
|
} else {
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
188
|
+
log(` ${entry.name} → refreshed${note}`);
|
|
184
189
|
}
|
|
185
190
|
writeConfig(config);
|
|
186
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
255
|
+
log(` ${entry.name} → refreshed${note}`);
|
|
251
256
|
}
|
|
252
257
|
writeConfig(config);
|
|
253
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
46
|
+
newChecksum: checksumOfPayload(rebuilt),
|
|
48
47
|
newUpdatedDate: upstream.updated_date,
|
|
49
48
|
newStatus: upstream.status,
|
|
50
49
|
diffs,
|