@nmakarov/cli-toolkit 0.79.0 → 0.80.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-runner.cjs +135 -31
- package/dist/cli-runner.cjs.map +1 -1
- package/dist/cli-runner.js +135 -31
- package/dist/cli-runner.js.map +1 -1
- package/dist/db.cjs +105 -25
- package/dist/db.cjs.map +1 -1
- package/dist/db.js +105 -25
- package/dist/db.js.map +1 -1
- package/dist/index.cjs +116 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +116 -30
- package/dist/index.js.map +1 -1
- package/dist/init.cjs +30 -6
- package/dist/init.cjs.map +1 -1
- package/dist/init.js +30 -6
- package/dist/init.js.map +1 -1
- package/dist/logger.cjs +6 -5
- package/dist/logger.cjs.map +1 -1
- package/dist/logger.js +6 -5
- package/dist/logger.js.map +1 -1
- package/package.json +1 -1
package/dist/cli-runner.js
CHANGED
|
@@ -2654,13 +2654,14 @@ var Logger = class _Logger {
|
|
|
2654
2654
|
*/
|
|
2655
2655
|
progress(message, opts) {
|
|
2656
2656
|
const { prefix, count, total } = opts;
|
|
2657
|
-
const
|
|
2657
|
+
const displayTotal = Math.max(Number(total) || 0, Number(count) || 0);
|
|
2658
|
+
const paddedTotal = String(displayTotal).length;
|
|
2658
2659
|
const paddedCount = String(count).padStart(paddedTotal, " ");
|
|
2659
2660
|
const payload = {
|
|
2660
2661
|
level: "progress",
|
|
2661
2662
|
message,
|
|
2662
2663
|
count: paddedCount,
|
|
2663
|
-
total,
|
|
2664
|
+
total: displayTotal,
|
|
2664
2665
|
prefix
|
|
2665
2666
|
};
|
|
2666
2667
|
const key = prefix ?? "";
|
|
@@ -2677,7 +2678,7 @@ var Logger = class _Logger {
|
|
|
2677
2678
|
if (wantTimes) {
|
|
2678
2679
|
let remaining = -1;
|
|
2679
2680
|
if (itemsPerSec > 0) {
|
|
2680
|
-
remaining = (
|
|
2681
|
+
remaining = Math.max(0, (displayTotal - count) / itemsPerSec);
|
|
2681
2682
|
}
|
|
2682
2683
|
payload.elapsed = this.round(elapsedSeconds, 2);
|
|
2683
2684
|
payload.remaining = remaining >= 0 ? this.round(remaining, 2) : remaining;
|
|
@@ -2686,12 +2687,12 @@ var Logger = class _Logger {
|
|
|
2686
2687
|
payload.rate = itemsPerSec >= 0 ? this.round(itemsPerSec, 2) : itemsPerSec;
|
|
2687
2688
|
}
|
|
2688
2689
|
}
|
|
2689
|
-
if (count
|
|
2690
|
+
if (count === total) {
|
|
2690
2691
|
delete this.startTimes[key];
|
|
2691
2692
|
delete this.startCounts[key];
|
|
2692
2693
|
delete this.lastProgressTimes[key];
|
|
2693
2694
|
}
|
|
2694
|
-
if (this.shouldOutputProgress(prefix ?? "", count,
|
|
2695
|
+
if (this.shouldOutputProgress(prefix ?? "", count, displayTotal)) {
|
|
2695
2696
|
this.out(payload);
|
|
2696
2697
|
if (this.options.progressThrottle && prefix) {
|
|
2697
2698
|
this.lastProgressTimes[prefix] = Date.now();
|
|
@@ -2850,6 +2851,8 @@ function setup(opts = {}) {
|
|
|
2850
2851
|
// a quick "show me the figured params and quit" that skips the flow's
|
|
2851
2852
|
// actual work. Like --stopAfter=init, this is a hard exit(0) (registered
|
|
2852
2853
|
// cleanups are skipped); call it once components/params are resolved.
|
|
2854
|
+
_requestExitCode: null,
|
|
2855
|
+
requestExit: null,
|
|
2853
2856
|
showUsedParamsIfNeeded: () => {
|
|
2854
2857
|
const mode = params.getShowUsedParamsMode?.();
|
|
2855
2858
|
if (mode !== "top" && mode !== "stop") return;
|
|
@@ -2860,6 +2863,9 @@ function setup(opts = {}) {
|
|
|
2860
2863
|
}
|
|
2861
2864
|
}
|
|
2862
2865
|
};
|
|
2866
|
+
context.requestExit = (code = 0) => {
|
|
2867
|
+
context._requestExitCode = code;
|
|
2868
|
+
};
|
|
2863
2869
|
logger.debug("[setup] completed successfully");
|
|
2864
2870
|
return context;
|
|
2865
2871
|
}
|
|
@@ -2895,9 +2901,25 @@ async function init(flow2, opts = {}) {
|
|
|
2895
2901
|
if (cleanupRan) return;
|
|
2896
2902
|
cleanupRan = true;
|
|
2897
2903
|
const fns = [...ctx.cleanupFunctions].reverse();
|
|
2904
|
+
const budgetMs = 5e3;
|
|
2905
|
+
const started = Date.now();
|
|
2898
2906
|
for (const fn of fns) {
|
|
2907
|
+
const left = budgetMs - (Date.now() - started);
|
|
2908
|
+
if (left <= 0) {
|
|
2909
|
+
ctx.logger.warn("[cleanup] budget exhausted \u2014 skipping remaining cleanup");
|
|
2910
|
+
break;
|
|
2911
|
+
}
|
|
2899
2912
|
try {
|
|
2900
|
-
await
|
|
2913
|
+
await Promise.race([
|
|
2914
|
+
Promise.resolve(fn(ctx)),
|
|
2915
|
+
new Promise((_, reject) => {
|
|
2916
|
+
const t = setTimeout(
|
|
2917
|
+
() => reject(new Error(`cleanup timed out after ${left}ms`)),
|
|
2918
|
+
left
|
|
2919
|
+
);
|
|
2920
|
+
t.unref?.();
|
|
2921
|
+
})
|
|
2922
|
+
]);
|
|
2901
2923
|
} catch (error) {
|
|
2902
2924
|
ctx.logger.warn("[cleanup] error in cleanup function:", error);
|
|
2903
2925
|
}
|
|
@@ -2993,6 +3015,8 @@ async function init(flow2, opts = {}) {
|
|
|
2993
3015
|
process.exit(process.exitCode);
|
|
2994
3016
|
} else if (stop || kill) {
|
|
2995
3017
|
process.exit(0);
|
|
3018
|
+
} else if (context._requestExitCode != null) {
|
|
3019
|
+
process.exit(context._requestExitCode);
|
|
2996
3020
|
}
|
|
2997
3021
|
}
|
|
2998
3022
|
}
|
|
@@ -3116,7 +3140,7 @@ var CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
3116
3140
|
"57P02",
|
|
3117
3141
|
"57P03"
|
|
3118
3142
|
]);
|
|
3119
|
-
var CONNECTION_ERROR_MESSAGE_RE = /connection (terminated|ended|closed|destroyed|reset|refused|not open)|Connection terminated unexpectedly|Client has encountered a connection error|server closed the connection|Cannot use a pool after calling end|This socket has been ended|connect ECONNRESET/i;
|
|
3143
|
+
var CONNECTION_ERROR_MESSAGE_RE = /connection (terminated|ended|closed|destroyed|reset|refused|not open)|Connection (terminated|ended) unexpectedly|Client has encountered a connection error|server closed the connection|Cannot use a pool after calling end|This socket has been ended|connect ECONNRESET/i;
|
|
3120
3144
|
var Db = class _Db {
|
|
3121
3145
|
static async init(context, options = {}) {
|
|
3122
3146
|
const buildConfig = async () => {
|
|
@@ -3384,6 +3408,10 @@ var Db = class _Db {
|
|
|
3384
3408
|
this.isConnected = false;
|
|
3385
3409
|
this.queriesLog = [];
|
|
3386
3410
|
this._reconnectPromise = null;
|
|
3411
|
+
this._closed = false;
|
|
3412
|
+
this._liveKnex = /* @__PURE__ */ new Set();
|
|
3413
|
+
this._reconnectCooldownUntil = 0;
|
|
3414
|
+
this._reconnectAcquireTimeoutMs = null;
|
|
3387
3415
|
this.config = {
|
|
3388
3416
|
testConnection: true,
|
|
3389
3417
|
profile: false,
|
|
@@ -3468,6 +3496,9 @@ var Db = class _Db {
|
|
|
3468
3496
|
return null;
|
|
3469
3497
|
}
|
|
3470
3498
|
async connect() {
|
|
3499
|
+
if (this._closed) {
|
|
3500
|
+
throw new ParamError("Db: Connection closed");
|
|
3501
|
+
}
|
|
3471
3502
|
if (this.isConnected && this.knexInstance) {
|
|
3472
3503
|
this.logger.warn?.("[Db] Already connected");
|
|
3473
3504
|
return;
|
|
@@ -3483,22 +3514,46 @@ var Db = class _Db {
|
|
|
3483
3514
|
connectionString: this.config.connectionString,
|
|
3484
3515
|
family: 4
|
|
3485
3516
|
};
|
|
3517
|
+
const acquireTimeout = this._reconnectAcquireTimeoutMs ?? this.config.acquireConnectionTimeout;
|
|
3486
3518
|
this.knexInstance = knex({
|
|
3487
3519
|
client,
|
|
3488
3520
|
connection: connectionConfig,
|
|
3489
3521
|
pool: this.config.pool,
|
|
3490
|
-
acquireConnectionTimeout:
|
|
3522
|
+
acquireConnectionTimeout: acquireTimeout,
|
|
3491
3523
|
...this.config.ssl && { ssl: this.config.ssl }
|
|
3492
3524
|
});
|
|
3525
|
+
this._liveKnex.add(this.knexInstance);
|
|
3526
|
+
this.knexInstance.on?.("error", (err) => {
|
|
3527
|
+
if (this._closed) return;
|
|
3528
|
+
this.logger.warn?.(
|
|
3529
|
+
`[Db] Connection error (${this.getErrorMessage(err)})`
|
|
3530
|
+
);
|
|
3531
|
+
});
|
|
3532
|
+
if (this._closed) {
|
|
3533
|
+
await this._destroyKnex(this.knexInstance, "connect aborted (closed)");
|
|
3534
|
+
this.knexInstance = null;
|
|
3535
|
+
throw new ParamError("Db: Connection closed");
|
|
3536
|
+
}
|
|
3493
3537
|
if (this.config.profile) {
|
|
3494
3538
|
this.attachProfiler();
|
|
3495
3539
|
}
|
|
3496
3540
|
if (this.config.testConnection) {
|
|
3497
3541
|
await this.testConnection();
|
|
3498
3542
|
}
|
|
3543
|
+
if (this._closed) {
|
|
3544
|
+
await this._destroyKnex(this.knexInstance, "connect aborted (closed)");
|
|
3545
|
+
this.knexInstance = null;
|
|
3546
|
+
throw new ParamError("Db: Connection closed");
|
|
3547
|
+
}
|
|
3499
3548
|
this.isConnected = true;
|
|
3500
3549
|
this.logger.debug?.(formatDbConnectMessage(this.config.name, this.config.connectionString));
|
|
3501
3550
|
} catch (error) {
|
|
3551
|
+
const failed = this.knexInstance;
|
|
3552
|
+
this.knexInstance = null;
|
|
3553
|
+
this.isConnected = false;
|
|
3554
|
+
if (failed) {
|
|
3555
|
+
await this._destroyKnex(failed, "connect failed");
|
|
3556
|
+
}
|
|
3502
3557
|
if (error instanceof ParamError) {
|
|
3503
3558
|
throw error;
|
|
3504
3559
|
}
|
|
@@ -3506,20 +3561,42 @@ var Db = class _Db {
|
|
|
3506
3561
|
throw new ParamError(`Db: Connection failed - ${errorMsg}`);
|
|
3507
3562
|
}
|
|
3508
3563
|
}
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3564
|
+
/**
|
|
3565
|
+
* Destroy a knex pool without hanging exit on stuck TCP sockets (ETIMEDOUT).
|
|
3566
|
+
* @param {import("knex").Knex | null | undefined} knexInst
|
|
3567
|
+
* @param {string} [reason]
|
|
3568
|
+
* @param {number} [timeoutMs]
|
|
3569
|
+
*/
|
|
3570
|
+
async _destroyKnex(knexInst, reason = "destroy", timeoutMs = 3e3) {
|
|
3571
|
+
if (!knexInst || typeof knexInst.destroy !== "function") return;
|
|
3572
|
+
this._liveKnex.delete(knexInst);
|
|
3513
3573
|
try {
|
|
3514
|
-
await
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3574
|
+
await Promise.race([
|
|
3575
|
+
knexInst.destroy(),
|
|
3576
|
+
new Promise((_, reject) => {
|
|
3577
|
+
const t = setTimeout(
|
|
3578
|
+
() => reject(new Error(`Db: ${reason} timed out after ${timeoutMs}ms`)),
|
|
3579
|
+
timeoutMs
|
|
3580
|
+
);
|
|
3581
|
+
t.unref?.();
|
|
3582
|
+
})
|
|
3583
|
+
]);
|
|
3519
3584
|
} catch (error) {
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3585
|
+
this.logger.debug?.(
|
|
3586
|
+
`[Db] ${reason}: ${this.getErrorMessage(error)}`
|
|
3587
|
+
);
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3590
|
+
async disconnect() {
|
|
3591
|
+
this._closed = true;
|
|
3592
|
+
this.isConnected = false;
|
|
3593
|
+
this.knexInstance = null;
|
|
3594
|
+
this.queriesLog = [];
|
|
3595
|
+
const all = [...this._liveKnex];
|
|
3596
|
+
this._liveKnex.clear();
|
|
3597
|
+
await Promise.all(all.map((inst) => this._destroyKnex(inst, "disconnect")));
|
|
3598
|
+
if (all.length > 0) {
|
|
3599
|
+
this.logger.debug?.(formatDbDisconnectMessage(this.config.name, this.config.connectionString));
|
|
3523
3600
|
}
|
|
3524
3601
|
}
|
|
3525
3602
|
/**
|
|
@@ -3542,27 +3619,39 @@ var Db = class _Db {
|
|
|
3542
3619
|
}
|
|
3543
3620
|
/**
|
|
3544
3621
|
* Destroy the current knex pool and open a new one. Concurrent callers share one attempt.
|
|
3622
|
+
* No-ops once disconnect() has closed the handle.
|
|
3545
3623
|
*/
|
|
3546
3624
|
async reconnect() {
|
|
3625
|
+
if (this._closed) {
|
|
3626
|
+
return;
|
|
3627
|
+
}
|
|
3628
|
+
if (this._reconnectCooldownUntil && Date.now() < this._reconnectCooldownUntil) {
|
|
3629
|
+
return;
|
|
3630
|
+
}
|
|
3547
3631
|
if (this._reconnectPromise) {
|
|
3548
3632
|
await this._reconnectPromise;
|
|
3549
3633
|
return;
|
|
3550
3634
|
}
|
|
3551
3635
|
this._reconnectPromise = (async () => {
|
|
3636
|
+
if (this._closed) return;
|
|
3552
3637
|
const old = this.knexInstance;
|
|
3553
3638
|
this.isConnected = false;
|
|
3554
3639
|
this.knexInstance = null;
|
|
3555
3640
|
this.queriesLog = [];
|
|
3556
3641
|
if (old) {
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3642
|
+
await this._destroyKnex(old, "destroy during reconnect");
|
|
3643
|
+
}
|
|
3644
|
+
if (this._closed) return;
|
|
3645
|
+
this._reconnectAcquireTimeoutMs = 3e3;
|
|
3646
|
+
try {
|
|
3647
|
+
await this.connect();
|
|
3648
|
+
this._reconnectCooldownUntil = 0;
|
|
3649
|
+
} catch (error) {
|
|
3650
|
+
this._reconnectCooldownUntil = Date.now() + 5e3;
|
|
3651
|
+
throw error;
|
|
3652
|
+
} finally {
|
|
3653
|
+
this._reconnectAcquireTimeoutMs = null;
|
|
3564
3654
|
}
|
|
3565
|
-
await this.connect();
|
|
3566
3655
|
})();
|
|
3567
3656
|
try {
|
|
3568
3657
|
await this._reconnectPromise;
|
|
@@ -3571,10 +3660,19 @@ var Db = class _Db {
|
|
|
3571
3660
|
}
|
|
3572
3661
|
}
|
|
3573
3662
|
async reconnectAfterConnectionError(error) {
|
|
3663
|
+
if (this._closed) {
|
|
3664
|
+
return;
|
|
3665
|
+
}
|
|
3666
|
+
if (this._reconnectCooldownUntil && Date.now() < this._reconnectCooldownUntil) {
|
|
3667
|
+
return;
|
|
3668
|
+
}
|
|
3574
3669
|
this.logger.warn?.(
|
|
3575
3670
|
`[Db] Connection lost (${this.getErrorMessage(error)}) \u2014 reconnecting\u2026`
|
|
3576
3671
|
);
|
|
3577
|
-
|
|
3672
|
+
try {
|
|
3673
|
+
await this.reconnect();
|
|
3674
|
+
} catch {
|
|
3675
|
+
}
|
|
3578
3676
|
}
|
|
3579
3677
|
/**
|
|
3580
3678
|
* Run `fn`; on a connection error, reconnect once (by default) and retry `fn`.
|
|
@@ -3587,10 +3685,13 @@ var Db = class _Db {
|
|
|
3587
3685
|
return await fn();
|
|
3588
3686
|
} catch (error) {
|
|
3589
3687
|
lastError = error;
|
|
3590
|
-
if (!this.isConnectionError(error) || attempt >= retries) {
|
|
3688
|
+
if (this._closed || !this.isConnectionError(error) || attempt >= retries) {
|
|
3591
3689
|
throw error;
|
|
3592
3690
|
}
|
|
3593
3691
|
await this.reconnectAfterConnectionError(error);
|
|
3692
|
+
if (this._closed) {
|
|
3693
|
+
throw error;
|
|
3694
|
+
}
|
|
3594
3695
|
}
|
|
3595
3696
|
}
|
|
3596
3697
|
throw lastError;
|
|
@@ -3614,10 +3715,13 @@ var Db = class _Db {
|
|
|
3614
3715
|
try {
|
|
3615
3716
|
return await protoThen.call(builder);
|
|
3616
3717
|
} catch (error) {
|
|
3617
|
-
if (!inst.isConnectionError(error)) {
|
|
3718
|
+
if (inst._closed || !inst.isConnectionError(error)) {
|
|
3618
3719
|
throw error;
|
|
3619
3720
|
}
|
|
3620
3721
|
await inst.reconnectAfterConnectionError(error);
|
|
3722
|
+
if (inst._closed || !inst.knexInstance) {
|
|
3723
|
+
throw error;
|
|
3724
|
+
}
|
|
3621
3725
|
if (typeof builder.clone === "function") {
|
|
3622
3726
|
const retry = builder.clone();
|
|
3623
3727
|
retry.client = inst.knexInstance.client;
|