@robinmordasiewicz/f5xc-xcsh 2.0.21-2601100343 → 2.0.21-2601101304
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/dist/index.js +827 -67
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -7941,7 +7941,7 @@ var require_react_reconciler_development = __commonJS({
|
|
|
7941
7941
|
var ContextConsumer = 9;
|
|
7942
7942
|
var ContextProvider = 10;
|
|
7943
7943
|
var ForwardRef = 11;
|
|
7944
|
-
var
|
|
7944
|
+
var Profiler2 = 12;
|
|
7945
7945
|
var SuspenseComponent = 13;
|
|
7946
7946
|
var MemoComponent = 14;
|
|
7947
7947
|
var SimpleMemoComponent = 15;
|
|
@@ -8095,7 +8095,7 @@ var require_react_reconciler_development = __commonJS({
|
|
|
8095
8095
|
return "Mode";
|
|
8096
8096
|
case OffscreenComponent:
|
|
8097
8097
|
return "Offscreen";
|
|
8098
|
-
case
|
|
8098
|
+
case Profiler2:
|
|
8099
8099
|
return "Profiler";
|
|
8100
8100
|
case ScopeComponent:
|
|
8101
8101
|
return "Scope";
|
|
@@ -14477,7 +14477,7 @@ var require_react_reconciler_development = __commonJS({
|
|
|
14477
14477
|
var root = parentFiber.stateNode;
|
|
14478
14478
|
root.effectDuration += elapsedTime;
|
|
14479
14479
|
return;
|
|
14480
|
-
case
|
|
14480
|
+
case Profiler2:
|
|
14481
14481
|
var parentStateNode = parentFiber.stateNode;
|
|
14482
14482
|
parentStateNode.effectDuration += elapsedTime;
|
|
14483
14483
|
return;
|
|
@@ -14499,7 +14499,7 @@ var require_react_reconciler_development = __commonJS({
|
|
|
14499
14499
|
root.passiveEffectDuration += elapsedTime;
|
|
14500
14500
|
}
|
|
14501
14501
|
return;
|
|
14502
|
-
case
|
|
14502
|
+
case Profiler2:
|
|
14503
14503
|
var parentStateNode = parentFiber.stateNode;
|
|
14504
14504
|
if (parentStateNode !== null) {
|
|
14505
14505
|
parentStateNode.passiveEffectDuration += elapsedTime;
|
|
@@ -16964,7 +16964,7 @@ var require_react_reconciler_development = __commonJS({
|
|
|
16964
16964
|
pushProvider(workInProgress2, context, newValue);
|
|
16965
16965
|
break;
|
|
16966
16966
|
}
|
|
16967
|
-
case
|
|
16967
|
+
case Profiler2:
|
|
16968
16968
|
{
|
|
16969
16969
|
var hasChildWork = includesSomeLane(renderLanes2, workInProgress2.childLanes);
|
|
16970
16970
|
if (hasChildWork) {
|
|
@@ -17108,7 +17108,7 @@ var require_react_reconciler_development = __commonJS({
|
|
|
17108
17108
|
return updateFragment(current2, workInProgress2, renderLanes2);
|
|
17109
17109
|
case Mode:
|
|
17110
17110
|
return updateMode(current2, workInProgress2, renderLanes2);
|
|
17111
|
-
case
|
|
17111
|
+
case Profiler2:
|
|
17112
17112
|
return updateProfiler(current2, workInProgress2, renderLanes2);
|
|
17113
17113
|
case ContextProvider:
|
|
17114
17114
|
return updateContextProvider(current2, workInProgress2, renderLanes2);
|
|
@@ -17543,7 +17543,7 @@ var require_react_reconciler_development = __commonJS({
|
|
|
17543
17543
|
case ForwardRef:
|
|
17544
17544
|
case Fragment2:
|
|
17545
17545
|
case Mode:
|
|
17546
|
-
case
|
|
17546
|
+
case Profiler2:
|
|
17547
17547
|
case ContextConsumer:
|
|
17548
17548
|
case MemoComponent:
|
|
17549
17549
|
bubbleProperties(workInProgress2);
|
|
@@ -18383,7 +18383,7 @@ var require_react_reconciler_development = __commonJS({
|
|
|
18383
18383
|
{
|
|
18384
18384
|
if ((finishedWork.flags & Update) !== NoFlags) {
|
|
18385
18385
|
switch (finishedWork.tag) {
|
|
18386
|
-
case
|
|
18386
|
+
case Profiler2: {
|
|
18387
18387
|
var passiveEffectDuration = finishedWork.stateNode.passiveEffectDuration;
|
|
18388
18388
|
var _finishedWork$memoize = finishedWork.memoizedProps, id = _finishedWork$memoize.id, onPostCommit = _finishedWork$memoize.onPostCommit;
|
|
18389
18389
|
var commitTime2 = getCommitTime();
|
|
@@ -18403,7 +18403,7 @@ var require_react_reconciler_development = __commonJS({
|
|
|
18403
18403
|
var root = parentFiber.stateNode;
|
|
18404
18404
|
root.passiveEffectDuration += passiveEffectDuration;
|
|
18405
18405
|
break outer;
|
|
18406
|
-
case
|
|
18406
|
+
case Profiler2:
|
|
18407
18407
|
var parentStateNode = parentFiber.stateNode;
|
|
18408
18408
|
parentStateNode.passiveEffectDuration += passiveEffectDuration;
|
|
18409
18409
|
break outer;
|
|
@@ -18536,7 +18536,7 @@ var require_react_reconciler_development = __commonJS({
|
|
|
18536
18536
|
case HostPortal: {
|
|
18537
18537
|
break;
|
|
18538
18538
|
}
|
|
18539
|
-
case
|
|
18539
|
+
case Profiler2: {
|
|
18540
18540
|
{
|
|
18541
18541
|
var _finishedWork$memoize2 = finishedWork.memoizedProps, onCommit = _finishedWork$memoize2.onCommit, onRender = _finishedWork$memoize2.onRender;
|
|
18542
18542
|
var effectDuration = finishedWork.stateNode.effectDuration;
|
|
@@ -18562,7 +18562,7 @@ var require_react_reconciler_development = __commonJS({
|
|
|
18562
18562
|
var root = parentFiber.stateNode;
|
|
18563
18563
|
root.effectDuration += effectDuration;
|
|
18564
18564
|
break outer;
|
|
18565
|
-
case
|
|
18565
|
+
case Profiler2:
|
|
18566
18566
|
var parentStateNode = parentFiber.stateNode;
|
|
18567
18567
|
parentStateNode.effectDuration += effectDuration;
|
|
18568
18568
|
break outer;
|
|
@@ -22319,7 +22319,7 @@ var require_react_reconciler_development = __commonJS({
|
|
|
22319
22319
|
error('Profiler must specify an "id" of type `string` as a prop. Received the type `%s` instead.', typeof pendingProps.id);
|
|
22320
22320
|
}
|
|
22321
22321
|
}
|
|
22322
|
-
var fiber = createFiber(
|
|
22322
|
+
var fiber = createFiber(Profiler2, pendingProps, key, mode | ProfileMode);
|
|
22323
22323
|
fiber.elementType = REACT_PROFILER_TYPE;
|
|
22324
22324
|
fiber.lanes = lanes;
|
|
22325
22325
|
{
|
|
@@ -40058,6 +40058,582 @@ var require_cli_spinners = __commonJS({
|
|
|
40058
40058
|
}
|
|
40059
40059
|
});
|
|
40060
40060
|
|
|
40061
|
+
// src/profiling/profiler.ts
|
|
40062
|
+
var DEFAULT_THRESHOLDS = {
|
|
40063
|
+
slowPhaseMs: 100,
|
|
40064
|
+
slowNetworkMs: 500,
|
|
40065
|
+
memorySpikeMB: 50
|
|
40066
|
+
};
|
|
40067
|
+
function getProfileLevel() {
|
|
40068
|
+
const level = process.env.XCSH_PROFILE_LEVEL;
|
|
40069
|
+
if (level === "basic" || level === "detailed" || level === "full") {
|
|
40070
|
+
return level;
|
|
40071
|
+
}
|
|
40072
|
+
if (process.env.XCSH_PROFILE === "true") {
|
|
40073
|
+
return "basic";
|
|
40074
|
+
}
|
|
40075
|
+
return "none";
|
|
40076
|
+
}
|
|
40077
|
+
var Profiler = class {
|
|
40078
|
+
config;
|
|
40079
|
+
spans = /* @__PURE__ */ new Map();
|
|
40080
|
+
networkSpans = /* @__PURE__ */ new Map();
|
|
40081
|
+
memorySnapshots = [];
|
|
40082
|
+
startTime;
|
|
40083
|
+
spanCounter = 0;
|
|
40084
|
+
rootSpanId = null;
|
|
40085
|
+
constructor(config) {
|
|
40086
|
+
const level = getProfileLevel();
|
|
40087
|
+
this.config = {
|
|
40088
|
+
enabled: level !== "none",
|
|
40089
|
+
level,
|
|
40090
|
+
thresholds: {
|
|
40091
|
+
...DEFAULT_THRESHOLDS,
|
|
40092
|
+
...config?.thresholds
|
|
40093
|
+
}
|
|
40094
|
+
};
|
|
40095
|
+
this.startTime = Date.now();
|
|
40096
|
+
if (this.isEnabled()) {
|
|
40097
|
+
this.memorySnapshot("profiler_init");
|
|
40098
|
+
}
|
|
40099
|
+
}
|
|
40100
|
+
/**
|
|
40101
|
+
* Check if profiling is enabled
|
|
40102
|
+
*/
|
|
40103
|
+
isEnabled() {
|
|
40104
|
+
return this.config.enabled;
|
|
40105
|
+
}
|
|
40106
|
+
/**
|
|
40107
|
+
* Get current profiling level
|
|
40108
|
+
*/
|
|
40109
|
+
getLevel() {
|
|
40110
|
+
return this.config.level;
|
|
40111
|
+
}
|
|
40112
|
+
/**
|
|
40113
|
+
* Get elapsed time since profiler start
|
|
40114
|
+
*/
|
|
40115
|
+
elapsed() {
|
|
40116
|
+
return Date.now() - this.startTime;
|
|
40117
|
+
}
|
|
40118
|
+
/**
|
|
40119
|
+
* Generate unique span ID
|
|
40120
|
+
*/
|
|
40121
|
+
generateSpanId() {
|
|
40122
|
+
return `span_${++this.spanCounter}`;
|
|
40123
|
+
}
|
|
40124
|
+
/**
|
|
40125
|
+
* Start a new profiling span
|
|
40126
|
+
*/
|
|
40127
|
+
startSpan(name, label, parentId) {
|
|
40128
|
+
if (!this.isEnabled()) return "";
|
|
40129
|
+
const id = this.generateSpanId();
|
|
40130
|
+
const span = {
|
|
40131
|
+
id,
|
|
40132
|
+
name,
|
|
40133
|
+
label: label ?? name,
|
|
40134
|
+
parentId: parentId ?? void 0,
|
|
40135
|
+
startTime: this.elapsed(),
|
|
40136
|
+
endTime: void 0,
|
|
40137
|
+
duration: void 0,
|
|
40138
|
+
metadata: {},
|
|
40139
|
+
isNetwork: void 0,
|
|
40140
|
+
children: []
|
|
40141
|
+
};
|
|
40142
|
+
this.spans.set(id, span);
|
|
40143
|
+
if (!parentId && !this.rootSpanId) {
|
|
40144
|
+
this.rootSpanId = id;
|
|
40145
|
+
}
|
|
40146
|
+
if (parentId) {
|
|
40147
|
+
const parent = this.spans.get(parentId);
|
|
40148
|
+
if (parent) {
|
|
40149
|
+
parent.children.push(span);
|
|
40150
|
+
}
|
|
40151
|
+
}
|
|
40152
|
+
if (this.config.level === "basic") {
|
|
40153
|
+
console.error(`[PROFILE] ${name}:start: ${span.startTime}ms`);
|
|
40154
|
+
}
|
|
40155
|
+
return id;
|
|
40156
|
+
}
|
|
40157
|
+
/**
|
|
40158
|
+
* End a profiling span
|
|
40159
|
+
*/
|
|
40160
|
+
endSpan(spanId, metadata) {
|
|
40161
|
+
if (!this.isEnabled() || !spanId) return;
|
|
40162
|
+
const span = this.spans.get(spanId);
|
|
40163
|
+
if (!span) return;
|
|
40164
|
+
span.endTime = this.elapsed();
|
|
40165
|
+
span.duration = span.endTime - span.startTime;
|
|
40166
|
+
if (metadata) {
|
|
40167
|
+
span.metadata = { ...span.metadata, ...metadata };
|
|
40168
|
+
}
|
|
40169
|
+
if (this.config.level === "basic") {
|
|
40170
|
+
console.error(
|
|
40171
|
+
`[PROFILE] ${span.name}:end: ${span.endTime}ms (${span.duration}ms)`
|
|
40172
|
+
);
|
|
40173
|
+
}
|
|
40174
|
+
}
|
|
40175
|
+
/**
|
|
40176
|
+
* Create a checkpoint within current context (simple timing marker)
|
|
40177
|
+
*/
|
|
40178
|
+
checkpoint(name) {
|
|
40179
|
+
if (!this.isEnabled()) return;
|
|
40180
|
+
const elapsed = this.elapsed();
|
|
40181
|
+
if (this.config.level === "basic") {
|
|
40182
|
+
console.error(`[PROFILE] ${name}: ${elapsed}ms`);
|
|
40183
|
+
}
|
|
40184
|
+
}
|
|
40185
|
+
/**
|
|
40186
|
+
* Start a network span with additional request details
|
|
40187
|
+
*/
|
|
40188
|
+
startNetworkSpan(url, method, parentId) {
|
|
40189
|
+
if (!this.isEnabled()) return "";
|
|
40190
|
+
const id = this.generateSpanId();
|
|
40191
|
+
const span = {
|
|
40192
|
+
id,
|
|
40193
|
+
name: `network:${method}`,
|
|
40194
|
+
label: `${method} ${this.truncateUrl(url)}`,
|
|
40195
|
+
parentId: parentId ?? void 0,
|
|
40196
|
+
startTime: this.elapsed(),
|
|
40197
|
+
endTime: void 0,
|
|
40198
|
+
duration: void 0,
|
|
40199
|
+
metadata: {},
|
|
40200
|
+
children: [],
|
|
40201
|
+
isNetwork: true,
|
|
40202
|
+
url,
|
|
40203
|
+
method,
|
|
40204
|
+
statusCode: void 0,
|
|
40205
|
+
requestSize: void 0,
|
|
40206
|
+
responseSize: void 0,
|
|
40207
|
+
retryCount: 0,
|
|
40208
|
+
error: void 0
|
|
40209
|
+
};
|
|
40210
|
+
this.networkSpans.set(id, span);
|
|
40211
|
+
this.spans.set(id, span);
|
|
40212
|
+
if (parentId) {
|
|
40213
|
+
const parent = this.spans.get(parentId);
|
|
40214
|
+
if (parent) {
|
|
40215
|
+
parent.children.push(span);
|
|
40216
|
+
}
|
|
40217
|
+
}
|
|
40218
|
+
if (this.config.level !== "none") {
|
|
40219
|
+
console.error(
|
|
40220
|
+
`[PROFILE] network:${method}:start: ${span.startTime}ms - ${this.truncateUrl(url)}`
|
|
40221
|
+
);
|
|
40222
|
+
}
|
|
40223
|
+
return id;
|
|
40224
|
+
}
|
|
40225
|
+
/**
|
|
40226
|
+
* End a network span with response details
|
|
40227
|
+
*/
|
|
40228
|
+
endNetworkSpan(spanId, details) {
|
|
40229
|
+
if (!this.isEnabled() || !spanId) return;
|
|
40230
|
+
const span = this.networkSpans.get(spanId);
|
|
40231
|
+
if (!span) return;
|
|
40232
|
+
span.endTime = this.elapsed();
|
|
40233
|
+
span.duration = span.endTime - span.startTime;
|
|
40234
|
+
span.statusCode = details.statusCode;
|
|
40235
|
+
span.responseSize = details.responseSize;
|
|
40236
|
+
span.retryCount = details.retryCount ?? 0;
|
|
40237
|
+
span.error = details.error;
|
|
40238
|
+
if (this.config.level !== "none") {
|
|
40239
|
+
const status = details.error ? `ERROR: ${details.error}` : `${details.statusCode ?? "?"}`;
|
|
40240
|
+
console.error(
|
|
40241
|
+
`[PROFILE] network:end: ${span.endTime}ms (${span.duration}ms) - ${status}`
|
|
40242
|
+
);
|
|
40243
|
+
}
|
|
40244
|
+
}
|
|
40245
|
+
/**
|
|
40246
|
+
* Take a memory snapshot
|
|
40247
|
+
*/
|
|
40248
|
+
memorySnapshot(label) {
|
|
40249
|
+
if (!this.isEnabled()) return;
|
|
40250
|
+
if (this.config.level === "basic") return;
|
|
40251
|
+
const memory = process.memoryUsage();
|
|
40252
|
+
const snapshot = {
|
|
40253
|
+
timestamp: this.elapsed(),
|
|
40254
|
+
label,
|
|
40255
|
+
heapUsedMB: Math.round(memory.heapUsed / 1024 / 1024 * 100) / 100,
|
|
40256
|
+
heapTotalMB: Math.round(memory.heapTotal / 1024 / 1024 * 100) / 100,
|
|
40257
|
+
externalMB: Math.round(memory.external / 1024 / 1024 * 100) / 100,
|
|
40258
|
+
rssMB: Math.round(memory.rss / 1024 / 1024 * 100) / 100
|
|
40259
|
+
};
|
|
40260
|
+
this.memorySnapshots.push(snapshot);
|
|
40261
|
+
if (this.config.level === "detailed" || this.config.level === "full") {
|
|
40262
|
+
console.error(
|
|
40263
|
+
`[PROFILE] memory:${label}: heap=${snapshot.heapUsedMB}MB rss=${snapshot.rssMB}MB`
|
|
40264
|
+
);
|
|
40265
|
+
}
|
|
40266
|
+
}
|
|
40267
|
+
/**
|
|
40268
|
+
* Identify bottlenecks based on configured thresholds
|
|
40269
|
+
*/
|
|
40270
|
+
identifyBottlenecks() {
|
|
40271
|
+
const bottlenecks = [];
|
|
40272
|
+
for (const span of this.spans.values()) {
|
|
40273
|
+
if (!span.duration) continue;
|
|
40274
|
+
const isNetwork = this.networkSpans.has(span.id);
|
|
40275
|
+
const threshold = isNetwork ? this.config.thresholds.slowNetworkMs : this.config.thresholds.slowPhaseMs;
|
|
40276
|
+
if (span.duration > threshold) {
|
|
40277
|
+
const suggestion = isNetwork ? "Check network connectivity or API server status" : "Consider lazy loading or parallelizing this operation";
|
|
40278
|
+
bottlenecks.push({
|
|
40279
|
+
spanId: span.id,
|
|
40280
|
+
spanName: span.name,
|
|
40281
|
+
duration: span.duration,
|
|
40282
|
+
threshold,
|
|
40283
|
+
reason: `Duration ${span.duration}ms exceeds threshold ${threshold}ms`,
|
|
40284
|
+
suggestion
|
|
40285
|
+
});
|
|
40286
|
+
}
|
|
40287
|
+
}
|
|
40288
|
+
if (this.memorySnapshots.length >= 2) {
|
|
40289
|
+
const first = this.memorySnapshots[0];
|
|
40290
|
+
const peak = Math.max(...this.memorySnapshots.map((s) => s.rssMB));
|
|
40291
|
+
const spike = peak - (first?.rssMB ?? 0);
|
|
40292
|
+
if (spike > this.config.thresholds.memorySpikeMB) {
|
|
40293
|
+
bottlenecks.push({
|
|
40294
|
+
spanId: "memory",
|
|
40295
|
+
spanName: "memory_spike",
|
|
40296
|
+
duration: spike,
|
|
40297
|
+
threshold: this.config.thresholds.memorySpikeMB,
|
|
40298
|
+
reason: `Memory increased by ${spike.toFixed(1)}MB`,
|
|
40299
|
+
suggestion: "Review large object allocations during startup"
|
|
40300
|
+
});
|
|
40301
|
+
}
|
|
40302
|
+
}
|
|
40303
|
+
return bottlenecks.sort((a, b) => b.duration - a.duration);
|
|
40304
|
+
}
|
|
40305
|
+
/**
|
|
40306
|
+
* Get all root-level spans (no parent)
|
|
40307
|
+
*/
|
|
40308
|
+
getRootSpans() {
|
|
40309
|
+
return Array.from(this.spans.values()).filter((span) => !span.parentId);
|
|
40310
|
+
}
|
|
40311
|
+
/**
|
|
40312
|
+
* Generate complete profile report
|
|
40313
|
+
*/
|
|
40314
|
+
generateReport() {
|
|
40315
|
+
const spans = this.getRootSpans();
|
|
40316
|
+
const networkSpans = Array.from(this.networkSpans.values());
|
|
40317
|
+
const bottlenecks = this.identifyBottlenecks();
|
|
40318
|
+
const memoryStart = this.memorySnapshots[0]?.rssMB ?? 0;
|
|
40319
|
+
const memoryEnd = this.memorySnapshots[this.memorySnapshots.length - 1]?.rssMB ?? 0;
|
|
40320
|
+
const memoryPeak = Math.max(
|
|
40321
|
+
...this.memorySnapshots.map((s) => s.rssMB),
|
|
40322
|
+
0
|
|
40323
|
+
);
|
|
40324
|
+
return {
|
|
40325
|
+
startupTimeMs: this.elapsed(),
|
|
40326
|
+
spans,
|
|
40327
|
+
networkSpans,
|
|
40328
|
+
memorySnapshots: this.memorySnapshots,
|
|
40329
|
+
memoryPeakMB: memoryPeak,
|
|
40330
|
+
memoryDeltaMB: memoryEnd - memoryStart,
|
|
40331
|
+
bottlenecks,
|
|
40332
|
+
level: this.config.level
|
|
40333
|
+
};
|
|
40334
|
+
}
|
|
40335
|
+
/**
|
|
40336
|
+
* Truncate URL for display
|
|
40337
|
+
*/
|
|
40338
|
+
truncateUrl(url) {
|
|
40339
|
+
try {
|
|
40340
|
+
const parsed = new URL(url);
|
|
40341
|
+
return parsed.pathname.slice(0, 40) + (parsed.pathname.length > 40 ? "..." : "");
|
|
40342
|
+
} catch {
|
|
40343
|
+
return url.slice(0, 40);
|
|
40344
|
+
}
|
|
40345
|
+
}
|
|
40346
|
+
};
|
|
40347
|
+
var globalProfiler = null;
|
|
40348
|
+
function getProfiler() {
|
|
40349
|
+
if (!globalProfiler) {
|
|
40350
|
+
globalProfiler = new Profiler();
|
|
40351
|
+
}
|
|
40352
|
+
return globalProfiler;
|
|
40353
|
+
}
|
|
40354
|
+
function initProfiler(config) {
|
|
40355
|
+
globalProfiler = new Profiler(config);
|
|
40356
|
+
return globalProfiler;
|
|
40357
|
+
}
|
|
40358
|
+
|
|
40359
|
+
// src/profiling/reporter.ts
|
|
40360
|
+
function formatDuration(ms) {
|
|
40361
|
+
if (ms < 1e3) {
|
|
40362
|
+
return `${Math.round(ms)}ms`;
|
|
40363
|
+
}
|
|
40364
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
40365
|
+
}
|
|
40366
|
+
function formatSpanText(span, indent = 0, totalTime) {
|
|
40367
|
+
const lines = [];
|
|
40368
|
+
const prefix = " ".repeat(indent);
|
|
40369
|
+
const duration = span.duration ?? 0;
|
|
40370
|
+
const percentage = totalTime > 0 ? (duration / totalTime * 100).toFixed(1) : "0";
|
|
40371
|
+
const networkTag = span.isNetwork ? " [N]" : "";
|
|
40372
|
+
const warning = duration > 500 ? " !!" : duration > 100 ? " !" : "";
|
|
40373
|
+
lines.push(
|
|
40374
|
+
`${prefix}${span.label}${networkTag}: ${formatDuration(duration)} (${percentage}%)${warning}`
|
|
40375
|
+
);
|
|
40376
|
+
if (Object.keys(span.metadata).length > 0) {
|
|
40377
|
+
const metaStr = Object.entries(span.metadata).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(", ");
|
|
40378
|
+
lines.push(`${prefix} metadata: ${metaStr}`);
|
|
40379
|
+
}
|
|
40380
|
+
for (const child of span.children) {
|
|
40381
|
+
lines.push(...formatSpanText(child, indent + 1, totalTime));
|
|
40382
|
+
}
|
|
40383
|
+
return lines;
|
|
40384
|
+
}
|
|
40385
|
+
function formatBottlenecksText(bottlenecks) {
|
|
40386
|
+
if (bottlenecks.length === 0) {
|
|
40387
|
+
return [" No bottlenecks detected"];
|
|
40388
|
+
}
|
|
40389
|
+
const lines = [];
|
|
40390
|
+
for (let i = 0; i < bottlenecks.length; i++) {
|
|
40391
|
+
const b = bottlenecks[i];
|
|
40392
|
+
if (!b) continue;
|
|
40393
|
+
lines.push(` ${i + 1}. ${b.spanName}: ${formatDuration(b.duration)}`);
|
|
40394
|
+
lines.push(` Threshold: ${formatDuration(b.threshold)}`);
|
|
40395
|
+
lines.push(` Suggestion: ${b.suggestion}`);
|
|
40396
|
+
}
|
|
40397
|
+
return lines;
|
|
40398
|
+
}
|
|
40399
|
+
function formatMemoryText(snapshots) {
|
|
40400
|
+
if (snapshots.length === 0) {
|
|
40401
|
+
return [" No memory snapshots"];
|
|
40402
|
+
}
|
|
40403
|
+
const first = snapshots[0];
|
|
40404
|
+
const last = snapshots[snapshots.length - 1];
|
|
40405
|
+
const peak = Math.max(...snapshots.map((s) => s.rssMB));
|
|
40406
|
+
const lines = [];
|
|
40407
|
+
lines.push(
|
|
40408
|
+
` Start: ${first?.rssMB.toFixed(1)}MB -> Peak: ${peak.toFixed(1)}MB -> End: ${last?.rssMB.toFixed(1)}MB`
|
|
40409
|
+
);
|
|
40410
|
+
lines.push(
|
|
40411
|
+
` Delta: ${((last?.rssMB ?? 0) - (first?.rssMB ?? 0)).toFixed(1)}MB`
|
|
40412
|
+
);
|
|
40413
|
+
return lines;
|
|
40414
|
+
}
|
|
40415
|
+
function formatReportText(report) {
|
|
40416
|
+
const lines = [];
|
|
40417
|
+
lines.push("=".repeat(60));
|
|
40418
|
+
lines.push(`XCSH Startup Profile Report`);
|
|
40419
|
+
lines.push(`Total Time: ${formatDuration(report.startupTimeMs)}`);
|
|
40420
|
+
lines.push(`Profile Level: ${report.level}`);
|
|
40421
|
+
lines.push("=".repeat(60));
|
|
40422
|
+
lines.push("");
|
|
40423
|
+
lines.push("PHASES:");
|
|
40424
|
+
lines.push("-".repeat(40));
|
|
40425
|
+
for (const span of report.spans) {
|
|
40426
|
+
lines.push(...formatSpanText(span, 0, report.startupTimeMs));
|
|
40427
|
+
}
|
|
40428
|
+
if (report.networkSpans.length > 0) {
|
|
40429
|
+
lines.push("");
|
|
40430
|
+
lines.push("NETWORK CALLS:");
|
|
40431
|
+
lines.push("-".repeat(40));
|
|
40432
|
+
for (const span of report.networkSpans) {
|
|
40433
|
+
const status = span.error ? `ERROR: ${span.error}` : `${span.statusCode ?? "?"}`;
|
|
40434
|
+
const retries = span.retryCount > 0 ? ` (${span.retryCount} retries)` : "";
|
|
40435
|
+
lines.push(
|
|
40436
|
+
` ${span.method} ${span.url.slice(0, 50)}: ${formatDuration(span.duration ?? 0)} - ${status}${retries}`
|
|
40437
|
+
);
|
|
40438
|
+
}
|
|
40439
|
+
}
|
|
40440
|
+
if (report.memorySnapshots.length > 0) {
|
|
40441
|
+
lines.push("");
|
|
40442
|
+
lines.push("MEMORY:");
|
|
40443
|
+
lines.push("-".repeat(40));
|
|
40444
|
+
lines.push(...formatMemoryText(report.memorySnapshots));
|
|
40445
|
+
}
|
|
40446
|
+
if (report.bottlenecks.length > 0) {
|
|
40447
|
+
lines.push("");
|
|
40448
|
+
lines.push("BOTTLENECKS:");
|
|
40449
|
+
lines.push("-".repeat(40));
|
|
40450
|
+
lines.push(...formatBottlenecksText(report.bottlenecks));
|
|
40451
|
+
}
|
|
40452
|
+
lines.push("");
|
|
40453
|
+
lines.push("=".repeat(60));
|
|
40454
|
+
lines.push(
|
|
40455
|
+
"[N] = Network call !! = Critical (>500ms) ! = Warning (>100ms)"
|
|
40456
|
+
);
|
|
40457
|
+
lines.push("=".repeat(60));
|
|
40458
|
+
return lines.join("\n");
|
|
40459
|
+
}
|
|
40460
|
+
|
|
40461
|
+
// src/profiling/waterfall.ts
|
|
40462
|
+
var BOX_CHARS = {
|
|
40463
|
+
topLeft: "\u250C",
|
|
40464
|
+
topRight: "\u2510",
|
|
40465
|
+
bottomLeft: "\u2514",
|
|
40466
|
+
bottomRight: "\u2518",
|
|
40467
|
+
horizontal: "\u2500",
|
|
40468
|
+
vertical: "\u2502",
|
|
40469
|
+
cross: "\u253C",
|
|
40470
|
+
teeDown: "\u252C",
|
|
40471
|
+
teeUp: "\u2534",
|
|
40472
|
+
teeRight: "\u251C",
|
|
40473
|
+
teeLeft: "\u2524"
|
|
40474
|
+
};
|
|
40475
|
+
var TREE_CHARS = {
|
|
40476
|
+
branch: "\u251C",
|
|
40477
|
+
last: "\u2514",
|
|
40478
|
+
vertical: "\u2502",
|
|
40479
|
+
horizontal: "\u2500"
|
|
40480
|
+
};
|
|
40481
|
+
function flattenSpans(spans, depth = 0, bottleneckIds) {
|
|
40482
|
+
const result = [];
|
|
40483
|
+
for (let i = 0; i < spans.length; i++) {
|
|
40484
|
+
const span = spans[i];
|
|
40485
|
+
if (!span) continue;
|
|
40486
|
+
const isLast = i === spans.length - 1;
|
|
40487
|
+
result.push({
|
|
40488
|
+
name: span.name,
|
|
40489
|
+
label: span.label,
|
|
40490
|
+
startTime: span.startTime,
|
|
40491
|
+
duration: span.duration ?? 0,
|
|
40492
|
+
depth,
|
|
40493
|
+
isNetwork: span.isNetwork ?? false,
|
|
40494
|
+
isBottleneck: bottleneckIds.has(span.id),
|
|
40495
|
+
isLast
|
|
40496
|
+
});
|
|
40497
|
+
if (span.children.length > 0) {
|
|
40498
|
+
result.push(
|
|
40499
|
+
...flattenSpans(span.children, depth + 1, bottleneckIds)
|
|
40500
|
+
);
|
|
40501
|
+
}
|
|
40502
|
+
}
|
|
40503
|
+
return result;
|
|
40504
|
+
}
|
|
40505
|
+
function createTreePrefix(depth, isLast) {
|
|
40506
|
+
if (depth === 0) return "";
|
|
40507
|
+
const indent = " ".repeat(depth - 1);
|
|
40508
|
+
const branch = isLast ? TREE_CHARS.last : TREE_CHARS.branch;
|
|
40509
|
+
return indent + branch + TREE_CHARS.horizontal;
|
|
40510
|
+
}
|
|
40511
|
+
function renderBar(startTime, duration, totalTime, barWidth) {
|
|
40512
|
+
if (totalTime === 0) return " ".repeat(barWidth);
|
|
40513
|
+
const startPos = Math.floor(startTime / totalTime * barWidth);
|
|
40514
|
+
const barLen = Math.max(1, Math.ceil(duration / totalTime * barWidth));
|
|
40515
|
+
const before = " ".repeat(Math.min(startPos, barWidth));
|
|
40516
|
+
const bar = "\u2588".repeat(Math.min(barLen, barWidth - startPos));
|
|
40517
|
+
const after = " ".repeat(Math.max(0, barWidth - startPos - barLen));
|
|
40518
|
+
return (before + bar + after).slice(0, barWidth);
|
|
40519
|
+
}
|
|
40520
|
+
function formatDuration2(ms) {
|
|
40521
|
+
if (ms < 1e3) {
|
|
40522
|
+
return `${Math.round(ms)}ms`;
|
|
40523
|
+
}
|
|
40524
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
40525
|
+
}
|
|
40526
|
+
function renderWaterfall(report) {
|
|
40527
|
+
const lines = [];
|
|
40528
|
+
const totalTime = report.startupTimeMs;
|
|
40529
|
+
const labelWidth = 28;
|
|
40530
|
+
const barWidth = 35;
|
|
40531
|
+
const timeWidth = 10;
|
|
40532
|
+
const totalWidth = labelWidth + barWidth + timeWidth + 6;
|
|
40533
|
+
const bottleneckIds = new Set(report.bottlenecks.map((b) => b.spanId));
|
|
40534
|
+
const flatSpans = flattenSpans(report.spans, 0, bottleneckIds);
|
|
40535
|
+
lines.push(
|
|
40536
|
+
BOX_CHARS.topLeft + BOX_CHARS.horizontal.repeat(totalWidth - 2) + BOX_CHARS.topRight
|
|
40537
|
+
);
|
|
40538
|
+
lines.push(
|
|
40539
|
+
BOX_CHARS.vertical + ` XCSH Startup Profile`.padEnd(totalWidth - 14) + `Total: ${formatDuration2(totalTime)}`.padStart(12) + BOX_CHARS.vertical
|
|
40540
|
+
);
|
|
40541
|
+
lines.push(
|
|
40542
|
+
BOX_CHARS.teeRight + BOX_CHARS.horizontal.repeat(totalWidth - 2) + BOX_CHARS.teeLeft
|
|
40543
|
+
);
|
|
40544
|
+
const headerPhase = "Phase".padEnd(labelWidth);
|
|
40545
|
+
const headerTimeline = "Timeline".padEnd(barWidth);
|
|
40546
|
+
const headerTime = "Time".padStart(timeWidth);
|
|
40547
|
+
lines.push(
|
|
40548
|
+
BOX_CHARS.vertical + ` ${headerPhase}${BOX_CHARS.vertical} ${headerTimeline}${BOX_CHARS.vertical}${headerTime} ` + BOX_CHARS.vertical
|
|
40549
|
+
);
|
|
40550
|
+
lines.push(
|
|
40551
|
+
BOX_CHARS.teeRight + BOX_CHARS.horizontal.repeat(labelWidth + 1) + BOX_CHARS.cross + BOX_CHARS.horizontal.repeat(barWidth + 1) + BOX_CHARS.cross + BOX_CHARS.horizontal.repeat(timeWidth + 1) + BOX_CHARS.teeLeft
|
|
40552
|
+
);
|
|
40553
|
+
for (const span of flatSpans) {
|
|
40554
|
+
const prefix = createTreePrefix(span.depth, span.isLast);
|
|
40555
|
+
const networkTag = span.isNetwork ? " [N]" : "";
|
|
40556
|
+
const label = (prefix + span.label + networkTag).slice(0, labelWidth).padEnd(labelWidth);
|
|
40557
|
+
const bar = renderBar(
|
|
40558
|
+
span.startTime,
|
|
40559
|
+
span.duration,
|
|
40560
|
+
totalTime,
|
|
40561
|
+
barWidth
|
|
40562
|
+
);
|
|
40563
|
+
const time = formatDuration2(span.duration).padStart(timeWidth - 2);
|
|
40564
|
+
const warning = span.isBottleneck ? " !!" : span.duration > 100 ? " !" : " ";
|
|
40565
|
+
lines.push(
|
|
40566
|
+
BOX_CHARS.vertical + ` ${label}${BOX_CHARS.vertical} ${bar}${BOX_CHARS.vertical}${time}${warning}` + BOX_CHARS.vertical
|
|
40567
|
+
);
|
|
40568
|
+
}
|
|
40569
|
+
lines.push(
|
|
40570
|
+
BOX_CHARS.bottomLeft + BOX_CHARS.horizontal.repeat(totalWidth - 2) + BOX_CHARS.bottomRight
|
|
40571
|
+
);
|
|
40572
|
+
lines.push(
|
|
40573
|
+
"[N] = Network call !! = Bottleneck (>500ms) ! = Slow (>100ms)"
|
|
40574
|
+
);
|
|
40575
|
+
if (report.memorySnapshots.length > 0) {
|
|
40576
|
+
const first = report.memorySnapshots[0];
|
|
40577
|
+
const last = report.memorySnapshots[report.memorySnapshots.length - 1];
|
|
40578
|
+
lines.push("");
|
|
40579
|
+
lines.push(
|
|
40580
|
+
`Memory: ${first?.rssMB.toFixed(1)}MB -> Peak ${report.memoryPeakMB.toFixed(1)}MB -> ${last?.rssMB.toFixed(1)}MB (${report.memoryDeltaMB >= 0 ? "+" : ""}${report.memoryDeltaMB.toFixed(1)}MB)`
|
|
40581
|
+
);
|
|
40582
|
+
}
|
|
40583
|
+
if (report.bottlenecks.length > 0) {
|
|
40584
|
+
lines.push("");
|
|
40585
|
+
lines.push("Bottlenecks Identified:");
|
|
40586
|
+
for (let i = 0; i < Math.min(report.bottlenecks.length, 3); i++) {
|
|
40587
|
+
const b = report.bottlenecks[i];
|
|
40588
|
+
if (!b) continue;
|
|
40589
|
+
lines.push(
|
|
40590
|
+
` ${i + 1}. ${b.spanName}: ${formatDuration2(b.duration)} - ${b.suggestion}`
|
|
40591
|
+
);
|
|
40592
|
+
}
|
|
40593
|
+
}
|
|
40594
|
+
return lines.join("\n");
|
|
40595
|
+
}
|
|
40596
|
+
function renderCompactWaterfall(report) {
|
|
40597
|
+
const lines = [];
|
|
40598
|
+
const totalTime = report.startupTimeMs;
|
|
40599
|
+
lines.push(`
|
|
40600
|
+
[PROFILE] Startup: ${formatDuration2(totalTime)}`);
|
|
40601
|
+
for (const span of report.spans) {
|
|
40602
|
+
const duration = span.duration ?? 0;
|
|
40603
|
+
const percentage = (duration / totalTime * 100).toFixed(0);
|
|
40604
|
+
const bar = "\u2588".repeat(
|
|
40605
|
+
Math.min(20, Math.ceil(duration / totalTime * 20))
|
|
40606
|
+
);
|
|
40607
|
+
lines.push(
|
|
40608
|
+
` ${span.label.padEnd(20)} ${bar.padEnd(20)} ${formatDuration2(duration).padStart(8)} (${percentage}%)`
|
|
40609
|
+
);
|
|
40610
|
+
}
|
|
40611
|
+
return lines.join("\n");
|
|
40612
|
+
}
|
|
40613
|
+
|
|
40614
|
+
// src/profiling/index.ts
|
|
40615
|
+
function printProfileReport() {
|
|
40616
|
+
const profiler2 = getProfiler();
|
|
40617
|
+
if (!profiler2.isEnabled()) return;
|
|
40618
|
+
const report = profiler2.generateReport();
|
|
40619
|
+
const level = profiler2.getLevel();
|
|
40620
|
+
let output;
|
|
40621
|
+
switch (level) {
|
|
40622
|
+
case "full":
|
|
40623
|
+
output = renderWaterfall(report);
|
|
40624
|
+
break;
|
|
40625
|
+
case "detailed":
|
|
40626
|
+
output = formatReportText(report);
|
|
40627
|
+
break;
|
|
40628
|
+
case "basic":
|
|
40629
|
+
output = renderCompactWaterfall(report);
|
|
40630
|
+
break;
|
|
40631
|
+
default:
|
|
40632
|
+
return;
|
|
40633
|
+
}
|
|
40634
|
+
console.error("\n" + output);
|
|
40635
|
+
}
|
|
40636
|
+
|
|
40061
40637
|
// node_modules/ink/build/render.js
|
|
40062
40638
|
import { Stream } from "stream";
|
|
40063
40639
|
import process12 from "process";
|
|
@@ -48639,8 +49215,8 @@ function getLogoModeFromEnv(envPrefix) {
|
|
|
48639
49215
|
var CLI_NAME = "xcsh";
|
|
48640
49216
|
var CLI_FULL_NAME = "F5 Distributed Cloud Shell";
|
|
48641
49217
|
function getVersion() {
|
|
48642
|
-
if ("v2.0.21-
|
|
48643
|
-
return "v2.0.21-
|
|
49218
|
+
if ("v2.0.21-2601101304") {
|
|
49219
|
+
return "v2.0.21-2601101304";
|
|
48644
49220
|
}
|
|
48645
49221
|
if (process.env.XCSH_VERSION) {
|
|
48646
49222
|
return process.env.XCSH_VERSION;
|
|
@@ -49040,6 +49616,9 @@ var DEFAULT_RETRY_CONFIG = {
|
|
|
49040
49616
|
backoffMultiplier: 2,
|
|
49041
49617
|
jitter: true
|
|
49042
49618
|
};
|
|
49619
|
+
var STARTUP_TIMEOUT = 3e3;
|
|
49620
|
+
var STARTUP_MAX_RETRIES = 0;
|
|
49621
|
+
var CONNECTIVITY_TIMEOUT = 2e3;
|
|
49043
49622
|
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([
|
|
49044
49623
|
408,
|
|
49045
49624
|
// Request Timeout
|
|
@@ -49095,18 +49674,33 @@ var APIClient = class {
|
|
|
49095
49674
|
/**
|
|
49096
49675
|
* Validate the API token by making a lightweight API call.
|
|
49097
49676
|
* Returns true if token is valid, false otherwise.
|
|
49677
|
+
*
|
|
49678
|
+
* @param options.startupMode - Use aggressive timeouts for fast-fail during startup
|
|
49098
49679
|
*/
|
|
49099
|
-
async validateToken() {
|
|
49680
|
+
async validateToken(options) {
|
|
49681
|
+
const startupMode = options?.startupMode ?? false;
|
|
49100
49682
|
if (!this.apiToken) {
|
|
49101
49683
|
this._isValidated = false;
|
|
49102
49684
|
this._validationError = "No API token configured";
|
|
49103
49685
|
return { valid: false, error: this._validationError };
|
|
49104
49686
|
}
|
|
49105
49687
|
if (this.debug) {
|
|
49106
|
-
console.error(
|
|
49688
|
+
console.error(
|
|
49689
|
+
`DEBUG: Validating token against ${this.serverUrl}${startupMode ? " (startup mode)" : ""}`
|
|
49690
|
+
);
|
|
49107
49691
|
}
|
|
49108
49692
|
try {
|
|
49109
|
-
|
|
49693
|
+
const requestOptions = {
|
|
49694
|
+
method: "GET",
|
|
49695
|
+
path: "/api/web/namespaces"
|
|
49696
|
+
};
|
|
49697
|
+
if (startupMode) {
|
|
49698
|
+
requestOptions.timeout = STARTUP_TIMEOUT;
|
|
49699
|
+
}
|
|
49700
|
+
await this.requestWithOptions(
|
|
49701
|
+
requestOptions,
|
|
49702
|
+
startupMode ? { maxRetries: STARTUP_MAX_RETRIES } : void 0
|
|
49703
|
+
);
|
|
49110
49704
|
this._isValidated = true;
|
|
49111
49705
|
this._validationError = null;
|
|
49112
49706
|
return { valid: true };
|
|
@@ -49120,6 +49714,20 @@ var APIClient = class {
|
|
|
49120
49714
|
this._isValidated = false;
|
|
49121
49715
|
this._validationError = "Token lacks required permissions";
|
|
49122
49716
|
return { valid: false, error: this._validationError };
|
|
49717
|
+
} else if (error.statusCode === 408 || error.statusCode === 0) {
|
|
49718
|
+
if (startupMode) {
|
|
49719
|
+
this._isValidated = false;
|
|
49720
|
+
this._validationError = "API endpoint unreachable - request timed out";
|
|
49721
|
+
return { valid: false, error: this._validationError };
|
|
49722
|
+
}
|
|
49723
|
+
if (this.debug) {
|
|
49724
|
+
console.error(
|
|
49725
|
+
`DEBUG: Validation timed out, assuming token is valid`
|
|
49726
|
+
);
|
|
49727
|
+
}
|
|
49728
|
+
this._isValidated = true;
|
|
49729
|
+
this._validationError = null;
|
|
49730
|
+
return { valid: true };
|
|
49123
49731
|
} else {
|
|
49124
49732
|
if (this.debug) {
|
|
49125
49733
|
console.error(
|
|
@@ -49131,6 +49739,11 @@ var APIClient = class {
|
|
|
49131
49739
|
return { valid: true };
|
|
49132
49740
|
}
|
|
49133
49741
|
} else {
|
|
49742
|
+
if (startupMode) {
|
|
49743
|
+
this._isValidated = false;
|
|
49744
|
+
this._validationError = `API endpoint unreachable - ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
49745
|
+
return { valid: false, error: this._validationError };
|
|
49746
|
+
}
|
|
49134
49747
|
if (this.debug) {
|
|
49135
49748
|
console.error(
|
|
49136
49749
|
`DEBUG: Validation error: ${error instanceof Error ? error.message : "Unknown"}, assuming token is valid`
|
|
@@ -49167,6 +49780,30 @@ var APIClient = class {
|
|
|
49167
49780
|
getServerUrl() {
|
|
49168
49781
|
return this.serverUrl;
|
|
49169
49782
|
}
|
|
49783
|
+
/**
|
|
49784
|
+
* Check connectivity to the API server.
|
|
49785
|
+
* Uses a lightweight HEAD request with aggressive timeout for fast-fail.
|
|
49786
|
+
* This is used during startup to quickly detect if API is unreachable.
|
|
49787
|
+
*/
|
|
49788
|
+
async checkConnectivity() {
|
|
49789
|
+
const start = Date.now();
|
|
49790
|
+
const controller = new AbortController();
|
|
49791
|
+
const timeoutId = setTimeout(
|
|
49792
|
+
() => controller.abort(),
|
|
49793
|
+
CONNECTIVITY_TIMEOUT
|
|
49794
|
+
);
|
|
49795
|
+
try {
|
|
49796
|
+
await fetch(this.serverUrl, {
|
|
49797
|
+
method: "HEAD",
|
|
49798
|
+
signal: controller.signal
|
|
49799
|
+
});
|
|
49800
|
+
clearTimeout(timeoutId);
|
|
49801
|
+
return { reachable: true, latencyMs: Date.now() - start };
|
|
49802
|
+
} catch {
|
|
49803
|
+
clearTimeout(timeoutId);
|
|
49804
|
+
return { reachable: false };
|
|
49805
|
+
}
|
|
49806
|
+
}
|
|
49170
49807
|
/**
|
|
49171
49808
|
* Build full URL from path and query parameters
|
|
49172
49809
|
*/
|
|
@@ -49271,11 +49908,25 @@ var APIClient = class {
|
|
|
49271
49908
|
throw error;
|
|
49272
49909
|
}
|
|
49273
49910
|
}
|
|
49911
|
+
/**
|
|
49912
|
+
* Execute an HTTP request with retry logic and optional retry override
|
|
49913
|
+
*/
|
|
49914
|
+
async requestWithOptions(options, retryOverride) {
|
|
49915
|
+
return this.requestInternal(options, retryOverride);
|
|
49916
|
+
}
|
|
49274
49917
|
/**
|
|
49275
49918
|
* Execute an HTTP request with retry logic
|
|
49276
49919
|
*/
|
|
49277
49920
|
async request(options) {
|
|
49921
|
+
return this.requestInternal(options);
|
|
49922
|
+
}
|
|
49923
|
+
/**
|
|
49924
|
+
* Internal request implementation with optional retry override
|
|
49925
|
+
*/
|
|
49926
|
+
async requestInternal(options, retryOverride) {
|
|
49278
49927
|
const url = this.buildUrl(options.path, options.query);
|
|
49928
|
+
const profiler2 = getProfiler();
|
|
49929
|
+
const networkSpan = profiler2.startNetworkSpan(url, options.method);
|
|
49279
49930
|
const headers = {
|
|
49280
49931
|
"Content-Type": "application/json",
|
|
49281
49932
|
Accept: "application/json",
|
|
@@ -49292,19 +49943,28 @@ var APIClient = class {
|
|
|
49292
49943
|
}
|
|
49293
49944
|
}
|
|
49294
49945
|
let lastError;
|
|
49295
|
-
|
|
49946
|
+
let retryCount = 0;
|
|
49947
|
+
const maxRetries = retryOverride?.maxRetries ?? this.retryConfig.maxRetries;
|
|
49948
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
49296
49949
|
try {
|
|
49297
|
-
|
|
49950
|
+
const result = await this.executeRequest(
|
|
49298
49951
|
options,
|
|
49299
49952
|
url,
|
|
49300
49953
|
headers,
|
|
49301
49954
|
body
|
|
49302
49955
|
);
|
|
49956
|
+
profiler2.endNetworkSpan(networkSpan, {
|
|
49957
|
+
statusCode: result.statusCode,
|
|
49958
|
+
responseSize: JSON.stringify(result.data).length,
|
|
49959
|
+
retryCount
|
|
49960
|
+
});
|
|
49961
|
+
return result;
|
|
49303
49962
|
} catch (error) {
|
|
49304
49963
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
49305
49964
|
const isRetryable = this.isRetryableError(error);
|
|
49306
|
-
const hasRetriesLeft = attempt <
|
|
49965
|
+
const hasRetriesLeft = attempt < maxRetries;
|
|
49307
49966
|
if (isRetryable && hasRetriesLeft) {
|
|
49967
|
+
retryCount++;
|
|
49308
49968
|
const delay = calculateBackoffDelay(
|
|
49309
49969
|
attempt,
|
|
49310
49970
|
this.retryConfig
|
|
@@ -49312,12 +49972,17 @@ var APIClient = class {
|
|
|
49312
49972
|
if (this.debug) {
|
|
49313
49973
|
const statusInfo = error instanceof APIError ? ` (${error.statusCode})` : "";
|
|
49314
49974
|
console.error(
|
|
49315
|
-
`DEBUG: Request failed${statusInfo}, retrying in ${delay}ms (attempt ${attempt + 1}/${
|
|
49975
|
+
`DEBUG: Request failed${statusInfo}, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`
|
|
49316
49976
|
);
|
|
49317
49977
|
}
|
|
49318
49978
|
await sleep(delay);
|
|
49319
49979
|
continue;
|
|
49320
49980
|
}
|
|
49981
|
+
profiler2.endNetworkSpan(networkSpan, {
|
|
49982
|
+
statusCode: error instanceof APIError ? error.statusCode : 0,
|
|
49983
|
+
retryCount,
|
|
49984
|
+
error: lastError.message
|
|
49985
|
+
});
|
|
49321
49986
|
throw error;
|
|
49322
49987
|
}
|
|
49323
49988
|
}
|
|
@@ -75275,6 +75940,8 @@ var REPLSession = class {
|
|
|
75275
75940
|
// Fallback tracking for improved error messages
|
|
75276
75941
|
_fallbackAttempted = false;
|
|
75277
75942
|
_fallbackReason = null;
|
|
75943
|
+
// Connection status for graceful offline handling
|
|
75944
|
+
_connectionStatus = "unknown";
|
|
75278
75945
|
constructor(config = {}) {
|
|
75279
75946
|
this._namespace = config.namespace ?? this.getDefaultNamespace();
|
|
75280
75947
|
this._contextPath = new ContextPath();
|
|
@@ -75304,48 +75971,91 @@ var REPLSession = class {
|
|
|
75304
75971
|
}
|
|
75305
75972
|
/**
|
|
75306
75973
|
* Initialize the session (async operations)
|
|
75974
|
+
* Uses fast-fail connectivity check to avoid long waits when API is unreachable.
|
|
75307
75975
|
*/
|
|
75308
75976
|
async initialize() {
|
|
75309
|
-
const
|
|
75310
|
-
const
|
|
75311
|
-
|
|
75312
|
-
|
|
75313
|
-
|
|
75314
|
-
);
|
|
75315
|
-
}
|
|
75316
|
-
};
|
|
75317
|
-
profile("initialize:start");
|
|
75977
|
+
const profiler2 = getProfiler();
|
|
75978
|
+
const historySpan = profiler2.startSpan(
|
|
75979
|
+
"history_load",
|
|
75980
|
+
"History Loading"
|
|
75981
|
+
);
|
|
75318
75982
|
try {
|
|
75319
75983
|
this._history = await HistoryManager.create(
|
|
75320
75984
|
getHistoryFilePath(),
|
|
75321
75985
|
1e3
|
|
75322
75986
|
);
|
|
75323
|
-
|
|
75987
|
+
profiler2.endSpan(historySpan, { status: "loaded" });
|
|
75324
75988
|
} catch (error) {
|
|
75325
75989
|
console.error("Warning: could not initialize history:", error);
|
|
75326
75990
|
this._history = new HistoryManager(getHistoryFilePath(), 1e3);
|
|
75327
|
-
|
|
75991
|
+
profiler2.endSpan(historySpan, {
|
|
75992
|
+
status: "fallback",
|
|
75993
|
+
error: String(error)
|
|
75994
|
+
});
|
|
75328
75995
|
}
|
|
75996
|
+
const profileSpan = profiler2.startSpan(
|
|
75997
|
+
"profile_load",
|
|
75998
|
+
"Profile Loading"
|
|
75999
|
+
);
|
|
75329
76000
|
await this.loadActiveProfile();
|
|
75330
|
-
|
|
76001
|
+
profiler2.endSpan(profileSpan, { profileName: this._activeProfileName });
|
|
75331
76002
|
if (this._apiClient?.isAuthenticated()) {
|
|
75332
|
-
|
|
76003
|
+
const connectivitySpan = profiler2.startSpan(
|
|
76004
|
+
"connectivity_check",
|
|
76005
|
+
"Connectivity Check"
|
|
76006
|
+
);
|
|
76007
|
+
const connectivity = await this._apiClient.checkConnectivity();
|
|
76008
|
+
profiler2.endSpan(connectivitySpan, {
|
|
76009
|
+
reachable: connectivity.reachable,
|
|
76010
|
+
latencyMs: connectivity.latencyMs
|
|
76011
|
+
});
|
|
76012
|
+
if (!connectivity.reachable) {
|
|
76013
|
+
this._connectionStatus = "offline";
|
|
76014
|
+
this._validationError = "API endpoint unreachable - running in offline mode";
|
|
76015
|
+
debugProtocol.auth("connectivity_failed", {
|
|
76016
|
+
serverUrl: this._serverUrl,
|
|
76017
|
+
status: "offline"
|
|
76018
|
+
});
|
|
76019
|
+
return;
|
|
76020
|
+
}
|
|
76021
|
+
const tokenSpan = profiler2.startSpan(
|
|
76022
|
+
"token_validation",
|
|
76023
|
+
"Token Validation"
|
|
76024
|
+
);
|
|
75333
76025
|
debugProtocol.auth("token_validation_start", {
|
|
75334
76026
|
serverUrl: this._serverUrl,
|
|
75335
76027
|
hasApiClient: true,
|
|
75336
|
-
authSource: this._authSource
|
|
76028
|
+
authSource: this._authSource,
|
|
76029
|
+
startupMode: true
|
|
76030
|
+
});
|
|
76031
|
+
const result = await this._apiClient.validateToken({
|
|
76032
|
+
startupMode: true
|
|
75337
76033
|
});
|
|
75338
|
-
const result = await this._apiClient.validateToken();
|
|
75339
76034
|
this._tokenValidated = result.valid;
|
|
75340
76035
|
this._validationError = result.error ?? null;
|
|
75341
|
-
|
|
76036
|
+
if (result.valid) {
|
|
76037
|
+
this._connectionStatus = "connected";
|
|
76038
|
+
} else if (result.error?.includes("unreachable") || result.error?.includes("timed out")) {
|
|
76039
|
+
this._connectionStatus = "offline";
|
|
76040
|
+
} else {
|
|
76041
|
+
this._connectionStatus = "error";
|
|
76042
|
+
}
|
|
76043
|
+
profiler2.endSpan(tokenSpan, {
|
|
76044
|
+
valid: result.valid,
|
|
76045
|
+
error: result.error,
|
|
76046
|
+
connectionStatus: this._connectionStatus
|
|
76047
|
+
});
|
|
75342
76048
|
debugProtocol.auth("token_validation_complete", {
|
|
75343
76049
|
valid: result.valid,
|
|
75344
76050
|
error: result.error,
|
|
75345
76051
|
tokenValidated: this._tokenValidated,
|
|
75346
76052
|
validationError: this._validationError,
|
|
75347
|
-
authSource: this._authSource
|
|
76053
|
+
authSource: this._authSource,
|
|
76054
|
+
connectionStatus: this._connectionStatus
|
|
75348
76055
|
});
|
|
76056
|
+
if (this._connectionStatus === "offline") {
|
|
76057
|
+
return;
|
|
76058
|
+
}
|
|
75349
76059
|
if (!result.valid && (this._authSource === "env" || this._authSource === "mixed")) {
|
|
75350
76060
|
if (this._activeProfile?.apiToken && this._activeProfile.apiToken !== this._apiToken) {
|
|
75351
76061
|
this._fallbackAttempted = true;
|
|
@@ -75366,20 +76076,29 @@ var REPLSession = class {
|
|
|
75366
76076
|
apiToken: this._apiToken,
|
|
75367
76077
|
debug: this._debug
|
|
75368
76078
|
});
|
|
75369
|
-
|
|
75370
|
-
|
|
75371
|
-
|
|
76079
|
+
const fallbackSpan = profiler2.startSpan(
|
|
76080
|
+
"fallback_validation",
|
|
76081
|
+
"Fallback Token Validation"
|
|
76082
|
+
);
|
|
76083
|
+
const fallbackResult = await this._apiClient.validateToken({
|
|
76084
|
+
startupMode: true
|
|
76085
|
+
});
|
|
76086
|
+
profiler2.endSpan(fallbackSpan, {
|
|
76087
|
+
valid: fallbackResult.valid
|
|
76088
|
+
});
|
|
75372
76089
|
if (fallbackResult.valid) {
|
|
75373
76090
|
this._tokenValidated = true;
|
|
75374
76091
|
this._validationError = null;
|
|
75375
76092
|
this._authSource = "profile-fallback";
|
|
75376
76093
|
this._fallbackReason = null;
|
|
76094
|
+
this._connectionStatus = "connected";
|
|
75377
76095
|
debugProtocol.auth("token_fallback_success", {
|
|
75378
76096
|
authSource: this._authSource,
|
|
75379
76097
|
profileName: this._activeProfileName
|
|
75380
76098
|
});
|
|
75381
76099
|
} else {
|
|
75382
76100
|
this._fallbackReason = `Profile '${this._activeProfileName}' credentials are also invalid`;
|
|
76101
|
+
this._connectionStatus = "error";
|
|
75383
76102
|
debugProtocol.auth("token_fallback_failed", {
|
|
75384
76103
|
error: fallbackResult.error,
|
|
75385
76104
|
profileName: this._activeProfileName
|
|
@@ -75403,13 +76122,15 @@ var REPLSession = class {
|
|
|
75403
76122
|
});
|
|
75404
76123
|
}
|
|
75405
76124
|
}
|
|
75406
|
-
if (this._tokenValidated) {
|
|
75407
|
-
|
|
76125
|
+
if (this._tokenValidated && this._connectionStatus === "connected") {
|
|
76126
|
+
const userInfoSpan = profiler2.startSpan(
|
|
76127
|
+
"user_info",
|
|
76128
|
+
"User Info Fetch"
|
|
76129
|
+
);
|
|
75408
76130
|
await this.fetchUserInfo();
|
|
75409
|
-
|
|
76131
|
+
profiler2.endSpan(userInfoSpan, { username: this._username });
|
|
75410
76132
|
}
|
|
75411
76133
|
}
|
|
75412
|
-
profile("initialize:complete");
|
|
75413
76134
|
}
|
|
75414
76135
|
/**
|
|
75415
76136
|
* Fetch user info from the API
|
|
@@ -75613,6 +76334,18 @@ var REPLSession = class {
|
|
|
75613
76334
|
getFallbackReason() {
|
|
75614
76335
|
return this._fallbackReason;
|
|
75615
76336
|
}
|
|
76337
|
+
/**
|
|
76338
|
+
* Get the current connection status
|
|
76339
|
+
*/
|
|
76340
|
+
getConnectionStatus() {
|
|
76341
|
+
return this._connectionStatus;
|
|
76342
|
+
}
|
|
76343
|
+
/**
|
|
76344
|
+
* Check if running in offline mode (API unreachable)
|
|
76345
|
+
*/
|
|
76346
|
+
isOfflineMode() {
|
|
76347
|
+
return this._connectionStatus === "offline";
|
|
76348
|
+
}
|
|
75616
76349
|
/**
|
|
75617
76350
|
* Get the API client
|
|
75618
76351
|
*/
|
|
@@ -85491,29 +86224,26 @@ var HeadlessController = class {
|
|
|
85491
86224
|
|
|
85492
86225
|
// src/index.tsx
|
|
85493
86226
|
var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
|
|
85494
|
-
var
|
|
85495
|
-
var
|
|
85496
|
-
|
|
85497
|
-
|
|
85498
|
-
|
|
85499
|
-
|
|
85500
|
-
|
|
85501
|
-
|
|
85502
|
-
|
|
85503
|
-
|
|
85504
|
-
|
|
85505
|
-
|
|
85506
|
-
|
|
85507
|
-
|
|
85508
|
-
|
|
85509
|
-
|
|
85510
|
-
|
|
85511
|
-
|
|
85512
|
-
_profile("import:spec");
|
|
85513
|
-
_profile("import:headless");
|
|
85514
|
-
_profile("imports:complete");
|
|
86227
|
+
var profiler = initProfiler();
|
|
86228
|
+
var importsSpan = profiler.startSpan("imports", "Module Imports");
|
|
86229
|
+
profiler.memorySnapshot("script_start");
|
|
86230
|
+
profiler.checkpoint("import:ink");
|
|
86231
|
+
profiler.checkpoint("import:commander");
|
|
86232
|
+
profiler.checkpoint("import:repl");
|
|
86233
|
+
profiler.checkpoint("import:branding");
|
|
86234
|
+
profiler.checkpoint("import:executor");
|
|
86235
|
+
profiler.checkpoint("import:session");
|
|
86236
|
+
profiler.checkpoint("import:help");
|
|
86237
|
+
profiler.checkpoint("import:config");
|
|
86238
|
+
profiler.checkpoint("import:output-types");
|
|
86239
|
+
profiler.checkpoint("import:banner");
|
|
86240
|
+
profiler.checkpoint("import:debug");
|
|
86241
|
+
profiler.checkpoint("import:spec");
|
|
86242
|
+
profiler.checkpoint("import:headless");
|
|
86243
|
+
profiler.endSpan(importsSpan);
|
|
86244
|
+
profiler.memorySnapshot("imports_complete");
|
|
85515
86245
|
var program2 = new Command();
|
|
85516
|
-
|
|
86246
|
+
profiler.checkpoint("commander:created");
|
|
85517
86247
|
program2.configureHelp({
|
|
85518
86248
|
formatHelp: () => formatRootHelp().join("\n")
|
|
85519
86249
|
});
|
|
@@ -85559,12 +86289,28 @@ program2.name(CLI_NAME).description("F5 Distributed Cloud Shell - Interactive CL
|
|
|
85559
86289
|
}
|
|
85560
86290
|
const cliLogoMode = options.logo && isValidLogoMode(options.logo) ? options.logo : void 0;
|
|
85561
86291
|
process.stdout.write("Initializing...");
|
|
86292
|
+
const sessionInitSpan = profiler.startSpan(
|
|
86293
|
+
"session_init",
|
|
86294
|
+
"Session Initialization"
|
|
86295
|
+
);
|
|
85562
86296
|
const session = new REPLSession();
|
|
85563
86297
|
await session.initialize();
|
|
86298
|
+
profiler.endSpan(sessionInitSpan);
|
|
85564
86299
|
debugProtocol.session("init", { mode: "repl" });
|
|
85565
86300
|
emitSessionState(session);
|
|
85566
86301
|
process.stdout.write("\r\x1B[K");
|
|
86302
|
+
profiler.memorySnapshot("pre_banner");
|
|
86303
|
+
printProfileReport();
|
|
85567
86304
|
renderBanner(cliLogoMode, "startup");
|
|
86305
|
+
if (session.isOfflineMode()) {
|
|
86306
|
+
console.log("");
|
|
86307
|
+
console.log(
|
|
86308
|
+
`${colors.yellow}\u26A0\uFE0F Offline Mode: API endpoint unreachable${colors.reset}`
|
|
86309
|
+
);
|
|
86310
|
+
console.log(
|
|
86311
|
+
`${colors.dim} Commands requiring API access will fail. Check network and try again.${colors.reset}`
|
|
86312
|
+
);
|
|
86313
|
+
}
|
|
85568
86314
|
if (session.getAuthSource() === "profile-fallback") {
|
|
85569
86315
|
const profileName = session.getActiveProfileName();
|
|
85570
86316
|
console.log("");
|
|
@@ -85629,13 +86375,27 @@ program2.name(CLI_NAME).description("F5 Distributed Cloud Shell - Interactive CL
|
|
|
85629
86375
|
}
|
|
85630
86376
|
);
|
|
85631
86377
|
async function executeNonInteractive(args) {
|
|
86378
|
+
const sessionInitSpan = profiler.startSpan(
|
|
86379
|
+
"session_init",
|
|
86380
|
+
"Session Initialization"
|
|
86381
|
+
);
|
|
85632
86382
|
const session = new REPLSession();
|
|
85633
86383
|
await session.initialize();
|
|
86384
|
+
profiler.endSpan(sessionInitSpan);
|
|
86385
|
+
printProfileReport();
|
|
85634
86386
|
debugProtocol.session("init", {
|
|
85635
86387
|
mode: "non-interactive",
|
|
85636
86388
|
command: args.join(" ")
|
|
85637
86389
|
});
|
|
85638
86390
|
emitSessionState(session);
|
|
86391
|
+
if (session.isOfflineMode()) {
|
|
86392
|
+
console.error(
|
|
86393
|
+
`${colors.yellow}\u26A0\uFE0F Offline Mode: API endpoint unreachable${colors.reset}`
|
|
86394
|
+
);
|
|
86395
|
+
console.error(
|
|
86396
|
+
`${colors.dim} Commands requiring API access will fail.${colors.reset}`
|
|
86397
|
+
);
|
|
86398
|
+
}
|
|
85639
86399
|
if (session.getAuthSource() === "profile-fallback") {
|
|
85640
86400
|
const profileName = session.getActiveProfileName();
|
|
85641
86401
|
console.error(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@robinmordasiewicz/f5xc-xcsh",
|
|
3
|
-
"version": "2.0.21-
|
|
3
|
+
"version": "2.0.21-2601101304",
|
|
4
4
|
"description": "F5 Distributed Cloud Shell - Interactive CLI for F5 XC",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,8 +25,13 @@
|
|
|
25
25
|
"build:binaries": "./scripts/build-binaries.sh",
|
|
26
26
|
"build:release": "npm run build && npm run build:binaries",
|
|
27
27
|
"dev": "tsx src/index.tsx",
|
|
28
|
+
"dev:profile": "XCSH_PROFILE_LEVEL=detailed tsx src/index.tsx",
|
|
29
|
+
"dev:profile:full": "XCSH_PROFILE_LEVEL=full tsx src/index.tsx",
|
|
28
30
|
"build:dev": "npm run build && ${HOME}/.bun/bin/bun build dist/index.js --compile --outfile ./xcsh",
|
|
31
|
+
"build:dev:debug": "tsup --sourcemap && bun build dist/index.js --compile --outfile ./xcsh-debug",
|
|
29
32
|
"start": "node dist/index.js",
|
|
33
|
+
"start:profile": "XCSH_PROFILE_LEVEL=detailed node dist/index.js",
|
|
34
|
+
"start:profile:full": "XCSH_PROFILE_LEVEL=full node dist/index.js",
|
|
30
35
|
"test": "vitest",
|
|
31
36
|
"test:coverage": "vitest --coverage",
|
|
32
37
|
"lint": "eslint src",
|