@nmakarov/cli-toolkit 0.44.0 → 0.47.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/args.cjs +17 -24
- package/dist/args.cjs.map +1 -1
- package/dist/args.js +17 -24
- package/dist/args.js.map +1 -1
- package/dist/cli-runner.cjs +180 -28
- package/dist/cli-runner.cjs.map +1 -1
- package/dist/cli-runner.js +180 -28
- package/dist/cli-runner.js.map +1 -1
- package/dist/db.cjs +163 -4
- package/dist/db.cjs.map +1 -1
- package/dist/db.js +163 -4
- package/dist/db.js.map +1 -1
- package/dist/deploy.cjs +2 -0
- package/dist/deploy.cjs.map +1 -1
- package/dist/deploy.js +2 -0
- package/dist/deploy.js.map +1 -1
- package/dist/http-client.cjs +1 -1
- package/dist/http-client.cjs.map +1 -1
- package/dist/http-client.js +1 -1
- package/dist/http-client.js.map +1 -1
- package/dist/index.cjs +182 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +182 -28
- package/dist/index.js.map +1 -1
- package/dist/init.cjs +17 -24
- package/dist/init.cjs.map +1 -1
- package/dist/init.js +17 -24
- package/dist/init.js.map +1 -1
- package/dist/s3.cjs.map +1 -1
- package/dist/s3.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1388,36 +1388,29 @@ var Args = class _Args {
|
|
|
1388
1388
|
}
|
|
1389
1389
|
return;
|
|
1390
1390
|
}
|
|
1391
|
-
let dotEnvPathFile =
|
|
1392
|
-
|
|
1393
|
-
const envSpecificPath = resolve(dotEnvPath, envSpecificFile);
|
|
1394
|
-
if (existsSync(envSpecificPath)) {
|
|
1395
|
-
dotEnvPathFile = envSpecificPath;
|
|
1396
|
-
}
|
|
1397
|
-
if (!dotEnvPathFile && !this.get("dotEnvPath")) {
|
|
1391
|
+
let dotEnvPathFile = resolve(dotEnvPath, dotEnvFile);
|
|
1392
|
+
if (!existsSync(dotEnvPathFile) && !this.get("dotEnvPath")) {
|
|
1398
1393
|
const examplesPath = resolve(dotEnvPath, "examples");
|
|
1399
|
-
const
|
|
1400
|
-
if (existsSync(
|
|
1401
|
-
dotEnvPathFile =
|
|
1402
|
-
}
|
|
1403
|
-
|
|
1404
|
-
if (!dotEnvPathFile) {
|
|
1405
|
-
dotEnvPathFile = resolve(dotEnvPath, dotEnvFile);
|
|
1406
|
-
if (!existsSync(dotEnvPathFile)) {
|
|
1407
|
-
if (!this.get("dotEnvPath")) {
|
|
1408
|
-
const examplesPath = resolve(dotEnvPath, "examples");
|
|
1409
|
-
const examplesEnvFile = resolve(examplesPath, dotEnvFile);
|
|
1410
|
-
if (existsSync(examplesEnvFile)) {
|
|
1411
|
-
dotEnvPathFile = examplesEnvFile;
|
|
1412
|
-
} else {
|
|
1413
|
-
dotEnvPathFile = resolve(dotEnvPath, "..", dotEnvFile);
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1394
|
+
const examplesEnvFile = resolve(examplesPath, dotEnvFile);
|
|
1395
|
+
if (existsSync(examplesEnvFile)) {
|
|
1396
|
+
dotEnvPathFile = examplesEnvFile;
|
|
1397
|
+
} else {
|
|
1398
|
+
dotEnvPathFile = resolve(dotEnvPath, "..", dotEnvFile);
|
|
1416
1399
|
}
|
|
1417
1400
|
}
|
|
1418
1401
|
if (dotEnvPathFile && existsSync(dotEnvPathFile)) {
|
|
1419
1402
|
config({ path: dotEnvPathFile, quiet: true });
|
|
1420
1403
|
}
|
|
1404
|
+
const envSpecificFile = `.env.${this.env}`;
|
|
1405
|
+
const envSpecificPath = resolve(dotEnvPath, envSpecificFile);
|
|
1406
|
+
if (existsSync(envSpecificPath) && envSpecificPath !== dotEnvPathFile) {
|
|
1407
|
+
config({ path: envSpecificPath, quiet: true });
|
|
1408
|
+
} else if (!this.get("dotEnvPath")) {
|
|
1409
|
+
const examplesOverlay = resolve(dotEnvPath, "examples", envSpecificFile);
|
|
1410
|
+
if (existsSync(examplesOverlay)) {
|
|
1411
|
+
config({ path: examplesOverlay, quiet: true });
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1421
1414
|
}
|
|
1422
1415
|
/**
|
|
1423
1416
|
* Load configuration files
|
|
@@ -3478,10 +3471,35 @@ async function ensureSchemaEverywhere(dbs, spec, options = {}) {
|
|
|
3478
3471
|
// src/db/index.js
|
|
3479
3472
|
var KNEX_DEFAULTS = {
|
|
3480
3473
|
testConnection: true,
|
|
3481
|
-
|
|
3474
|
+
// min: 0 avoids holding idle sockets that go stale during long-running CLIs
|
|
3475
|
+
pool: { min: 0, max: 10, idleTimeoutMillis: 3e4 },
|
|
3482
3476
|
acquireConnectionTimeout: 1e4,
|
|
3483
3477
|
ssl: { rejectUnauthorized: false }
|
|
3484
3478
|
};
|
|
3479
|
+
var CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
3480
|
+
"ECONNRESET",
|
|
3481
|
+
"ECONNREFUSED",
|
|
3482
|
+
"EPIPE",
|
|
3483
|
+
"ETIMEDOUT",
|
|
3484
|
+
"ENOTFOUND",
|
|
3485
|
+
"EHOSTUNREACH",
|
|
3486
|
+
"ENETUNREACH",
|
|
3487
|
+
"ECONNABORTED",
|
|
3488
|
+
"CONNECTION_ENDED",
|
|
3489
|
+
"CONNECTION_CLOSED",
|
|
3490
|
+
// PostgreSQL SQLSTATE class 08xxx (connection exception) + admin shutdowns
|
|
3491
|
+
"08000",
|
|
3492
|
+
"08001",
|
|
3493
|
+
"08003",
|
|
3494
|
+
"08004",
|
|
3495
|
+
"08006",
|
|
3496
|
+
"08007",
|
|
3497
|
+
"08P01",
|
|
3498
|
+
"57P01",
|
|
3499
|
+
"57P02",
|
|
3500
|
+
"57P03"
|
|
3501
|
+
]);
|
|
3502
|
+
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|Timeout acquiring a connection/i;
|
|
3485
3503
|
var Db = class _Db {
|
|
3486
3504
|
static async init(context, options = {}) {
|
|
3487
3505
|
const buildConfig = async () => {
|
|
@@ -3729,10 +3747,11 @@ var Db = class _Db {
|
|
|
3729
3747
|
this.knexInstance = null;
|
|
3730
3748
|
this.isConnected = false;
|
|
3731
3749
|
this.queriesLog = [];
|
|
3750
|
+
this._reconnectPromise = null;
|
|
3732
3751
|
this.config = {
|
|
3733
3752
|
testConnection: true,
|
|
3734
3753
|
profile: false,
|
|
3735
|
-
pool: { min:
|
|
3754
|
+
pool: { min: 0, max: 10, idleTimeoutMillis: 3e4 },
|
|
3736
3755
|
acquireConnectionTimeout: 1e4,
|
|
3737
3756
|
ssl: { rejectUnauthorized: false },
|
|
3738
3757
|
logger: console,
|
|
@@ -3750,7 +3769,7 @@ var Db = class _Db {
|
|
|
3750
3769
|
if (!inst.knexInstance) {
|
|
3751
3770
|
throw new Error("Db: Not connected. Call connect() first.");
|
|
3752
3771
|
}
|
|
3753
|
-
return inst.knexInstance(...argumentsList);
|
|
3772
|
+
return inst.wrapQueryBuilder(inst.knexInstance(...argumentsList));
|
|
3754
3773
|
},
|
|
3755
3774
|
get: (target, prop) => {
|
|
3756
3775
|
if (prop === "_instance") {
|
|
@@ -3760,8 +3779,12 @@ var Db = class _Db {
|
|
|
3760
3779
|
const ownMethods = [
|
|
3761
3780
|
"connect",
|
|
3762
3781
|
"disconnect",
|
|
3782
|
+
"reconnect",
|
|
3763
3783
|
"testConnection",
|
|
3764
3784
|
"tableExists",
|
|
3785
|
+
"raw",
|
|
3786
|
+
"withConnectionRetry",
|
|
3787
|
+
"isConnectionError",
|
|
3765
3788
|
"getQueryLog",
|
|
3766
3789
|
"getKnex",
|
|
3767
3790
|
"isConnectedToDb",
|
|
@@ -3781,6 +3804,9 @@ var Db = class _Db {
|
|
|
3781
3804
|
if (inst.knexInstance) {
|
|
3782
3805
|
const knexProp = inst.knexInstance[prop];
|
|
3783
3806
|
if (typeof knexProp === "function") {
|
|
3807
|
+
if (prop === "raw") {
|
|
3808
|
+
return (...args) => inst.raw(...args);
|
|
3809
|
+
}
|
|
3784
3810
|
return knexProp.bind(inst.knexInstance);
|
|
3785
3811
|
}
|
|
3786
3812
|
return knexProp;
|
|
@@ -3860,6 +3886,130 @@ var Db = class _Db {
|
|
|
3860
3886
|
throw error;
|
|
3861
3887
|
}
|
|
3862
3888
|
}
|
|
3889
|
+
/**
|
|
3890
|
+
* True when the error indicates a dead socket / pool that a fresh connect may fix.
|
|
3891
|
+
* Safe to call as `Db.prototype.isConnectionError(err)` or via a connected handle.
|
|
3892
|
+
*/
|
|
3893
|
+
isConnectionError(error) {
|
|
3894
|
+
if (!error) {
|
|
3895
|
+
return false;
|
|
3896
|
+
}
|
|
3897
|
+
if (error instanceof AggregateError && Array.isArray(error.errors)) {
|
|
3898
|
+
return error.errors.some((e) => this.isConnectionError(e));
|
|
3899
|
+
}
|
|
3900
|
+
const code = error.code ?? error.errno;
|
|
3901
|
+
if (code != null && CONNECTION_ERROR_CODES.has(String(code))) {
|
|
3902
|
+
return true;
|
|
3903
|
+
}
|
|
3904
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3905
|
+
return CONNECTION_ERROR_MESSAGE_RE.test(msg);
|
|
3906
|
+
}
|
|
3907
|
+
/**
|
|
3908
|
+
* Destroy the current knex pool and open a new one. Concurrent callers share one attempt.
|
|
3909
|
+
*/
|
|
3910
|
+
async reconnect() {
|
|
3911
|
+
if (this._reconnectPromise) {
|
|
3912
|
+
await this._reconnectPromise;
|
|
3913
|
+
return;
|
|
3914
|
+
}
|
|
3915
|
+
this._reconnectPromise = (async () => {
|
|
3916
|
+
const old = this.knexInstance;
|
|
3917
|
+
this.isConnected = false;
|
|
3918
|
+
this.knexInstance = null;
|
|
3919
|
+
this.queriesLog = [];
|
|
3920
|
+
if (old) {
|
|
3921
|
+
try {
|
|
3922
|
+
await old.destroy();
|
|
3923
|
+
} catch (error) {
|
|
3924
|
+
this.logger.debug?.(
|
|
3925
|
+
`[Db] destroy during reconnect: ${this.getErrorMessage(error)}`
|
|
3926
|
+
);
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
await this.connect();
|
|
3930
|
+
})();
|
|
3931
|
+
try {
|
|
3932
|
+
await this._reconnectPromise;
|
|
3933
|
+
} finally {
|
|
3934
|
+
this._reconnectPromise = null;
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3937
|
+
async reconnectAfterConnectionError(error) {
|
|
3938
|
+
this.logger.warn?.(
|
|
3939
|
+
`[Db] Connection lost (${this.getErrorMessage(error)}) \u2014 reconnecting\u2026`
|
|
3940
|
+
);
|
|
3941
|
+
await this.reconnect();
|
|
3942
|
+
}
|
|
3943
|
+
/**
|
|
3944
|
+
* Run `fn`; on a connection error, reconnect once (by default) and retry `fn`.
|
|
3945
|
+
* `fn` should look up `this.knexInstance` each call so the retry uses the new client.
|
|
3946
|
+
*/
|
|
3947
|
+
async withConnectionRetry(fn, { retries = 1 } = {}) {
|
|
3948
|
+
let lastError;
|
|
3949
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
3950
|
+
try {
|
|
3951
|
+
return await fn();
|
|
3952
|
+
} catch (error) {
|
|
3953
|
+
lastError = error;
|
|
3954
|
+
if (!this.isConnectionError(error) || attempt >= retries) {
|
|
3955
|
+
throw error;
|
|
3956
|
+
}
|
|
3957
|
+
await this.reconnectAfterConnectionError(error);
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
throw lastError;
|
|
3961
|
+
}
|
|
3962
|
+
/**
|
|
3963
|
+
* Wrap a knex QueryBuilder / Raw so that awaiting it retries once after reconnect
|
|
3964
|
+
* when the first attempt dies with a connection error.
|
|
3965
|
+
*/
|
|
3966
|
+
wrapQueryBuilder(builder) {
|
|
3967
|
+
if (!builder || typeof builder.then !== "function" || builder.__dbReconnectWrapped) {
|
|
3968
|
+
return builder;
|
|
3969
|
+
}
|
|
3970
|
+
builder.__dbReconnectWrapped = true;
|
|
3971
|
+
const inst = this;
|
|
3972
|
+
const protoThen = Object.getPrototypeOf(builder)?.then;
|
|
3973
|
+
if (typeof protoThen !== "function") {
|
|
3974
|
+
return builder;
|
|
3975
|
+
}
|
|
3976
|
+
builder.then = function(onFulfilled, onRejected) {
|
|
3977
|
+
const run2 = async () => {
|
|
3978
|
+
try {
|
|
3979
|
+
return await protoThen.call(builder);
|
|
3980
|
+
} catch (error) {
|
|
3981
|
+
if (!inst.isConnectionError(error)) {
|
|
3982
|
+
throw error;
|
|
3983
|
+
}
|
|
3984
|
+
await inst.reconnectAfterConnectionError(error);
|
|
3985
|
+
if (typeof builder.clone === "function") {
|
|
3986
|
+
const retry = builder.clone();
|
|
3987
|
+
retry.client = inst.knexInstance.client;
|
|
3988
|
+
return await protoThen.call(retry);
|
|
3989
|
+
}
|
|
3990
|
+
if (typeof builder.toSQL === "function" && inst.knexInstance) {
|
|
3991
|
+
const sql = builder.toSQL();
|
|
3992
|
+
const statements = Array.isArray(sql) ? sql : [sql];
|
|
3993
|
+
let last;
|
|
3994
|
+
for (const stmt of statements) {
|
|
3995
|
+
last = await inst.knexInstance.raw(stmt.sql, stmt.bindings);
|
|
3996
|
+
}
|
|
3997
|
+
return last;
|
|
3998
|
+
}
|
|
3999
|
+
throw error;
|
|
4000
|
+
}
|
|
4001
|
+
};
|
|
4002
|
+
return run2().then(onFulfilled, onRejected);
|
|
4003
|
+
};
|
|
4004
|
+
return builder;
|
|
4005
|
+
}
|
|
4006
|
+
/** knex.raw with auto-reconnect on dead connections. */
|
|
4007
|
+
raw(...args) {
|
|
4008
|
+
if (!this.knexInstance) {
|
|
4009
|
+
throw new Error("Db: Not connected. Call connect() first.");
|
|
4010
|
+
}
|
|
4011
|
+
return this.wrapQueryBuilder(this.knexInstance.raw(...args));
|
|
4012
|
+
}
|
|
3863
4013
|
getErrorMessage(error) {
|
|
3864
4014
|
if (error instanceof AggregateError) {
|
|
3865
4015
|
const errors = error.errors || [];
|
|
@@ -4030,7 +4180,9 @@ var Db = class _Db {
|
|
|
4030
4180
|
throw new Error("Db: Not connected. Call connect() first.");
|
|
4031
4181
|
}
|
|
4032
4182
|
try {
|
|
4033
|
-
return await this.
|
|
4183
|
+
return await this.withConnectionRetry(
|
|
4184
|
+
() => this.knexInstance.schema.hasTable(tableName)
|
|
4185
|
+
);
|
|
4034
4186
|
} catch (error) {
|
|
4035
4187
|
this.logger.error?.(`[Db] Error checking table existence: ${error.message}`);
|
|
4036
4188
|
throw error;
|
|
@@ -5665,6 +5817,8 @@ module.exports = {
|
|
|
5665
5817
|
merge_logs: true,
|
|
5666
5818
|
time: true,
|
|
5667
5819
|
env: {
|
|
5820
|
+
// cli-toolkit Args: ENV selects env-suffixed defaults (DB_NAME_PRODUCTION, BUCKET_PRODUCTION, \u2026)
|
|
5821
|
+
ENV: "production",
|
|
5668
5822
|
NODE_ENV: "production",
|
|
5669
5823
|
},
|
|
5670
5824
|
},
|