@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/cli-runner.cjs
CHANGED
|
@@ -1455,36 +1455,29 @@ var Args = class _Args {
|
|
|
1455
1455
|
}
|
|
1456
1456
|
return;
|
|
1457
1457
|
}
|
|
1458
|
-
let dotEnvPathFile =
|
|
1459
|
-
|
|
1460
|
-
const envSpecificPath = (0, import_path.resolve)(dotEnvPath, envSpecificFile);
|
|
1461
|
-
if ((0, import_fs.existsSync)(envSpecificPath)) {
|
|
1462
|
-
dotEnvPathFile = envSpecificPath;
|
|
1463
|
-
}
|
|
1464
|
-
if (!dotEnvPathFile && !this.get("dotEnvPath")) {
|
|
1458
|
+
let dotEnvPathFile = (0, import_path.resolve)(dotEnvPath, dotEnvFile);
|
|
1459
|
+
if (!(0, import_fs.existsSync)(dotEnvPathFile) && !this.get("dotEnvPath")) {
|
|
1465
1460
|
const examplesPath = (0, import_path.resolve)(dotEnvPath, "examples");
|
|
1466
|
-
const
|
|
1467
|
-
if ((0, import_fs.existsSync)(
|
|
1468
|
-
dotEnvPathFile =
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
if (!dotEnvPathFile) {
|
|
1472
|
-
dotEnvPathFile = (0, import_path.resolve)(dotEnvPath, dotEnvFile);
|
|
1473
|
-
if (!(0, import_fs.existsSync)(dotEnvPathFile)) {
|
|
1474
|
-
if (!this.get("dotEnvPath")) {
|
|
1475
|
-
const examplesPath = (0, import_path.resolve)(dotEnvPath, "examples");
|
|
1476
|
-
const examplesEnvFile = (0, import_path.resolve)(examplesPath, dotEnvFile);
|
|
1477
|
-
if ((0, import_fs.existsSync)(examplesEnvFile)) {
|
|
1478
|
-
dotEnvPathFile = examplesEnvFile;
|
|
1479
|
-
} else {
|
|
1480
|
-
dotEnvPathFile = (0, import_path.resolve)(dotEnvPath, "..", dotEnvFile);
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1461
|
+
const examplesEnvFile = (0, import_path.resolve)(examplesPath, dotEnvFile);
|
|
1462
|
+
if ((0, import_fs.existsSync)(examplesEnvFile)) {
|
|
1463
|
+
dotEnvPathFile = examplesEnvFile;
|
|
1464
|
+
} else {
|
|
1465
|
+
dotEnvPathFile = (0, import_path.resolve)(dotEnvPath, "..", dotEnvFile);
|
|
1483
1466
|
}
|
|
1484
1467
|
}
|
|
1485
1468
|
if (dotEnvPathFile && (0, import_fs.existsSync)(dotEnvPathFile)) {
|
|
1486
1469
|
(0, import_dotenv.config)({ path: dotEnvPathFile, quiet: true });
|
|
1487
1470
|
}
|
|
1471
|
+
const envSpecificFile = `.env.${this.env}`;
|
|
1472
|
+
const envSpecificPath = (0, import_path.resolve)(dotEnvPath, envSpecificFile);
|
|
1473
|
+
if ((0, import_fs.existsSync)(envSpecificPath) && envSpecificPath !== dotEnvPathFile) {
|
|
1474
|
+
(0, import_dotenv.config)({ path: envSpecificPath, quiet: true });
|
|
1475
|
+
} else if (!this.get("dotEnvPath")) {
|
|
1476
|
+
const examplesOverlay = (0, import_path.resolve)(dotEnvPath, "examples", envSpecificFile);
|
|
1477
|
+
if ((0, import_fs.existsSync)(examplesOverlay)) {
|
|
1478
|
+
(0, import_dotenv.config)({ path: examplesOverlay, quiet: true });
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1488
1481
|
}
|
|
1489
1482
|
/**
|
|
1490
1483
|
* Load configuration files
|
|
@@ -2804,10 +2797,35 @@ async function ensureSchema(db, spec, options = {}) {
|
|
|
2804
2797
|
// src/db/index.js
|
|
2805
2798
|
var KNEX_DEFAULTS = {
|
|
2806
2799
|
testConnection: true,
|
|
2807
|
-
|
|
2800
|
+
// min: 0 avoids holding idle sockets that go stale during long-running CLIs
|
|
2801
|
+
pool: { min: 0, max: 10, idleTimeoutMillis: 3e4 },
|
|
2808
2802
|
acquireConnectionTimeout: 1e4,
|
|
2809
2803
|
ssl: { rejectUnauthorized: false }
|
|
2810
2804
|
};
|
|
2805
|
+
var CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
2806
|
+
"ECONNRESET",
|
|
2807
|
+
"ECONNREFUSED",
|
|
2808
|
+
"EPIPE",
|
|
2809
|
+
"ETIMEDOUT",
|
|
2810
|
+
"ENOTFOUND",
|
|
2811
|
+
"EHOSTUNREACH",
|
|
2812
|
+
"ENETUNREACH",
|
|
2813
|
+
"ECONNABORTED",
|
|
2814
|
+
"CONNECTION_ENDED",
|
|
2815
|
+
"CONNECTION_CLOSED",
|
|
2816
|
+
// PostgreSQL SQLSTATE class 08xxx (connection exception) + admin shutdowns
|
|
2817
|
+
"08000",
|
|
2818
|
+
"08001",
|
|
2819
|
+
"08003",
|
|
2820
|
+
"08004",
|
|
2821
|
+
"08006",
|
|
2822
|
+
"08007",
|
|
2823
|
+
"08P01",
|
|
2824
|
+
"57P01",
|
|
2825
|
+
"57P02",
|
|
2826
|
+
"57P03"
|
|
2827
|
+
]);
|
|
2828
|
+
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;
|
|
2811
2829
|
var Db = class _Db {
|
|
2812
2830
|
static async init(context, options = {}) {
|
|
2813
2831
|
const buildConfig = async () => {
|
|
@@ -3055,10 +3073,11 @@ var Db = class _Db {
|
|
|
3055
3073
|
this.knexInstance = null;
|
|
3056
3074
|
this.isConnected = false;
|
|
3057
3075
|
this.queriesLog = [];
|
|
3076
|
+
this._reconnectPromise = null;
|
|
3058
3077
|
this.config = {
|
|
3059
3078
|
testConnection: true,
|
|
3060
3079
|
profile: false,
|
|
3061
|
-
pool: { min:
|
|
3080
|
+
pool: { min: 0, max: 10, idleTimeoutMillis: 3e4 },
|
|
3062
3081
|
acquireConnectionTimeout: 1e4,
|
|
3063
3082
|
ssl: { rejectUnauthorized: false },
|
|
3064
3083
|
logger: console,
|
|
@@ -3076,7 +3095,7 @@ var Db = class _Db {
|
|
|
3076
3095
|
if (!inst.knexInstance) {
|
|
3077
3096
|
throw new Error("Db: Not connected. Call connect() first.");
|
|
3078
3097
|
}
|
|
3079
|
-
return inst.knexInstance(...argumentsList);
|
|
3098
|
+
return inst.wrapQueryBuilder(inst.knexInstance(...argumentsList));
|
|
3080
3099
|
},
|
|
3081
3100
|
get: (target, prop) => {
|
|
3082
3101
|
if (prop === "_instance") {
|
|
@@ -3086,8 +3105,12 @@ var Db = class _Db {
|
|
|
3086
3105
|
const ownMethods = [
|
|
3087
3106
|
"connect",
|
|
3088
3107
|
"disconnect",
|
|
3108
|
+
"reconnect",
|
|
3089
3109
|
"testConnection",
|
|
3090
3110
|
"tableExists",
|
|
3111
|
+
"raw",
|
|
3112
|
+
"withConnectionRetry",
|
|
3113
|
+
"isConnectionError",
|
|
3091
3114
|
"getQueryLog",
|
|
3092
3115
|
"getKnex",
|
|
3093
3116
|
"isConnectedToDb",
|
|
@@ -3107,6 +3130,9 @@ var Db = class _Db {
|
|
|
3107
3130
|
if (inst.knexInstance) {
|
|
3108
3131
|
const knexProp = inst.knexInstance[prop];
|
|
3109
3132
|
if (typeof knexProp === "function") {
|
|
3133
|
+
if (prop === "raw") {
|
|
3134
|
+
return (...args) => inst.raw(...args);
|
|
3135
|
+
}
|
|
3110
3136
|
return knexProp.bind(inst.knexInstance);
|
|
3111
3137
|
}
|
|
3112
3138
|
return knexProp;
|
|
@@ -3186,6 +3212,130 @@ var Db = class _Db {
|
|
|
3186
3212
|
throw error;
|
|
3187
3213
|
}
|
|
3188
3214
|
}
|
|
3215
|
+
/**
|
|
3216
|
+
* True when the error indicates a dead socket / pool that a fresh connect may fix.
|
|
3217
|
+
* Safe to call as `Db.prototype.isConnectionError(err)` or via a connected handle.
|
|
3218
|
+
*/
|
|
3219
|
+
isConnectionError(error) {
|
|
3220
|
+
if (!error) {
|
|
3221
|
+
return false;
|
|
3222
|
+
}
|
|
3223
|
+
if (error instanceof AggregateError && Array.isArray(error.errors)) {
|
|
3224
|
+
return error.errors.some((e) => this.isConnectionError(e));
|
|
3225
|
+
}
|
|
3226
|
+
const code = error.code ?? error.errno;
|
|
3227
|
+
if (code != null && CONNECTION_ERROR_CODES.has(String(code))) {
|
|
3228
|
+
return true;
|
|
3229
|
+
}
|
|
3230
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3231
|
+
return CONNECTION_ERROR_MESSAGE_RE.test(msg);
|
|
3232
|
+
}
|
|
3233
|
+
/**
|
|
3234
|
+
* Destroy the current knex pool and open a new one. Concurrent callers share one attempt.
|
|
3235
|
+
*/
|
|
3236
|
+
async reconnect() {
|
|
3237
|
+
if (this._reconnectPromise) {
|
|
3238
|
+
await this._reconnectPromise;
|
|
3239
|
+
return;
|
|
3240
|
+
}
|
|
3241
|
+
this._reconnectPromise = (async () => {
|
|
3242
|
+
const old = this.knexInstance;
|
|
3243
|
+
this.isConnected = false;
|
|
3244
|
+
this.knexInstance = null;
|
|
3245
|
+
this.queriesLog = [];
|
|
3246
|
+
if (old) {
|
|
3247
|
+
try {
|
|
3248
|
+
await old.destroy();
|
|
3249
|
+
} catch (error) {
|
|
3250
|
+
this.logger.debug?.(
|
|
3251
|
+
`[Db] destroy during reconnect: ${this.getErrorMessage(error)}`
|
|
3252
|
+
);
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
await this.connect();
|
|
3256
|
+
})();
|
|
3257
|
+
try {
|
|
3258
|
+
await this._reconnectPromise;
|
|
3259
|
+
} finally {
|
|
3260
|
+
this._reconnectPromise = null;
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
async reconnectAfterConnectionError(error) {
|
|
3264
|
+
this.logger.warn?.(
|
|
3265
|
+
`[Db] Connection lost (${this.getErrorMessage(error)}) \u2014 reconnecting\u2026`
|
|
3266
|
+
);
|
|
3267
|
+
await this.reconnect();
|
|
3268
|
+
}
|
|
3269
|
+
/**
|
|
3270
|
+
* Run `fn`; on a connection error, reconnect once (by default) and retry `fn`.
|
|
3271
|
+
* `fn` should look up `this.knexInstance` each call so the retry uses the new client.
|
|
3272
|
+
*/
|
|
3273
|
+
async withConnectionRetry(fn, { retries = 1 } = {}) {
|
|
3274
|
+
let lastError;
|
|
3275
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
3276
|
+
try {
|
|
3277
|
+
return await fn();
|
|
3278
|
+
} catch (error) {
|
|
3279
|
+
lastError = error;
|
|
3280
|
+
if (!this.isConnectionError(error) || attempt >= retries) {
|
|
3281
|
+
throw error;
|
|
3282
|
+
}
|
|
3283
|
+
await this.reconnectAfterConnectionError(error);
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
throw lastError;
|
|
3287
|
+
}
|
|
3288
|
+
/**
|
|
3289
|
+
* Wrap a knex QueryBuilder / Raw so that awaiting it retries once after reconnect
|
|
3290
|
+
* when the first attempt dies with a connection error.
|
|
3291
|
+
*/
|
|
3292
|
+
wrapQueryBuilder(builder) {
|
|
3293
|
+
if (!builder || typeof builder.then !== "function" || builder.__dbReconnectWrapped) {
|
|
3294
|
+
return builder;
|
|
3295
|
+
}
|
|
3296
|
+
builder.__dbReconnectWrapped = true;
|
|
3297
|
+
const inst = this;
|
|
3298
|
+
const protoThen = Object.getPrototypeOf(builder)?.then;
|
|
3299
|
+
if (typeof protoThen !== "function") {
|
|
3300
|
+
return builder;
|
|
3301
|
+
}
|
|
3302
|
+
builder.then = function(onFulfilled, onRejected) {
|
|
3303
|
+
const run = async () => {
|
|
3304
|
+
try {
|
|
3305
|
+
return await protoThen.call(builder);
|
|
3306
|
+
} catch (error) {
|
|
3307
|
+
if (!inst.isConnectionError(error)) {
|
|
3308
|
+
throw error;
|
|
3309
|
+
}
|
|
3310
|
+
await inst.reconnectAfterConnectionError(error);
|
|
3311
|
+
if (typeof builder.clone === "function") {
|
|
3312
|
+
const retry = builder.clone();
|
|
3313
|
+
retry.client = inst.knexInstance.client;
|
|
3314
|
+
return await protoThen.call(retry);
|
|
3315
|
+
}
|
|
3316
|
+
if (typeof builder.toSQL === "function" && inst.knexInstance) {
|
|
3317
|
+
const sql = builder.toSQL();
|
|
3318
|
+
const statements = Array.isArray(sql) ? sql : [sql];
|
|
3319
|
+
let last;
|
|
3320
|
+
for (const stmt of statements) {
|
|
3321
|
+
last = await inst.knexInstance.raw(stmt.sql, stmt.bindings);
|
|
3322
|
+
}
|
|
3323
|
+
return last;
|
|
3324
|
+
}
|
|
3325
|
+
throw error;
|
|
3326
|
+
}
|
|
3327
|
+
};
|
|
3328
|
+
return run().then(onFulfilled, onRejected);
|
|
3329
|
+
};
|
|
3330
|
+
return builder;
|
|
3331
|
+
}
|
|
3332
|
+
/** knex.raw with auto-reconnect on dead connections. */
|
|
3333
|
+
raw(...args) {
|
|
3334
|
+
if (!this.knexInstance) {
|
|
3335
|
+
throw new Error("Db: Not connected. Call connect() first.");
|
|
3336
|
+
}
|
|
3337
|
+
return this.wrapQueryBuilder(this.knexInstance.raw(...args));
|
|
3338
|
+
}
|
|
3189
3339
|
getErrorMessage(error) {
|
|
3190
3340
|
if (error instanceof AggregateError) {
|
|
3191
3341
|
const errors = error.errors || [];
|
|
@@ -3356,7 +3506,9 @@ var Db = class _Db {
|
|
|
3356
3506
|
throw new Error("Db: Not connected. Call connect() first.");
|
|
3357
3507
|
}
|
|
3358
3508
|
try {
|
|
3359
|
-
return await this.
|
|
3509
|
+
return await this.withConnectionRetry(
|
|
3510
|
+
() => this.knexInstance.schema.hasTable(tableName)
|
|
3511
|
+
);
|
|
3360
3512
|
} catch (error) {
|
|
3361
3513
|
this.logger.error?.(`[Db] Error checking table existence: ${error.message}`);
|
|
3362
3514
|
throw error;
|