@camunda8/cli 2.7.0-alpha.2 → 2.7.0-alpha.4
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
|
|
12
12
|
c8ctl cluster start stable
|
|
13
13
|
c8ctl cluster start alpha
|
|
14
14
|
|
|
@@ -81,14 +81,18 @@ export async function discoverLatestVersions() {
|
|
|
81
81
|
* - Minor version directories (e.g. "8.8/", "8.9/") are rolling releases
|
|
82
82
|
* updated in-place.
|
|
83
83
|
* - An alpha train exists when there are "X.Y.0-alphaN/" directories
|
|
84
|
-
* for a given minor
|
|
85
|
-
*
|
|
84
|
+
* for a given minor that has NOT yet shipped a GA release (X.Y.0/).
|
|
85
|
+
* The highest such minor is the alpha train.
|
|
86
|
+
* - stable = highest minor that is NOT in the alpha train.
|
|
87
|
+
* - alpha = highest minor overall (regardless of alpha train membership).
|
|
86
88
|
*/
|
|
87
89
|
export function parseVersionsFromHtml(html) {
|
|
88
90
|
// Match minor-version directories like "8.8/", "8.9/"
|
|
89
91
|
const minorMatches = [...html.matchAll(/href="(\d+\.\d+)\/"/g)].map(m => m[1]);
|
|
90
92
|
// Match alpha directories like "8.9.0-alpha5/"
|
|
91
93
|
const alphaMatches = [...html.matchAll(/href="(\d+\.\d+)\.0-alpha\d+\/"/g)].map(m => m[1]);
|
|
94
|
+
// Match GA release directories like "8.9.0/"
|
|
95
|
+
const gaMatches = [...html.matchAll(/href="(\d+\.\d+)\.0\/"/g)].map(m => m[1]);
|
|
92
96
|
|
|
93
97
|
if (minorMatches.length === 0) return null;
|
|
94
98
|
|
|
@@ -99,22 +103,17 @@ export function parseVersionsFromHtml(html) {
|
|
|
99
103
|
};
|
|
100
104
|
|
|
101
105
|
const sortedMinors = [...new Set(minorMatches)].sort(compareSemver);
|
|
102
|
-
const
|
|
106
|
+
const gaSet = new Set(gaMatches);
|
|
107
|
+
|
|
108
|
+
// A minor is only in the alpha train if it has alpha dirs but NO GA release yet.
|
|
109
|
+
// Once X.Y.0/ exists, that minor has graduated.
|
|
110
|
+
const alphaOnlyMinors = [...new Set(alphaMatches)].filter(v => !gaSet.has(v));
|
|
111
|
+
const alphaSet = new Set(alphaOnlyMinors);
|
|
103
112
|
|
|
104
113
|
const highestMinor = sortedMinors[sortedMinors.length - 1];
|
|
105
114
|
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
// or the highest minor if no alpha train exists.
|
|
109
|
-
const highestAlphaMinor = [...alphaSet].sort(compareSemver).pop();
|
|
110
|
-
|
|
111
|
-
let stable;
|
|
112
|
-
if (highestAlphaMinor) {
|
|
113
|
-
// Stable = the highest minor that is lower than the alpha train
|
|
114
|
-
stable = sortedMinors.filter(v => compareSemver(v, highestAlphaMinor) < 0).pop() || highestMinor;
|
|
115
|
-
} else {
|
|
116
|
-
stable = highestMinor;
|
|
117
|
-
}
|
|
115
|
+
// Stable = highest minor that is NOT in the alpha train
|
|
116
|
+
const stable = sortedMinors.filter(v => !alphaSet.has(v)).pop() || highestMinor;
|
|
118
117
|
|
|
119
118
|
return {
|
|
120
119
|
stable,
|
|
@@ -132,24 +131,34 @@ async function getDynamicAliases() {
|
|
|
132
131
|
return _dynamicAliases;
|
|
133
132
|
}
|
|
134
133
|
|
|
135
|
-
async function resolveVersion(versionSpec, { preferLocal = false, cacheDir } = {}) {
|
|
134
|
+
export async function resolveVersion(versionSpec, { preferLocal = false, cacheDir } = {}) {
|
|
136
135
|
if (!isVersionAlias(versionSpec)) return versionSpec;
|
|
137
136
|
|
|
138
|
-
//
|
|
137
|
+
// start: use cached alias if the version is installed (deterministic)
|
|
139
138
|
if (preferLocal && cacheDir) {
|
|
140
139
|
const local = readLocalAliasMapping(cacheDir, versionSpec);
|
|
141
140
|
if (local) return local;
|
|
142
141
|
}
|
|
143
142
|
|
|
143
|
+
// Try remote discovery
|
|
144
144
|
const dynamic = await getDynamicAliases();
|
|
145
|
-
const resolved = dynamic?.[versionSpec] ?? _fallbackAliases[versionSpec] ?? versionSpec;
|
|
146
145
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
if (dynamic?.[versionSpec]) {
|
|
147
|
+
// Persist for offline use and future preferLocal lookups
|
|
148
|
+
if (cacheDir) {
|
|
149
|
+
storeLocalAliasMapping(cacheDir, versionSpec, dynamic[versionSpec]);
|
|
150
|
+
}
|
|
151
|
+
return dynamic[versionSpec];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Remote unavailable — fall back to persisted alias if the version is installed
|
|
155
|
+
if (!preferLocal && cacheDir) {
|
|
156
|
+
const local = readLocalAliasMapping(cacheDir, versionSpec);
|
|
157
|
+
if (local) return local;
|
|
150
158
|
}
|
|
151
159
|
|
|
152
|
-
|
|
160
|
+
// Last resort: hardcoded fallback from package.json
|
|
161
|
+
return _fallbackAliases[versionSpec] ?? versionSpec;
|
|
153
162
|
}
|
|
154
163
|
|
|
155
164
|
function getAliasMappingPath(cacheDir, alias) {
|
|
@@ -169,7 +178,7 @@ function readLocalAliasMapping(cacheDir, alias) {
|
|
|
169
178
|
}
|
|
170
179
|
}
|
|
171
180
|
|
|
172
|
-
function storeLocalAliasMapping(cacheDir, alias, resolved) {
|
|
181
|
+
export function storeLocalAliasMapping(cacheDir, alias, resolved) {
|
|
173
182
|
try {
|
|
174
183
|
mkdirSync(cacheDir, { recursive: true });
|
|
175
184
|
writeFileSync(getAliasMappingPath(cacheDir, alias), resolved);
|
|
@@ -184,11 +193,66 @@ async function getVersionAliasEntries() {
|
|
|
184
193
|
return Object.entries(aliases);
|
|
185
194
|
}
|
|
186
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Synchronous variant that uses only the cached discovery result or fallback.
|
|
198
|
+
* Used for help output so it never blocks on a network request.
|
|
199
|
+
*/
|
|
200
|
+
function getVersionAliasEntriesSync() {
|
|
201
|
+
const aliases = _dynamicAliases || _fallbackAliases;
|
|
202
|
+
return Object.entries(aliases);
|
|
203
|
+
}
|
|
204
|
+
|
|
187
205
|
/** Reset the cached dynamic aliases (for testing). */
|
|
188
206
|
export function _resetDynamicAliasCache() {
|
|
189
207
|
_dynamicAliases = undefined;
|
|
190
208
|
}
|
|
191
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Non-blocking background check: query the remote download server and print
|
|
212
|
+
* a hint if the remote alias resolves to a different (newer) version.
|
|
213
|
+
*
|
|
214
|
+
* Important: does NOT update the persisted alias mapping. The mapping is only
|
|
215
|
+
* updated when the user explicitly runs `cluster install`, keeping `start`
|
|
216
|
+
* deterministic across runs.
|
|
217
|
+
*
|
|
218
|
+
* Returns the fire-and-forget promise (for testing).
|
|
219
|
+
*/
|
|
220
|
+
export function checkBackgroundAliasFreshness(versionSpec, resolvedVersion) {
|
|
221
|
+
const logger = getLogger();
|
|
222
|
+
const controller = new AbortController();
|
|
223
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
224
|
+
return fireAndForget(
|
|
225
|
+
fetch(DOWNLOAD_BASE_URL, { signal: controller.signal })
|
|
226
|
+
.then((res) => res.ok ? res.text() : null)
|
|
227
|
+
.then((html) => {
|
|
228
|
+
if (html) {
|
|
229
|
+
const remote = parseVersionsFromHtml(html);
|
|
230
|
+
const remoteVersion = remote?.[versionSpec];
|
|
231
|
+
if (remoteVersion && remoteVersion !== resolvedVersion) {
|
|
232
|
+
logger.info(
|
|
233
|
+
`A newer "${versionSpec}" release is available (${remoteVersion}). ` +
|
|
234
|
+
`Install it with: c8ctl cluster install ${versionSpec}`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
.finally(() => clearTimeout(timeoutId)),
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// Helpers – fire-and-forget
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Run a promise without blocking the caller. Errors are always swallowed so
|
|
249
|
+
* fire-and-forget chains never produce unhandled-rejection warnings.
|
|
250
|
+
* Returns the guarded promise (useful in tests).
|
|
251
|
+
*/
|
|
252
|
+
function fireAndForget(promise) {
|
|
253
|
+
return promise.catch(() => {});
|
|
254
|
+
}
|
|
255
|
+
|
|
192
256
|
// ---------------------------------------------------------------------------
|
|
193
257
|
// Plugin metadata
|
|
194
258
|
// ---------------------------------------------------------------------------
|
|
@@ -626,17 +690,16 @@ export async function ensureC8RunInstalled(config) {
|
|
|
626
690
|
// For rolling versions on start, do a bounded non-blocking check and hint.
|
|
627
691
|
// We store the promise so tests can await it, but we don't block the caller.
|
|
628
692
|
if (config.checkForUpdateHint) {
|
|
629
|
-
config._hintPromise =
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
});
|
|
693
|
+
config._hintPromise = fireAndForget(
|
|
694
|
+
hasNewerVersionAvailable(config)
|
|
695
|
+
.then((hasUpdate) => {
|
|
696
|
+
if (hasUpdate) {
|
|
697
|
+
logger.info(
|
|
698
|
+
`A newer server version is available. Install it with: c8ctl cluster install ${config.version}`,
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
}),
|
|
702
|
+
);
|
|
640
703
|
}
|
|
641
704
|
|
|
642
705
|
return;
|
|
@@ -733,6 +796,7 @@ function printSummary(rawOutput, version) {
|
|
|
733
796
|
async function startC8Run(config, debug = false) {
|
|
734
797
|
const logger = getLogger();
|
|
735
798
|
const binaryPath = getC8RunBinaryPath(config);
|
|
799
|
+
const logDir = join(dirname(binaryPath), 'log');
|
|
736
800
|
|
|
737
801
|
if (!existsSync(binaryPath)) {
|
|
738
802
|
throw new Error(`c8run binary not found at ${binaryPath}`);
|
|
@@ -761,7 +825,9 @@ async function startC8Run(config, debug = false) {
|
|
|
761
825
|
});
|
|
762
826
|
|
|
763
827
|
if (typeof proc.pid !== 'number') {
|
|
764
|
-
logger.error('Failed to start cluster process: no PID received from c8run.
|
|
828
|
+
logger.error('Failed to start cluster process: no PID received from c8run.');
|
|
829
|
+
logger.error(`Check logs in: ${logDir}`);
|
|
830
|
+
logger.info(`Print logs with: cat "${logDir}"/*.log`);
|
|
765
831
|
process.exit(1);
|
|
766
832
|
}
|
|
767
833
|
|
|
@@ -818,8 +884,10 @@ async function startC8Run(config, debug = false) {
|
|
|
818
884
|
printSummary(startupOutput, config.version);
|
|
819
885
|
} else {
|
|
820
886
|
logger.error(
|
|
821
|
-
'Cluster failed to start within timeout.
|
|
887
|
+
'Cluster failed to start within timeout.',
|
|
822
888
|
);
|
|
889
|
+
logger.error(`Check logs in: ${logDir}`);
|
|
890
|
+
logger.info(`Print logs with: cat "${logDir}"/*.log`);
|
|
823
891
|
process.exit(1);
|
|
824
892
|
}
|
|
825
893
|
}
|
|
@@ -1044,7 +1112,7 @@ export async function listInstalledVersions(cacheDir) {
|
|
|
1044
1112
|
}
|
|
1045
1113
|
|
|
1046
1114
|
console.log('');
|
|
1047
|
-
console.log('Version aliases
|
|
1115
|
+
console.log('Version aliases:');
|
|
1048
1116
|
for (const [alias, resolved] of aliasEntries) {
|
|
1049
1117
|
console.log(` ${alias.padEnd(ALIAS_COLUMN_WIDTH)} → ${resolved}`);
|
|
1050
1118
|
}
|
|
@@ -1103,7 +1171,7 @@ export async function listRemoteVersions() {
|
|
|
1103
1171
|
}
|
|
1104
1172
|
|
|
1105
1173
|
console.log('');
|
|
1106
|
-
console.log('Version aliases
|
|
1174
|
+
console.log('Version aliases:');
|
|
1107
1175
|
for (const [alias, resolved] of aliasEntries) {
|
|
1108
1176
|
console.log(` ${alias.padEnd(ALIAS_COLUMN_WIDTH)} → ${resolved}`);
|
|
1109
1177
|
}
|
|
@@ -1125,9 +1193,11 @@ export async function deleteVersion(cacheDir, versionSpec) {
|
|
|
1125
1193
|
validateVersionSpec(versionSpec);
|
|
1126
1194
|
|
|
1127
1195
|
// Resolve named aliases (stable/alpha) to the actual cached version name.
|
|
1196
|
+
// Use preferLocal so we delete the version that's actually installed,
|
|
1197
|
+
// not whatever the remote currently resolves the alias to.
|
|
1128
1198
|
// Major.minor patterns like 8.8 are used as-is since the cache dir is named c8run-8.8.
|
|
1129
1199
|
const resolvedVersion = isVersionAlias(versionSpec)
|
|
1130
|
-
? await resolveVersion(versionSpec)
|
|
1200
|
+
? await resolveVersion(versionSpec, { preferLocal: true, cacheDir })
|
|
1131
1201
|
: versionSpec;
|
|
1132
1202
|
|
|
1133
1203
|
// Prevent deleting a currently running version
|
|
@@ -1319,15 +1389,15 @@ export const commands = {
|
|
|
1319
1389
|
console.log(' --debug Stream raw c8run output during start');
|
|
1320
1390
|
console.log('');
|
|
1321
1391
|
console.log('A <version> can be:');
|
|
1322
|
-
console.log(' stable / alpha Named aliases
|
|
1392
|
+
console.log(' stable / alpha Named aliases');
|
|
1323
1393
|
console.log(' 8.8, 8.9 Major.minor — rolling release for that minor');
|
|
1324
1394
|
console.log(' 8.9.0-alpha5 Exact pinned version');
|
|
1325
1395
|
console.log('');
|
|
1326
|
-
console.log(' start uses a
|
|
1396
|
+
console.log(' start uses a locally installed version if available.');
|
|
1327
1397
|
console.log(' install always checks the remote for a newer rolling release.');
|
|
1328
1398
|
console.log('');
|
|
1329
|
-
console.log('Version aliases
|
|
1330
|
-
for (const [alias, resolved] of
|
|
1399
|
+
console.log('Version aliases:');
|
|
1400
|
+
for (const [alias, resolved] of getVersionAliasEntriesSync()) {
|
|
1331
1401
|
console.log(` ${alias.padEnd(ALIAS_COLUMN_WIDTH)} → ${resolved}`);
|
|
1332
1402
|
}
|
|
1333
1403
|
console.log('');
|
|
@@ -1416,14 +1486,21 @@ export const commands = {
|
|
|
1416
1486
|
process.exit(1);
|
|
1417
1487
|
}
|
|
1418
1488
|
const theCacheDir = getCacheDir();
|
|
1489
|
+
const isStart = parsed.subcommand === 'start';
|
|
1419
1490
|
const version = await resolveVersion(versionSpec, {
|
|
1420
|
-
// start:
|
|
1421
|
-
preferLocal:
|
|
1491
|
+
// start: use cached alias if installed (deterministic behavior)
|
|
1492
|
+
preferLocal: isStart,
|
|
1422
1493
|
cacheDir: theCacheDir,
|
|
1423
1494
|
});
|
|
1424
1495
|
if (isVersionAlias(versionSpec)) {
|
|
1425
1496
|
logger.info(`Resolved alias "${versionSpec}" → ${version}`);
|
|
1426
1497
|
}
|
|
1498
|
+
|
|
1499
|
+
// For start with a named alias, check in the background whether the
|
|
1500
|
+
// remote resolves to a different (newer) version and hint if so.
|
|
1501
|
+
if (isStart && isVersionAlias(versionSpec)) {
|
|
1502
|
+
checkBackgroundAliasFreshness(versionSpec, version);
|
|
1503
|
+
}
|
|
1427
1504
|
const rolling = isRollingVersion(versionSpec);
|
|
1428
1505
|
const config = {
|
|
1429
1506
|
cacheDir: theCacheDir,
|