@camunda8/cli 2.6.0-alpha.4 → 2.6.0-alpha.5

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.
@@ -8,7 +8,7 @@ A default [c8ctl](https://github.com/camunda/c8ctl) plugin that provides an opin
8
8
  # Start with a specific version
9
9
  c8ctl cluster start 8.9.0-alpha5
10
10
 
11
- # Start using a version alias
11
+ # Start using a version alias (dynamically resolved)
12
12
  c8ctl cluster start stable
13
13
  c8ctl cluster start alpha
14
14
 
@@ -22,6 +22,21 @@ c8ctl cluster start --debug
22
22
  c8ctl cluster stop
23
23
  ```
24
24
 
25
+ ## Version aliases
26
+
27
+ The `stable` and `alpha` aliases are resolved dynamically by querying the
28
+ [Camunda Download Center](https://downloads.camunda.cloud/release/camunda/c8run/).
29
+ This means you always get the latest available version without waiting for a
30
+ plugin update.
31
+
32
+ | Alias | Resolves to |
33
+ |----------|-------------|
34
+ | `stable` | Highest minor release that is GA (e.g. `8.8`) |
35
+ | `alpha` | Highest minor release overall (e.g. `8.9`) |
36
+
37
+ If the download server is unreachable, the aliases fall back to the values
38
+ shipped in the plugin's `package.json`.
39
+
25
40
  ## How it works
26
41
 
27
42
  1. **Download**: Automatically downloads the correct c8run binary for your platform from the Camunda Download Center
@@ -24,25 +24,112 @@ import { join, dirname } from 'node:path';
24
24
  import { fileURLToPath } from 'node:url';
25
25
 
26
26
  // ---------------------------------------------------------------------------
27
- // Version aliases (loaded from package.json c8ctl.versionAliases)
27
+ // Version aliases dynamic discovery with package.json fallback
28
28
  // ---------------------------------------------------------------------------
29
29
 
30
+ const DOWNLOAD_BASE_URL = 'https://downloads.camunda.cloud/release/camunda/c8run/';
31
+
30
32
  const _pluginPackageJson = JSON.parse(
31
33
  readFileSync(join(dirname(fileURLToPath(import.meta.url)), 'package.json'), 'utf-8'),
32
34
  );
33
- const _versionAliases = _pluginPackageJson.c8ctl.versionAliases;
34
- const VERSION_ALIASES = new Set(Object.keys(_versionAliases));
35
+ const _fallbackAliases = _pluginPackageJson.c8ctl.versionAliases;
36
+ const KNOWN_ALIAS_NAMES = new Set(['stable', 'alpha']);
35
37
 
36
38
  function isVersionAlias(versionSpec) {
37
- return VERSION_ALIASES.has(versionSpec);
39
+ return KNOWN_ALIAS_NAMES.has(versionSpec);
40
+ }
41
+
42
+ /**
43
+ * Fetch the c8run download directory listing and discover the latest
44
+ * stable and alpha minor versions.
45
+ *
46
+ * Returns { stable: "X.Y", alpha: "X.Y" } or null on failure.
47
+ */
48
+ export async function discoverLatestVersions() {
49
+ try {
50
+ const response = await fetch(DOWNLOAD_BASE_URL);
51
+ if (!response.ok) return null;
52
+ const html = await response.text();
53
+ return parseVersionsFromHtml(html);
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Parse the HTML directory listing from the download server to extract
61
+ * the latest stable and alpha minor versions.
62
+ *
63
+ * - Minor version directories (e.g. "8.8/", "8.9/") are rolling releases
64
+ * updated in-place.
65
+ * - An alpha train exists when there are "X.Y.0-alphaN/" directories
66
+ * for a given minor. The highest minor with alphas is the alpha alias.
67
+ * - The highest minor without alphas is the stable alias.
68
+ */
69
+ export function parseVersionsFromHtml(html) {
70
+ // Match minor-version directories like "8.8/", "8.9/"
71
+ const minorMatches = [...html.matchAll(/href="(\d+\.\d+)\/"/g)].map(m => m[1]);
72
+ // Match alpha directories like "8.9.0-alpha5/"
73
+ const alphaMatches = [...html.matchAll(/href="(\d+\.\d+)\.0-alpha\d+\/"/g)].map(m => m[1]);
74
+
75
+ if (minorMatches.length === 0) return null;
76
+
77
+ const compareSemver = (a, b) => {
78
+ const [aMaj, aMin] = a.split('.').map(Number);
79
+ const [bMaj, bMin] = b.split('.').map(Number);
80
+ return aMaj - bMaj || aMin - bMin;
81
+ };
82
+
83
+ const sortedMinors = [...new Set(minorMatches)].sort(compareSemver);
84
+ const alphaSet = new Set(alphaMatches);
85
+
86
+ const highestMinor = sortedMinors[sortedMinors.length - 1];
87
+
88
+ // The alpha train is the highest minor that has alpha directories.
89
+ // The stable release is the minor just below the alpha train,
90
+ // or the highest minor if no alpha train exists.
91
+ const highestAlphaMinor = [...alphaSet].sort(compareSemver).pop();
92
+
93
+ let stable;
94
+ if (highestAlphaMinor) {
95
+ // Stable = the highest minor that is lower than the alpha train
96
+ stable = sortedMinors.filter(v => compareSemver(v, highestAlphaMinor) < 0).pop() || highestMinor;
97
+ } else {
98
+ stable = highestMinor;
99
+ }
100
+
101
+ return {
102
+ stable,
103
+ alpha: highestMinor,
104
+ };
38
105
  }
39
106
 
40
- function resolveVersion(versionSpec) {
41
- return _versionAliases[versionSpec] ?? versionSpec;
107
+ // Cache the discovery result for the process lifetime
108
+ let _dynamicAliases = undefined;
109
+
110
+ async function getDynamicAliases() {
111
+ if (_dynamicAliases === undefined) {
112
+ _dynamicAliases = await discoverLatestVersions();
113
+ }
114
+ return _dynamicAliases;
115
+ }
116
+
117
+ async function resolveVersion(versionSpec) {
118
+ if (!isVersionAlias(versionSpec)) return versionSpec;
119
+ const dynamic = await getDynamicAliases();
120
+ if (dynamic?.[versionSpec]) return dynamic[versionSpec];
121
+ return _fallbackAliases[versionSpec] ?? versionSpec;
42
122
  }
43
123
 
44
- function getVersionAliasEntries() {
45
- return Object.entries(_versionAliases);
124
+ async function getVersionAliasEntries() {
125
+ const dynamic = await getDynamicAliases();
126
+ const aliases = dynamic || _fallbackAliases;
127
+ return Object.entries(aliases);
128
+ }
129
+
130
+ /** Reset the cached dynamic aliases (for testing). */
131
+ export function _resetDynamicAliasCache() {
132
+ _dynamicAliases = undefined;
46
133
  }
47
134
 
48
135
  // ---------------------------------------------------------------------------
@@ -714,8 +801,8 @@ export const commands = {
714
801
  console.log(' --c8-version <version> Alternative flag form for version');
715
802
  console.log(' --debug Stream raw c8run output during start');
716
803
  console.log('');
717
- console.log('Version aliases:');
718
- for (const [alias, resolved] of getVersionAliasEntries()) {
804
+ console.log('Version aliases (dynamically resolved):');
805
+ for (const [alias, resolved] of await getVersionAliasEntries()) {
719
806
  console.log(` ${alias.padEnd(22)} → ${resolved}`);
720
807
  }
721
808
  console.log('');
@@ -737,7 +824,10 @@ export const commands = {
737
824
  logger.error(error.message);
738
825
  process.exit(1);
739
826
  }
740
- const version = resolveVersion(versionSpec);
827
+ const version = await resolveVersion(versionSpec);
828
+ if (isVersionAlias(versionSpec)) {
829
+ logger.info(`Resolved alias "${versionSpec}" → ${version}`);
830
+ }
741
831
  const config = { cacheDir: getCacheDir(), version, isAlias: isVersionAlias(versionSpec) };
742
832
 
743
833
  if (parsed.subcommand === 'start') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda8/cli",
3
- "version": "2.6.0-alpha.4",
3
+ "version": "2.6.0-alpha.5",
4
4
  "description": "Camunda 8 CLI - minimal-dependency CLI for Camunda 8 operations",
5
5
  "type": "module",
6
6
  "engines": {