@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.cjs
CHANGED
|
@@ -2670,13 +2670,14 @@ var Logger = class _Logger {
|
|
|
2670
2670
|
*/
|
|
2671
2671
|
progress(message, opts) {
|
|
2672
2672
|
const { prefix, count, total } = opts;
|
|
2673
|
-
const
|
|
2673
|
+
const displayTotal = Math.max(Number(total) || 0, Number(count) || 0);
|
|
2674
|
+
const paddedTotal = String(displayTotal).length;
|
|
2674
2675
|
const paddedCount = String(count).padStart(paddedTotal, " ");
|
|
2675
2676
|
const payload = {
|
|
2676
2677
|
level: "progress",
|
|
2677
2678
|
message,
|
|
2678
2679
|
count: paddedCount,
|
|
2679
|
-
total,
|
|
2680
|
+
total: displayTotal,
|
|
2680
2681
|
prefix
|
|
2681
2682
|
};
|
|
2682
2683
|
const key = prefix ?? "";
|
|
@@ -2693,7 +2694,7 @@ var Logger = class _Logger {
|
|
|
2693
2694
|
if (wantTimes) {
|
|
2694
2695
|
let remaining = -1;
|
|
2695
2696
|
if (itemsPerSec > 0) {
|
|
2696
|
-
remaining = (
|
|
2697
|
+
remaining = Math.max(0, (displayTotal - count) / itemsPerSec);
|
|
2697
2698
|
}
|
|
2698
2699
|
payload.elapsed = this.round(elapsedSeconds, 2);
|
|
2699
2700
|
payload.remaining = remaining >= 0 ? this.round(remaining, 2) : remaining;
|
|
@@ -2702,12 +2703,12 @@ var Logger = class _Logger {
|
|
|
2702
2703
|
payload.rate = itemsPerSec >= 0 ? this.round(itemsPerSec, 2) : itemsPerSec;
|
|
2703
2704
|
}
|
|
2704
2705
|
}
|
|
2705
|
-
if (count
|
|
2706
|
+
if (count === total) {
|
|
2706
2707
|
delete this.startTimes[key];
|
|
2707
2708
|
delete this.startCounts[key];
|
|
2708
2709
|
delete this.lastProgressTimes[key];
|
|
2709
2710
|
}
|
|
2710
|
-
if (this.shouldOutputProgress(prefix ?? "", count,
|
|
2711
|
+
if (this.shouldOutputProgress(prefix ?? "", count, displayTotal)) {
|
|
2711
2712
|
this.out(payload);
|
|
2712
2713
|
if (this.options.progressThrottle && prefix) {
|
|
2713
2714
|
this.lastProgressTimes[prefix] = Date.now();
|
|
@@ -2866,6 +2867,8 @@ function setup(opts = {}) {
|
|
|
2866
2867
|
// a quick "show me the figured params and quit" that skips the flow's
|
|
2867
2868
|
// actual work. Like --stopAfter=init, this is a hard exit(0) (registered
|
|
2868
2869
|
// cleanups are skipped); call it once components/params are resolved.
|
|
2870
|
+
_requestExitCode: null,
|
|
2871
|
+
requestExit: null,
|
|
2869
2872
|
showUsedParamsIfNeeded: () => {
|
|
2870
2873
|
const mode = params.getShowUsedParamsMode?.();
|
|
2871
2874
|
if (mode !== "top" && mode !== "stop") return;
|
|
@@ -2876,6 +2879,9 @@ function setup(opts = {}) {
|
|
|
2876
2879
|
}
|
|
2877
2880
|
}
|
|
2878
2881
|
};
|
|
2882
|
+
context.requestExit = (code = 0) => {
|
|
2883
|
+
context._requestExitCode = code;
|
|
2884
|
+
};
|
|
2879
2885
|
logger.debug("[setup] completed successfully");
|
|
2880
2886
|
return context;
|
|
2881
2887
|
}
|
|
@@ -2911,9 +2917,25 @@ async function init(flow2, opts = {}) {
|
|
|
2911
2917
|
if (cleanupRan) return;
|
|
2912
2918
|
cleanupRan = true;
|
|
2913
2919
|
const fns = [...ctx.cleanupFunctions].reverse();
|
|
2920
|
+
const budgetMs = 5e3;
|
|
2921
|
+
const started = Date.now();
|
|
2914
2922
|
for (const fn of fns) {
|
|
2923
|
+
const left = budgetMs - (Date.now() - started);
|
|
2924
|
+
if (left <= 0) {
|
|
2925
|
+
ctx.logger.warn("[cleanup] budget exhausted \u2014 skipping remaining cleanup");
|
|
2926
|
+
break;
|
|
2927
|
+
}
|
|
2915
2928
|
try {
|
|
2916
|
-
await
|
|
2929
|
+
await Promise.race([
|
|
2930
|
+
Promise.resolve(fn(ctx)),
|
|
2931
|
+
new Promise((_, reject) => {
|
|
2932
|
+
const t = setTimeout(
|
|
2933
|
+
() => reject(new Error(`cleanup timed out after ${left}ms`)),
|
|
2934
|
+
left
|
|
2935
|
+
);
|
|
2936
|
+
t.unref?.();
|
|
2937
|
+
})
|
|
2938
|
+
]);
|
|
2917
2939
|
} catch (error) {
|
|
2918
2940
|
ctx.logger.warn("[cleanup] error in cleanup function:", error);
|
|
2919
2941
|
}
|
|
@@ -3009,6 +3031,8 @@ async function init(flow2, opts = {}) {
|
|
|
3009
3031
|
process.exit(process.exitCode);
|
|
3010
3032
|
} else if (stop || kill) {
|
|
3011
3033
|
process.exit(0);
|
|
3034
|
+
} else if (context._requestExitCode != null) {
|
|
3035
|
+
process.exit(context._requestExitCode);
|
|
3012
3036
|
}
|
|
3013
3037
|
}
|
|
3014
3038
|
}
|
|
@@ -3132,7 +3156,7 @@ var CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
3132
3156
|
"57P02",
|
|
3133
3157
|
"57P03"
|
|
3134
3158
|
]);
|
|
3135
|
-
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;
|
|
3159
|
+
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;
|
|
3136
3160
|
var Db = class _Db {
|
|
3137
3161
|
static async init(context, options = {}) {
|
|
3138
3162
|
const buildConfig = async () => {
|
|
@@ -3400,6 +3424,10 @@ var Db = class _Db {
|
|
|
3400
3424
|
this.isConnected = false;
|
|
3401
3425
|
this.queriesLog = [];
|
|
3402
3426
|
this._reconnectPromise = null;
|
|
3427
|
+
this._closed = false;
|
|
3428
|
+
this._liveKnex = /* @__PURE__ */ new Set();
|
|
3429
|
+
this._reconnectCooldownUntil = 0;
|
|
3430
|
+
this._reconnectAcquireTimeoutMs = null;
|
|
3403
3431
|
this.config = {
|
|
3404
3432
|
testConnection: true,
|
|
3405
3433
|
profile: false,
|
|
@@ -3484,6 +3512,9 @@ var Db = class _Db {
|
|
|
3484
3512
|
return null;
|
|
3485
3513
|
}
|
|
3486
3514
|
async connect() {
|
|
3515
|
+
if (this._closed) {
|
|
3516
|
+
throw new ParamError("Db: Connection closed");
|
|
3517
|
+
}
|
|
3487
3518
|
if (this.isConnected && this.knexInstance) {
|
|
3488
3519
|
this.logger.warn?.("[Db] Already connected");
|
|
3489
3520
|
return;
|
|
@@ -3499,22 +3530,46 @@ var Db = class _Db {
|
|
|
3499
3530
|
connectionString: this.config.connectionString,
|
|
3500
3531
|
family: 4
|
|
3501
3532
|
};
|
|
3533
|
+
const acquireTimeout = this._reconnectAcquireTimeoutMs ?? this.config.acquireConnectionTimeout;
|
|
3502
3534
|
this.knexInstance = (0, import_knex.default)({
|
|
3503
3535
|
client,
|
|
3504
3536
|
connection: connectionConfig,
|
|
3505
3537
|
pool: this.config.pool,
|
|
3506
|
-
acquireConnectionTimeout:
|
|
3538
|
+
acquireConnectionTimeout: acquireTimeout,
|
|
3507
3539
|
...this.config.ssl && { ssl: this.config.ssl }
|
|
3508
3540
|
});
|
|
3541
|
+
this._liveKnex.add(this.knexInstance);
|
|
3542
|
+
this.knexInstance.on?.("error", (err) => {
|
|
3543
|
+
if (this._closed) return;
|
|
3544
|
+
this.logger.warn?.(
|
|
3545
|
+
`[Db] Connection error (${this.getErrorMessage(err)})`
|
|
3546
|
+
);
|
|
3547
|
+
});
|
|
3548
|
+
if (this._closed) {
|
|
3549
|
+
await this._destroyKnex(this.knexInstance, "connect aborted (closed)");
|
|
3550
|
+
this.knexInstance = null;
|
|
3551
|
+
throw new ParamError("Db: Connection closed");
|
|
3552
|
+
}
|
|
3509
3553
|
if (this.config.profile) {
|
|
3510
3554
|
this.attachProfiler();
|
|
3511
3555
|
}
|
|
3512
3556
|
if (this.config.testConnection) {
|
|
3513
3557
|
await this.testConnection();
|
|
3514
3558
|
}
|
|
3559
|
+
if (this._closed) {
|
|
3560
|
+
await this._destroyKnex(this.knexInstance, "connect aborted (closed)");
|
|
3561
|
+
this.knexInstance = null;
|
|
3562
|
+
throw new ParamError("Db: Connection closed");
|
|
3563
|
+
}
|
|
3515
3564
|
this.isConnected = true;
|
|
3516
3565
|
this.logger.debug?.(formatDbConnectMessage(this.config.name, this.config.connectionString));
|
|
3517
3566
|
} catch (error) {
|
|
3567
|
+
const failed = this.knexInstance;
|
|
3568
|
+
this.knexInstance = null;
|
|
3569
|
+
this.isConnected = false;
|
|
3570
|
+
if (failed) {
|
|
3571
|
+
await this._destroyKnex(failed, "connect failed");
|
|
3572
|
+
}
|
|
3518
3573
|
if (error instanceof ParamError) {
|
|
3519
3574
|
throw error;
|
|
3520
3575
|
}
|
|
@@ -3522,20 +3577,42 @@ var Db = class _Db {
|
|
|
3522
3577
|
throw new ParamError(`Db: Connection failed - ${errorMsg}`);
|
|
3523
3578
|
}
|
|
3524
3579
|
}
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3580
|
+
/**
|
|
3581
|
+
* Destroy a knex pool without hanging exit on stuck TCP sockets (ETIMEDOUT).
|
|
3582
|
+
* @param {import("knex").Knex | null | undefined} knexInst
|
|
3583
|
+
* @param {string} [reason]
|
|
3584
|
+
* @param {number} [timeoutMs]
|
|
3585
|
+
*/
|
|
3586
|
+
async _destroyKnex(knexInst, reason = "destroy", timeoutMs = 3e3) {
|
|
3587
|
+
if (!knexInst || typeof knexInst.destroy !== "function") return;
|
|
3588
|
+
this._liveKnex.delete(knexInst);
|
|
3529
3589
|
try {
|
|
3530
|
-
await
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3590
|
+
await Promise.race([
|
|
3591
|
+
knexInst.destroy(),
|
|
3592
|
+
new Promise((_, reject) => {
|
|
3593
|
+
const t = setTimeout(
|
|
3594
|
+
() => reject(new Error(`Db: ${reason} timed out after ${timeoutMs}ms`)),
|
|
3595
|
+
timeoutMs
|
|
3596
|
+
);
|
|
3597
|
+
t.unref?.();
|
|
3598
|
+
})
|
|
3599
|
+
]);
|
|
3535
3600
|
} catch (error) {
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3601
|
+
this.logger.debug?.(
|
|
3602
|
+
`[Db] ${reason}: ${this.getErrorMessage(error)}`
|
|
3603
|
+
);
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
async disconnect() {
|
|
3607
|
+
this._closed = true;
|
|
3608
|
+
this.isConnected = false;
|
|
3609
|
+
this.knexInstance = null;
|
|
3610
|
+
this.queriesLog = [];
|
|
3611
|
+
const all = [...this._liveKnex];
|
|
3612
|
+
this._liveKnex.clear();
|
|
3613
|
+
await Promise.all(all.map((inst) => this._destroyKnex(inst, "disconnect")));
|
|
3614
|
+
if (all.length > 0) {
|
|
3615
|
+
this.logger.debug?.(formatDbDisconnectMessage(this.config.name, this.config.connectionString));
|
|
3539
3616
|
}
|
|
3540
3617
|
}
|
|
3541
3618
|
/**
|
|
@@ -3558,27 +3635,39 @@ var Db = class _Db {
|
|
|
3558
3635
|
}
|
|
3559
3636
|
/**
|
|
3560
3637
|
* Destroy the current knex pool and open a new one. Concurrent callers share one attempt.
|
|
3638
|
+
* No-ops once disconnect() has closed the handle.
|
|
3561
3639
|
*/
|
|
3562
3640
|
async reconnect() {
|
|
3641
|
+
if (this._closed) {
|
|
3642
|
+
return;
|
|
3643
|
+
}
|
|
3644
|
+
if (this._reconnectCooldownUntil && Date.now() < this._reconnectCooldownUntil) {
|
|
3645
|
+
return;
|
|
3646
|
+
}
|
|
3563
3647
|
if (this._reconnectPromise) {
|
|
3564
3648
|
await this._reconnectPromise;
|
|
3565
3649
|
return;
|
|
3566
3650
|
}
|
|
3567
3651
|
this._reconnectPromise = (async () => {
|
|
3652
|
+
if (this._closed) return;
|
|
3568
3653
|
const old = this.knexInstance;
|
|
3569
3654
|
this.isConnected = false;
|
|
3570
3655
|
this.knexInstance = null;
|
|
3571
3656
|
this.queriesLog = [];
|
|
3572
3657
|
if (old) {
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3658
|
+
await this._destroyKnex(old, "destroy during reconnect");
|
|
3659
|
+
}
|
|
3660
|
+
if (this._closed) return;
|
|
3661
|
+
this._reconnectAcquireTimeoutMs = 3e3;
|
|
3662
|
+
try {
|
|
3663
|
+
await this.connect();
|
|
3664
|
+
this._reconnectCooldownUntil = 0;
|
|
3665
|
+
} catch (error) {
|
|
3666
|
+
this._reconnectCooldownUntil = Date.now() + 5e3;
|
|
3667
|
+
throw error;
|
|
3668
|
+
} finally {
|
|
3669
|
+
this._reconnectAcquireTimeoutMs = null;
|
|
3580
3670
|
}
|
|
3581
|
-
await this.connect();
|
|
3582
3671
|
})();
|
|
3583
3672
|
try {
|
|
3584
3673
|
await this._reconnectPromise;
|
|
@@ -3587,10 +3676,19 @@ var Db = class _Db {
|
|
|
3587
3676
|
}
|
|
3588
3677
|
}
|
|
3589
3678
|
async reconnectAfterConnectionError(error) {
|
|
3679
|
+
if (this._closed) {
|
|
3680
|
+
return;
|
|
3681
|
+
}
|
|
3682
|
+
if (this._reconnectCooldownUntil && Date.now() < this._reconnectCooldownUntil) {
|
|
3683
|
+
return;
|
|
3684
|
+
}
|
|
3590
3685
|
this.logger.warn?.(
|
|
3591
3686
|
`[Db] Connection lost (${this.getErrorMessage(error)}) \u2014 reconnecting\u2026`
|
|
3592
3687
|
);
|
|
3593
|
-
|
|
3688
|
+
try {
|
|
3689
|
+
await this.reconnect();
|
|
3690
|
+
} catch {
|
|
3691
|
+
}
|
|
3594
3692
|
}
|
|
3595
3693
|
/**
|
|
3596
3694
|
* Run `fn`; on a connection error, reconnect once (by default) and retry `fn`.
|
|
@@ -3603,10 +3701,13 @@ var Db = class _Db {
|
|
|
3603
3701
|
return await fn();
|
|
3604
3702
|
} catch (error) {
|
|
3605
3703
|
lastError = error;
|
|
3606
|
-
if (!this.isConnectionError(error) || attempt >= retries) {
|
|
3704
|
+
if (this._closed || !this.isConnectionError(error) || attempt >= retries) {
|
|
3607
3705
|
throw error;
|
|
3608
3706
|
}
|
|
3609
3707
|
await this.reconnectAfterConnectionError(error);
|
|
3708
|
+
if (this._closed) {
|
|
3709
|
+
throw error;
|
|
3710
|
+
}
|
|
3610
3711
|
}
|
|
3611
3712
|
}
|
|
3612
3713
|
throw lastError;
|
|
@@ -3630,10 +3731,13 @@ var Db = class _Db {
|
|
|
3630
3731
|
try {
|
|
3631
3732
|
return await protoThen.call(builder);
|
|
3632
3733
|
} catch (error) {
|
|
3633
|
-
if (!inst.isConnectionError(error)) {
|
|
3734
|
+
if (inst._closed || !inst.isConnectionError(error)) {
|
|
3634
3735
|
throw error;
|
|
3635
3736
|
}
|
|
3636
3737
|
await inst.reconnectAfterConnectionError(error);
|
|
3738
|
+
if (inst._closed || !inst.knexInstance) {
|
|
3739
|
+
throw error;
|
|
3740
|
+
}
|
|
3637
3741
|
if (typeof builder.clone === "function") {
|
|
3638
3742
|
const retry = builder.clone();
|
|
3639
3743
|
retry.client = inst.knexInstance.client;
|