@camunda8/cli 3.3.0-alpha.5 → 3.3.0-alpha.6

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/EXAMPLES.md CHANGED
@@ -1233,6 +1233,26 @@ c8 cluster install alpha
1233
1233
  c8 cluster delete 8.8
1234
1234
  ```
1235
1235
 
1236
+ ### Purge Cluster Data
1237
+
1238
+ Wipe history and journal data while keeping the downloaded binary intact.
1239
+ The next `cluster start` begins with a fresh empty state without re-downloading anything.
1240
+
1241
+ ```bash
1242
+ # Purge data for a specific version (version required, like delete)
1243
+ c8 cluster purge 8.9
1244
+ c8 cluster purge 8.9.0-alpha5
1245
+
1246
+ # Stop the running cluster and purge its data in one step
1247
+ c8 cluster stop --purge
1248
+ ```
1249
+
1250
+ If the target version is currently running, stop it first with `cluster stop`
1251
+ (or use `cluster stop --purge` to stop and purge in one step).
1252
+
1253
+ `cluster stop --purge` is the recommended way to reset a running cluster:
1254
+ it captures the running version, stops it, then purges the data atomically.
1255
+
1236
1256
  ### Typical Local Development Workflow
1237
1257
 
1238
1258
  ```bash
package/README.md CHANGED
@@ -517,6 +517,12 @@ c8ctl cluster install 8.9
517
517
 
518
518
  # Remove a cached version
519
519
  c8ctl cluster delete 8.9
520
+
521
+ # Purge history and journal data for a version (binary stays intact)
522
+ c8ctl cluster purge 8.9
523
+
524
+ # Stop cluster and purge its runtime data in one step
525
+ c8ctl cluster stop --purge
520
526
  ```
521
527
 
522
528
  #### Version Aliases
@@ -320,6 +320,7 @@ export const metadata = {
320
320
  { name: 'delete', description: 'Remove a cached version' },
321
321
  { name: 'log', description: 'Stream cluster logs' },
322
322
  { name: 'logs', description: 'Stream cluster logs' },
323
+ { name: 'purge', description: 'Delete runtime data (keeps binary) so the next start is fresh' },
323
324
  ],
324
325
  examples: [
325
326
  { command: 'c8ctl cluster start', description: 'Start a local Camunda 8 cluster (latest stable)' },
@@ -332,6 +333,8 @@ export const metadata = {
332
333
  { command: 'c8ctl cluster list-remote', description: 'List all versions available on the remote download server' },
333
334
  { command: 'c8ctl cluster install 8.8', description: 'Download a version without starting it' },
334
335
  { command: 'c8ctl cluster delete 8.8', description: 'Remove a locally cached version' },
336
+ { command: 'c8ctl cluster purge 8.8', description: 'Delete runtime data for a version (binary stays intact)' },
337
+ { command: 'c8ctl cluster stop --purge', description: 'Stop the running cluster and delete its runtime data' },
335
338
  ],
336
339
  },
337
340
  },
@@ -1043,13 +1046,16 @@ export async function stopC8Run(config, debug = false) {
1043
1046
  logger.warn(
1044
1047
  'Cluster marker file found, but no running cluster processes detected. Cleaning up stale marker.',
1045
1048
  );
1046
- if (existsSync(markerFile)) {
1047
- rmSync(markerFile);
1048
- }
1049
+ let staleVersion = null;
1049
1050
  if (existsSync(versionFile)) {
1051
+ const v = readFileSync(versionFile, 'utf-8').trim();
1052
+ if (v) staleVersion = v;
1050
1053
  rmSync(versionFile);
1051
1054
  }
1052
- return;
1055
+ if (existsSync(markerFile)) {
1056
+ rmSync(markerFile);
1057
+ }
1058
+ return staleVersion;
1053
1059
  }
1054
1060
 
1055
1061
  if (!markerExists && clusterAppearsRunning) {
@@ -1069,10 +1075,12 @@ export async function stopC8Run(config, debug = false) {
1069
1075
  : [];
1070
1076
 
1071
1077
  const versionsToTry = [];
1078
+ let stoppedVersion = null;
1072
1079
 
1073
1080
  if (existsSync(versionFile)) {
1074
1081
  const markerVersion = readFileSync(versionFile, 'utf-8').trim();
1075
1082
  if (markerVersion) {
1083
+ stoppedVersion = markerVersion;
1076
1084
  versionsToTry.push(markerVersion);
1077
1085
  }
1078
1086
  }
@@ -1186,6 +1194,7 @@ export async function stopC8Run(config, debug = false) {
1186
1194
  }
1187
1195
 
1188
1196
  logger.info('Cluster stopped.');
1197
+ return stoppedVersion;
1189
1198
  }
1190
1199
 
1191
1200
  // ---------------------------------------------------------------------------
@@ -1369,6 +1378,37 @@ export async function listRemoteVersions() {
1369
1378
  console.log('');
1370
1379
  }
1371
1380
 
1381
+ // ---------------------------------------------------------------------------
1382
+ // Shared helpers for delete / purge
1383
+ // ---------------------------------------------------------------------------
1384
+
1385
+ // Validate and resolve a version spec to the locally installed version name.
1386
+ // Aliases (stable, alpha) are resolved preferring the local cache so that
1387
+ // delete/purge operate on what is actually installed.
1388
+ async function resolveVersionArg(cacheDir, versionSpec) {
1389
+ validateVersionSpec(versionSpec);
1390
+ return isVersionAlias(versionSpec)
1391
+ ? await resolveVersion(versionSpec, { preferLocal: true, cacheDir })
1392
+ : versionSpec;
1393
+ }
1394
+
1395
+ // Return true if the given version appears to be currently running.
1396
+ // Requires both marker files to identify the running version, then confirms
1397
+ // with live .process pidfiles. Without markers we cannot attribute any
1398
+ // running process to a specific version, so we return false — consistent
1399
+ // with stopC8Run treating marker absence as "not running".
1400
+ function isVersionRunning(cacheDir, version) {
1401
+ const markerFile = join(cacheDir, ACTIVE_MARKER_FILE);
1402
+ const versionFile = join(cacheDir, VERSION_MARKER_FILE);
1403
+
1404
+ if (!existsSync(markerFile) || !existsSync(versionFile)) return false;
1405
+
1406
+ const markerVersion = readFileSync(versionFile, 'utf-8').trim();
1407
+ if (markerVersion !== version) return false;
1408
+
1409
+ return hasRunningClusterPidfiles(cacheDir);
1410
+ }
1411
+
1372
1412
  // ---------------------------------------------------------------------------
1373
1413
  // Delete cached version
1374
1414
  // ---------------------------------------------------------------------------
@@ -1381,28 +1421,28 @@ export async function deleteVersion(cacheDir, versionSpec) {
1381
1421
  process.exit(1);
1382
1422
  }
1383
1423
 
1384
- validateVersionSpec(versionSpec);
1385
-
1386
- // Resolve named aliases (stable/alpha) to the actual cached version name.
1387
- // Use preferLocal so we delete the version that's actually installed,
1388
- // not whatever the remote currently resolves the alias to.
1389
1424
  // Major.minor patterns like 8.8 are used as-is since the cache dir is named c8run-8.8.
1390
- const resolvedVersion = isVersionAlias(versionSpec)
1391
- ? await resolveVersion(versionSpec, { preferLocal: true, cacheDir })
1392
- : versionSpec;
1425
+ const resolvedVersion = await resolveVersionArg(cacheDir, versionSpec);
1393
1426
 
1394
1427
  // Prevent deleting a currently running version
1395
- const versionFile = join(cacheDir, VERSION_MARKER_FILE);
1396
- const markerFile = join(cacheDir, ACTIVE_MARKER_FILE);
1428
+ if (isVersionRunning(cacheDir, resolvedVersion)) {
1429
+ logger.error(
1430
+ `Version ${resolvedVersion} is currently running. Stop it first with: c8ctl cluster stop`
1431
+ );
1432
+ process.exit(1);
1433
+ }
1397
1434
 
1398
- if (existsSync(markerFile) && existsSync(versionFile)) {
1399
- const runningVersion = readFileSync(versionFile, 'utf-8').trim();
1400
- if (runningVersion === resolvedVersion) {
1401
- logger.error(
1402
- `Version ${resolvedVersion} is currently running. Stop it first with: c8ctl cluster stop`
1403
- );
1404
- process.exit(1);
1405
- }
1435
+ // Safety backstop: if processes are running but either marker is absent we
1436
+ // cannot confirm the version, so refuse rather than risk deleting a live install.
1437
+ if (
1438
+ (!existsSync(join(cacheDir, ACTIVE_MARKER_FILE)) || !existsSync(join(cacheDir, VERSION_MARKER_FILE))) &&
1439
+ hasRunningClusterPidfiles(cacheDir)
1440
+ ) {
1441
+ logger.error(
1442
+ 'A cluster appears to be running (processes detected) but the cluster marker is missing. ' +
1443
+ 'Run c8ctl cluster stop first.'
1444
+ );
1445
+ process.exit(1);
1406
1446
  }
1407
1447
 
1408
1448
  const config = { cacheDir, version: resolvedVersion };
@@ -1416,6 +1456,79 @@ export async function deleteVersion(cacheDir, versionSpec) {
1416
1456
  logger.info(`Version ${versionSpec} has been deleted.`);
1417
1457
  }
1418
1458
 
1459
+ // ---------------------------------------------------------------------------
1460
+ // Purge cluster data
1461
+ // ---------------------------------------------------------------------------
1462
+
1463
+ export async function purgeClusterData(cacheDir, versionSpec) {
1464
+ const logger = getLogger();
1465
+
1466
+ const resolvedVersion = await resolveVersionArg(cacheDir, versionSpec);
1467
+
1468
+ // Prevent purging a currently running version
1469
+ if (isVersionRunning(cacheDir, resolvedVersion)) {
1470
+ logger.error(
1471
+ `Version ${resolvedVersion} is currently running. ` +
1472
+ 'Stop it first with: c8ctl cluster stop\n' +
1473
+ `Or stop and purge in one step: c8ctl cluster stop --purge`
1474
+ );
1475
+ process.exit(1);
1476
+ }
1477
+
1478
+ // Safety backstop: if processes are running but either marker is absent we
1479
+ // cannot confirm the version, so refuse rather than risk purging a live cluster.
1480
+ if (
1481
+ (!existsSync(join(cacheDir, ACTIVE_MARKER_FILE)) || !existsSync(join(cacheDir, VERSION_MARKER_FILE))) &&
1482
+ hasRunningClusterPidfiles(cacheDir)
1483
+ ) {
1484
+ logger.error(
1485
+ 'A cluster appears to be running (processes detected) but the cluster marker is missing. ' +
1486
+ 'Run c8ctl cluster stop first.'
1487
+ );
1488
+ process.exit(1);
1489
+ }
1490
+
1491
+ const config = { cacheDir, version: resolvedVersion };
1492
+ if (!isC8RunInstalled(config)) {
1493
+ logger.error(`Version ${resolvedVersion} is not installed locally.`);
1494
+ process.exit(1);
1495
+ }
1496
+
1497
+ const binaryPath = getC8RunBinaryPath(config);
1498
+ const binaryDir = dirname(binaryPath);
1499
+
1500
+ const deleted = [];
1501
+
1502
+ // Delete camunda-data (history data + application state)
1503
+ const camundaDataDir = join(binaryDir, 'camunda-data');
1504
+ if (existsSync(camundaDataDir)) {
1505
+ rmSync(camundaDataDir, { recursive: true });
1506
+ deleted.push('camunda-data');
1507
+ }
1508
+
1509
+ // Delete Zeebe journal data inside camunda-zeebe-* subdirectory
1510
+ if (existsSync(binaryDir)) {
1511
+ for (const entry of readdirSync(binaryDir, { withFileTypes: true })) {
1512
+ if (!entry.isDirectory() || !entry.name.startsWith('camunda-zeebe-')) continue;
1513
+ const dataDir = join(binaryDir, entry.name, 'data');
1514
+ if (existsSync(dataDir)) {
1515
+ rmSync(dataDir, { recursive: true });
1516
+ deleted.push(join(entry.name, 'data'));
1517
+ }
1518
+ }
1519
+ }
1520
+
1521
+ if (deleted.length > 0) {
1522
+ logger.info(`Purged runtime data for ${resolvedVersion}:`);
1523
+ for (const d of deleted) {
1524
+ logger.info(` deleted: ${d}`);
1525
+ }
1526
+ } else {
1527
+ logger.info(`No runtime data found for ${resolvedVersion} — nothing to delete.`);
1528
+ }
1529
+ logger.info(`Binary and installed files preserved. Start fresh with: c8ctl cluster start ${resolvedVersion}`);
1530
+ }
1531
+
1419
1532
  // ---------------------------------------------------------------------------
1420
1533
  // Logs
1421
1534
  // ---------------------------------------------------------------------------
@@ -1499,7 +1612,7 @@ export async function streamLogs(cacheDir) {
1499
1612
  // ---------------------------------------------------------------------------
1500
1613
 
1501
1614
  export function parsePluginArgs(args) {
1502
- const result = { subcommand: null, version: null, debug: false };
1615
+ const result = { subcommand: null, version: null, debug: false, purge: false };
1503
1616
 
1504
1617
  let i = 0;
1505
1618
  while (i < args.length) {
@@ -1523,6 +1636,12 @@ export function parsePluginArgs(args) {
1523
1636
  continue;
1524
1637
  }
1525
1638
 
1639
+ if (arg === '--purge') {
1640
+ result.purge = true;
1641
+ i += 1;
1642
+ continue;
1643
+ }
1644
+
1526
1645
  if (!arg.startsWith('-') && result.subcommand === null) {
1527
1646
  result.subcommand = arg;
1528
1647
  i += 1;
@@ -1546,7 +1665,7 @@ export function parsePluginArgs(args) {
1546
1665
  // Plugin commands export
1547
1666
  // ---------------------------------------------------------------------------
1548
1667
 
1549
- const VALID_SUBCOMMANDS = ['start', 'stop', 'status', 'list', 'list-remote', 'install', 'delete', 'log', 'logs'];
1668
+ const VALID_SUBCOMMANDS = ['start', 'stop', 'status', 'list', 'list-remote', 'install', 'delete', 'purge', 'log', 'logs'];
1550
1669
 
1551
1670
  export const commands = {
1552
1671
  'cluster': async (args) => {
@@ -1556,28 +1675,31 @@ export const commands = {
1556
1675
  if (!parsed.subcommand || !VALID_SUBCOMMANDS.includes(parsed.subcommand)) {
1557
1676
  console.log('Usage:');
1558
1677
  console.log(' c8ctl cluster start [<version>] [--debug]');
1559
- console.log(' c8ctl cluster stop');
1678
+ console.log(' c8ctl cluster stop [--purge]');
1560
1679
  console.log(' c8ctl cluster status');
1561
1680
  console.log(' c8ctl cluster logs (alias: log)');
1562
1681
  console.log(' c8ctl cluster list');
1563
1682
  console.log(' c8ctl cluster list-remote');
1564
1683
  console.log(' c8ctl cluster install <version>');
1565
1684
  console.log(' c8ctl cluster delete <version>');
1685
+ console.log(' c8ctl cluster purge <version>');
1566
1686
  console.log('');
1567
1687
  console.log('Subcommands:');
1568
1688
  console.log(' start Download (if needed) and start a local Camunda 8 cluster');
1569
- console.log(' stop Stop the running local Camunda 8 cluster');
1689
+ console.log(' stop Stop the running local Camunda 8 cluster (--purge also deletes runtime data)');
1570
1690
  console.log(' status Show whether a cluster is running and connection details');
1571
1691
  console.log(' logs Stream log output from the running cluster');
1572
1692
  console.log(' list List locally cached versions and available version aliases');
1573
1693
  console.log(' list-remote List all versions available on the remote download server');
1574
1694
  console.log(' install Download a version without starting it');
1575
1695
  console.log(' delete Remove a locally cached version to reclaim disk space');
1696
+ console.log(' purge Delete runtime data for a version (binary stays intact, next start is fresh)');
1576
1697
  console.log('');
1577
1698
  console.log('Options:');
1578
1699
  console.log(' <version> Camunda version, alias, or major.minor (default: stable)');
1579
1700
  console.log(' --c8-version <version> Alternative flag form for version');
1580
1701
  console.log(' --debug Stream raw c8run output during start');
1702
+ console.log(' --purge (stop only) also delete runtime data after stopping');
1581
1703
  console.log('');
1582
1704
  console.log('A <version> can be:');
1583
1705
  console.log(' stable / alpha Named aliases');
@@ -1604,9 +1726,16 @@ export const commands = {
1604
1726
  console.log(' c8ctl cluster list-remote');
1605
1727
  console.log(' c8ctl cluster install 8.8');
1606
1728
  console.log(' c8ctl cluster delete 8.8');
1729
+ console.log(' c8ctl cluster purge 8.8 # Delete runtime data, keep binary');
1730
+ console.log(' c8ctl cluster stop --purge # Stop cluster and delete its runtime data');
1607
1731
  return;
1608
1732
  }
1609
1733
 
1734
+ if (parsed.purge && parsed.subcommand !== 'stop') {
1735
+ logger.error('--purge can only be used with the stop subcommand. Example: c8ctl cluster stop --purge');
1736
+ process.exit(1);
1737
+ }
1738
+
1610
1739
  if (parsed.subcommand === 'status') {
1611
1740
  try {
1612
1741
  await clusterStatus(getCacheDir());
@@ -1647,11 +1776,14 @@ export const commands = {
1647
1776
  return;
1648
1777
  }
1649
1778
 
1650
- // install and delete require an explicit version argument
1651
- if (!parsed.version && (parsed.subcommand === 'install' || parsed.subcommand === 'delete')) {
1652
- const example = parsed.subcommand === 'delete'
1653
- ? 'c8ctl cluster delete 8.8'
1654
- : 'c8ctl cluster install stable';
1779
+ // install, delete, and purge require an explicit version argument
1780
+ if (!parsed.version && (parsed.subcommand === 'install' || parsed.subcommand === 'delete' || parsed.subcommand === 'purge')) {
1781
+ const example =
1782
+ parsed.subcommand === 'delete'
1783
+ ? 'c8ctl cluster delete 8.8'
1784
+ : parsed.subcommand === 'purge'
1785
+ ? 'c8ctl cluster purge 8.8'
1786
+ : 'c8ctl cluster install stable';
1655
1787
  logger.error(`Please specify a version. Example: ${example}`);
1656
1788
  process.exit(1);
1657
1789
  }
@@ -1666,6 +1798,16 @@ export const commands = {
1666
1798
  return;
1667
1799
  }
1668
1800
 
1801
+ if (parsed.subcommand === 'purge') {
1802
+ try {
1803
+ await purgeClusterData(getCacheDir(), parsed.version);
1804
+ } catch (error) {
1805
+ logger.error(`Failed to purge cluster data: ${error}`);
1806
+ process.exit(1);
1807
+ }
1808
+ return;
1809
+ }
1810
+
1669
1811
  const versionSpec = parsed.version || 'stable';
1670
1812
  if (!parsed.version && parsed.subcommand === 'start') {
1671
1813
  logger.info(`No version specified, using default: "${versionSpec}"`);
@@ -1713,7 +1855,15 @@ export const commands = {
1713
1855
  }
1714
1856
  } else if (parsed.subcommand === 'stop') {
1715
1857
  try {
1716
- await stopC8Run(config, parsed.debug);
1858
+ const stoppedVersion = await stopC8Run(config, parsed.debug);
1859
+ if (parsed.purge) {
1860
+ if (!stoppedVersion) {
1861
+ logger.warn('Cannot determine which version to purge (version marker is missing). ' +
1862
+ 'To purge manually, run: c8ctl cluster purge <version>');
1863
+ } else {
1864
+ await purgeClusterData(theCacheDir, stoppedVersion);
1865
+ }
1866
+ }
1717
1867
  } catch (error) {
1718
1868
  logger.error(`Failed to stop cluster: ${error}`);
1719
1869
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda8/cli",
3
- "version": "3.3.0-alpha.5",
3
+ "version": "3.3.0-alpha.6",
4
4
  "description": "Camunda 8 CLI - minimal-dependency CLI for Camunda 8 operations",
5
5
  "type": "module",
6
6
  "engines": {