camunda-cli 0.4.3 → 0.6.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/PANDUAN.md +18 -0
- package/README.md +9 -1
- package/bin/camunda.js +10 -2
- package/package.json +1 -1
- package/src/client.js +2 -2
- package/src/commands/instances.js +120 -0
package/PANDUAN.md
CHANGED
|
@@ -152,6 +152,24 @@ camunda cancel --key Process_X -y -r "bersih-bersih" # tanpa konfirmasi
|
|
|
152
152
|
|
|
153
153
|
Menguji meninggalkan banyak instance menggantung. Tanpa `-y` akan diminta konfirmasi.
|
|
154
154
|
|
|
155
|
+
Untuk mengosongkan inbox satu penguji di AlurKerja sekaligus, tanpa peduli prosesnya:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
camunda remove dev-anla me@anla.my.id --dry-run # lihat dulu apa yang akan dihapus
|
|
159
|
+
camunda remove dev-anla me@anla.my.id # hapus, diminta mengetik jumlah instance
|
|
160
|
+
camunda remove dev-anla me@anla.my.id --live # tenant LIVE: peringatan, lalu ketik nama tenant
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Perintah ini menghapus setiap instance yang punya task terbuka milik email itu di tenant
|
|
164
|
+
`<Nama> (TEST)` workspace tersebut. Workspace boleh slug atau nama (`dev-anla`, `Plan Route`),
|
|
165
|
+
atau id tenant TEST kalau namanya ambigu (`hospital (TEST)` dan `Hospital (TEST)`) atau slug-nya
|
|
166
|
+
tidak mengikuti nama (`new-workspace` berjalan di `Telco (TEST)`). Id tenant LIVE ditolak kecuali
|
|
167
|
+
memakai `--live`. Dengan `--live`, setelah daftar task muncul peringatan bahwa ini tenant LIVE (request
|
|
168
|
+
sungguhan, bukan data uji) dan nama tenant harus diketik persis untuk melanjutkan; `-y` tidak
|
|
169
|
+
melewati konfirmasi ini.
|
|
170
|
+
Menghapus instance anak call activity ikut menghentikan induknya, jadi seluruh request berakhir,
|
|
171
|
+
termasuk task orang lain di request yang sama.
|
|
172
|
+
|
|
155
173
|
---
|
|
156
174
|
|
|
157
175
|
## Kasus 3: instance macet atau gagal
|
package/README.md
CHANGED
|
@@ -132,7 +132,7 @@ sentence you need is at the bottom, so that is what gets printed first.
|
|
|
132
132
|
```
|
|
133
133
|
Session login logout whoami
|
|
134
134
|
Models definitions inspect lint xml stats
|
|
135
|
-
Instances instances instance start cancel vars set-var
|
|
135
|
+
Instances instances instance start cancel remove vars set-var
|
|
136
136
|
Diagnosis diagnose trace incidents jobs stacktrace
|
|
137
137
|
Tasks tasks task complete claim
|
|
138
138
|
Deployment deployments deploy undeploy
|
|
@@ -173,6 +173,14 @@ Instance 3435051 finished in 8.0s.
|
|
|
173
173
|
**Cleaning up after a test run.** `cancel --key <key>` terminates every running instance of
|
|
174
174
|
a process in one go, which matters because testing a model leaves a trail of them behind.
|
|
175
175
|
|
|
176
|
+
`remove <workspace> <email>` clears one tester's inbox on an AlurKerja engine instead: it
|
|
177
|
+
deletes every instance holding an open task assigned to that email in the workspace's
|
|
178
|
+
`<Name> (TEST)` tenant. The workspace is a slug or name (`dev-anla`, `Plan Route`) or a Test
|
|
179
|
+
tenant id; a Live tenant id is refused. `--dry-run` lists what would go. `--live` clears the
|
|
180
|
+
workspace's Live tenant instead: after the list it prints a warning and asks for the tenant name
|
|
181
|
+
to be typed, a prompt `--yes` does not skip. Deleting a called
|
|
182
|
+
child instance also terminates its parent, so the whole request ends, not only that task.
|
|
183
|
+
|
|
176
184
|
**Variable types matter.** `--var n=300` sends a string, and `"300" > 200` is a string
|
|
177
185
|
comparison. Use `--var n=300:Integer` where a gateway compares numerically, or `name:=<json>`
|
|
178
186
|
for structured values.
|
package/bin/camunda.js
CHANGED
|
@@ -4,7 +4,7 @@ import { configureOutput, isJsonMode } from '../src/output.js';
|
|
|
4
4
|
import { unwrapError, explain } from '../src/errors.js';
|
|
5
5
|
import { loginCommand, logoutCommand, whoamiCommand } from '../src/commands/session.js';
|
|
6
6
|
import { definitionsCommand, inspectCommand, lintCommand, xmlCommand, statsCommand } from '../src/commands/definitions.js';
|
|
7
|
-
import { instancesCommand, instanceCommand, startCommand, cancelCommand, varsCommand } from '../src/commands/instances.js';
|
|
7
|
+
import { instancesCommand, instanceCommand, startCommand, cancelCommand, removeCommand, varsCommand } from '../src/commands/instances.js';
|
|
8
8
|
import { diagnoseCommand, incidentsCommand, jobsCommand, stacktraceCommand, traceCommand } from '../src/commands/diagnose.js';
|
|
9
9
|
import { tasksCommand, taskCommand, completeCommand, claimCommand } from '../src/commands/tasks.js';
|
|
10
10
|
import { deploymentsCommand, deployCommand, undeployCommand } from '../src/commands/deploy.js';
|
|
@@ -24,7 +24,7 @@ program
|
|
|
24
24
|
'Start with "camunda inspect <key>" to read a deployed model, and\n' +
|
|
25
25
|
'"camunda diagnose <instanceId>" when an instance misbehaves.'
|
|
26
26
|
)
|
|
27
|
-
.version('0.
|
|
27
|
+
.version('0.6.0')
|
|
28
28
|
.option('--json', 'print the raw API payload instead of a formatted view')
|
|
29
29
|
.option('--no-color', 'never emit colour, even on a terminal')
|
|
30
30
|
.showHelpAfterError()
|
|
@@ -130,6 +130,14 @@ withTenant(
|
|
|
130
130
|
.option('-y, --yes', 'skip the confirmation prompt')
|
|
131
131
|
).action(cancelCommand);
|
|
132
132
|
|
|
133
|
+
program
|
|
134
|
+
.command('remove <workspace> <assignee>')
|
|
135
|
+
.description("Delete every instance holding an open task for <assignee> in a workspace's TEST tenant, or its LIVE tenant with --live (workspace slug, name, or tenant id)")
|
|
136
|
+
.option('--live', 'clear the LIVE tenant instead; always asks you to type the tenant name')
|
|
137
|
+
.option('--dry-run', 'list what would be deleted and stop')
|
|
138
|
+
.option('-y, --yes', 'skip the confirmation prompt (never the --live one)')
|
|
139
|
+
.action(removeCommand);
|
|
140
|
+
|
|
133
141
|
program
|
|
134
142
|
.command('vars <instanceId>')
|
|
135
143
|
.description('Variables on an instance, or every write to them with --history')
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -159,9 +159,9 @@ export class Client {
|
|
|
159
159
|
activityInstanceTree(id) {
|
|
160
160
|
return this.get(`/process-instance/${id}/activity-instances`);
|
|
161
161
|
}
|
|
162
|
-
deleteProcessInstance(id, { reason, skipCustomListeners = true, skipIoMappings = true } = {}) {
|
|
162
|
+
deleteProcessInstance(id, { reason, skipCustomListeners = true, skipIoMappings = true, skipSubprocesses } = {}) {
|
|
163
163
|
return this.request('DELETE', `/process-instance/${id}`, {
|
|
164
|
-
query: { skipCustomListeners, skipIoMappings, deleteReason: reason },
|
|
164
|
+
query: { skipCustomListeners, skipIoMappings, skipSubprocesses, deleteReason: reason },
|
|
165
165
|
});
|
|
166
166
|
}
|
|
167
167
|
suspendProcessInstance(id, suspended) {
|
|
@@ -187,6 +187,126 @@ export async function cancelCommand(id, options) {
|
|
|
187
187
|
if (failures.length > 0) process.exitCode = 1;
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
// AlurKerja runs each workspace on two tenants, "<Name>" for Live and "<Name> (TEST)" for
|
|
191
|
+
// Test. remove clears the Test one unless --live is given, and a tenant id of the other kind
|
|
192
|
+
// is refused rather than used, so the flag alone decides which side gets deleted.
|
|
193
|
+
const TENANT_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
194
|
+
const TEST_SUFFIX = /\s*\(TEST\)\s*$/i;
|
|
195
|
+
const isTestTenant = (t) => TEST_SUFFIX.test(t.name ?? '');
|
|
196
|
+
const workspaceSlug = (name) =>
|
|
197
|
+
String(name ?? '')
|
|
198
|
+
.replace(TEST_SUFFIX, '')
|
|
199
|
+
.toLowerCase()
|
|
200
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
201
|
+
.replace(/^-+|-+$/g, '');
|
|
202
|
+
|
|
203
|
+
async function resolveTenant(client, workspace, live) {
|
|
204
|
+
const side = live ? 'Live' : 'Test';
|
|
205
|
+
if (TENANT_ID.test(workspace)) {
|
|
206
|
+
const [tenant] = await client.tenants({ id: workspace });
|
|
207
|
+
if (!tenant) throw new Error(`No tenant with id ${workspace}.`);
|
|
208
|
+
if (isTestTenant(tenant) === live) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
live
|
|
211
|
+
? `${workspace} is "${tenant.name}", a Test tenant. Drop --live to clear it.`
|
|
212
|
+
: `${workspace} is "${tenant.name}", a Live tenant. remove only clears it with --live.`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
return tenant;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const wanted = workspaceSlug(workspace);
|
|
219
|
+
const all = await client.tenants({ maxResults: 1000 });
|
|
220
|
+
const matches = all.filter((t) => isTestTenant(t) !== live && workspaceSlug(t.name) === wanted);
|
|
221
|
+
if (matches.length === 1) return matches[0];
|
|
222
|
+
if (matches.length > 1) {
|
|
223
|
+
const list = matches.map((t) => ` ${t.id} ${t.name}`).join('\n');
|
|
224
|
+
throw new Error(`"${workspace}" matches ${matches.length} ${side} tenants. Pass the tenant id instead:\n${list}`);
|
|
225
|
+
}
|
|
226
|
+
// The engine only knows tenant names, and a workspace slug does not always follow its
|
|
227
|
+
// name (the "new-workspace" slug runs on "Telco (TEST)").
|
|
228
|
+
throw new Error(`No ${side} tenant named like "${workspace}". Find it with: camunda tenants -s <name>, then pass its id.`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Settles on end of input too. Without that, a script with nothing on stdin leaves the prompt
|
|
232
|
+
// pending and node exits 0 silently, which reads like success.
|
|
233
|
+
async function ask(question) {
|
|
234
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
235
|
+
const closed = new Promise((resolve) => rl.once('close', () => resolve(null)));
|
|
236
|
+
const answer = await Promise.race([rl.question(question), closed]);
|
|
237
|
+
rl.close();
|
|
238
|
+
if (answer === null) out.line('');
|
|
239
|
+
return answer?.trim() ?? '';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Clears every open task assigned to one person in a workspace by deleting the instance each
|
|
243
|
+
// task belongs to. Repeated test runs fill a tester's inbox faster than cancelling them one
|
|
244
|
+
// request at a time in the App can empty it.
|
|
245
|
+
export async function removeCommand(workspace, assignee, options) {
|
|
246
|
+
const client = new Client(requireConfig());
|
|
247
|
+
const live = Boolean(options.live);
|
|
248
|
+
const tenant = await resolveTenant(client, workspace, live);
|
|
249
|
+
|
|
250
|
+
const tasks = await client.tasks({ tenantIdIn: tenant.id, assignee });
|
|
251
|
+
const instanceIds = [...new Set(tasks.map((t) => t.processInstanceId).filter(Boolean))].sort();
|
|
252
|
+
|
|
253
|
+
if (out.isJsonMode() && options.dryRun) return out.json({ tenant, live, assignee, tasks, instanceIds });
|
|
254
|
+
|
|
255
|
+
out.heading(`${tenant.name} ${tenant.id}${live ? ' LIVE' : ''}`);
|
|
256
|
+
out.kv([['assignee', assignee]]);
|
|
257
|
+
if (tasks.length === 0) return out.note(`\nNo open tasks assigned to ${assignee}.`);
|
|
258
|
+
|
|
259
|
+
const perName = new Map();
|
|
260
|
+
for (const t of tasks) perName.set(t.name ?? '(unnamed)', (perName.get(t.name ?? '(unnamed)') ?? 0) + 1);
|
|
261
|
+
out.line(`\n${tasks.length} open task(s) across ${instanceIds.length} instance(s)`);
|
|
262
|
+
out.table(
|
|
263
|
+
['TASKS', 'NAME'],
|
|
264
|
+
[...perName].sort((a, b) => b[1] - a[1]).map(([name, count]) => [String(count), out.truncate(name, 60)])
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
if (options.dryRun) return out.note('\nDry run, nothing deleted.');
|
|
268
|
+
|
|
269
|
+
if (live) {
|
|
270
|
+
// --yes does not cover this prompt: these are real requests, and a flag carried over from
|
|
271
|
+
// a Test command line should not be enough to delete them.
|
|
272
|
+
out.line('');
|
|
273
|
+
for (const l of out.wrap(
|
|
274
|
+
`WARNING: "${tenant.name}" is a LIVE tenant. These are real requests, not test data. ` +
|
|
275
|
+
`Deleting them ends each request for everyone involved in it and cannot be undone.`
|
|
276
|
+
)) out.problem(l);
|
|
277
|
+
const answer = await ask(`Are you sure? Type the tenant name "${tenant.name}" to delete ${instanceIds.length} instance(s): `);
|
|
278
|
+
if (answer !== tenant.name) return out.note('Aborted, that did not match.');
|
|
279
|
+
} else if (!options.yes) {
|
|
280
|
+
const expect = String(instanceIds.length);
|
|
281
|
+
const answer = await ask(`\nDeleting ${expect} instance(s) cannot be undone. Type "${expect}" to confirm: `);
|
|
282
|
+
if (answer !== expect) return out.note('Aborted, that did not match.');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
let removed = 0;
|
|
286
|
+
let gone = 0;
|
|
287
|
+
const failures = [];
|
|
288
|
+
for (const id of instanceIds) {
|
|
289
|
+
try {
|
|
290
|
+
await client.deleteProcessInstance(id, { skipSubprocesses: true });
|
|
291
|
+
removed++;
|
|
292
|
+
} catch (err) {
|
|
293
|
+
// Two tasks can share an instance with a parent that was deleted first.
|
|
294
|
+
if (err.status === 404) gone++;
|
|
295
|
+
else failures.push(`${id}: ${err.body?.message || err.message}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const left = await client.tasks({ tenantIdIn: tenant.id, assignee });
|
|
299
|
+
|
|
300
|
+
if (out.isJsonMode()) {
|
|
301
|
+
return out.json({ tenant, live, assignee, removed, alreadyGone: gone, failures, remainingTasks: left.length });
|
|
302
|
+
}
|
|
303
|
+
out.line(`\nDeleted ${removed} instance(s).${gone ? ` ${gone} were already gone.` : ''}`);
|
|
304
|
+
for (const f of failures) out.problem(` ${f}`);
|
|
305
|
+
if (left.length > 0) out.warn(`${left.length} task(s) still assigned to ${assignee}.`);
|
|
306
|
+
else out.note(`No open tasks left for ${assignee}.`);
|
|
307
|
+
if (failures.length > 0) process.exitCode = 1;
|
|
308
|
+
}
|
|
309
|
+
|
|
190
310
|
export async function varsCommand(id, options) {
|
|
191
311
|
const client = new Client(requireConfig());
|
|
192
312
|
|