@nmakarov/cli-toolkit 0.27.0 → 0.32.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.
@@ -1745,7 +1745,8 @@ var Params = class _Params {
1745
1745
  /**
1746
1746
  * Resolved early in constructor so cleanup does not read params lazily.
1747
1747
  * One of: false (off) | "end" (print at exit) | "top" (print after init,
1748
- * via context.showUsedParamsIfNeeded()).
1748
+ * via context.showUsedParamsIfNeeded()) | "stop" (print after init, then
1749
+ * exit the process — also via context.showUsedParamsIfNeeded()).
1749
1750
  */
1750
1751
  _showUsedParamsMode = false;
1751
1752
  /** Guard so the dump prints at most once (top OR end, never both). */
@@ -1769,8 +1770,9 @@ var Params = class _Params {
1769
1770
  * (absent) / --no-showUsedParams / =false -> false (off)
1770
1771
  * --showUsedParams / =true -> "end" (print at exit)
1771
1772
  * --showUsedParams=top -> "top" (print after init)
1772
- * Read raw (uncoerced) from args so the string "top" isn't forced to a
1773
- * boolean, then track it under the "script" module for the dump itself.
1773
+ * --showUsedParams=stop -> "stop" (print after init, then exit)
1774
+ * Read raw (uncoerced) from args so the string "top"/"stop" isn't forced to
1775
+ * a boolean, then track it under the "script" module for the dump itself.
1774
1776
  */
1775
1777
  _resolveShowUsedParams() {
1776
1778
  const raw = this.args.get("showUsedParams");
@@ -1780,6 +1782,8 @@ var Params = class _Params {
1780
1782
  mode = false;
1781
1783
  } else if (typeof raw === "string" && raw.trim().toLowerCase() === "top") {
1782
1784
  mode = "top";
1785
+ } else if (typeof raw === "string" && raw.trim().toLowerCase() === "stop") {
1786
+ mode = "stop";
1783
1787
  } else {
1784
1788
  const s = typeof raw === "string" ? raw.trim().toLowerCase() : raw;
1785
1789
  const falsey = s === false || s === "false" || s === "0" || s === "no" || s === "off";
@@ -1793,7 +1797,7 @@ var Params = class _Params {
1793
1797
  getShowUsedParams() {
1794
1798
  return this._showUsedParamsMode !== false;
1795
1799
  }
1796
- /** Resolved mode: false | "end" | "top". */
1800
+ /** Resolved mode: false | "end" | "top" | "stop". */
1797
1801
  getShowUsedParamsMode() {
1798
1802
  return this._showUsedParamsMode;
1799
1803
  }
@@ -2508,9 +2512,18 @@ function setup(opts = {}) {
2508
2512
  // the used-params list now (after the script has initialized all its
2509
2513
  // own components), instead of at exit. No-op for the default mode,
2510
2514
  // which prints at exit via the cleanup registered by Params.
2515
+ //
2516
+ // --showUsedParams=stop behaves like "top" but then exits immediately —
2517
+ // a quick "show me the figured params and quit" that skips the flow's
2518
+ // actual work. Like --stopAfter=init, this is a hard exit(0) (registered
2519
+ // cleanups are skipped); call it once components/params are resolved.
2511
2520
  showUsedParamsIfNeeded: () => {
2512
- if (params.getShowUsedParamsMode?.() === "top") {
2513
- params.printUsedParams(logger);
2521
+ const mode = params.getShowUsedParamsMode?.();
2522
+ if (mode !== "top" && mode !== "stop") return;
2523
+ params.printUsedParams(logger);
2524
+ if (mode === "stop") {
2525
+ logger.debug?.("[showUsedParams=stop] params printed \u2014 exiting");
2526
+ process.exit(0);
2514
2527
  }
2515
2528
  }
2516
2529
  };
@@ -3229,7 +3242,7 @@ function defineTasksTable(t, db, tableNameForIndex) {
3229
3242
  t.timestamp("past_due").defaultTo(null);
3230
3243
  t.text("name").notNullable();
3231
3244
  t.text("opid");
3232
- t.json("params");
3245
+ t.jsonb("params");
3233
3246
  t.text("service_group");
3234
3247
  t.integer("instance_number");
3235
3248
  t.text("service_name");
@@ -3238,7 +3251,7 @@ function defineTasksTable(t, db, tableNameForIndex) {
3238
3251
  t.timestamp("status_changed_at").defaultTo(null);
3239
3252
  t.text("progress");
3240
3253
  t.boolean("success");
3241
- t.json("results");
3254
+ t.jsonb("results");
3242
3255
  t.index(["service_group", "status", "priority", "created_at"], `${tableNameForIndex}_claim_idx`);
3243
3256
  t.index(["service_group", "name"], `${tableNameForIndex}_group_name_idx`);
3244
3257
  }
@@ -3253,11 +3266,29 @@ function taskHistoryInsertFromQueueRow(row, overrides) {
3253
3266
  async function ensureTaskTables(context, options = {}) {
3254
3267
  const queueName = options.queueName ?? "tasks";
3255
3268
  const recreate = options.recreate ?? false;
3269
+ const dryRun = options.dryRun ?? false;
3256
3270
  const db = getDb(context);
3271
+ const log = context.logger ?? console;
3257
3272
  const { tasksTable, historyTable, registryTable } = queueToTableNames(queueName);
3258
3273
  const needsTasks = recreate ? true : !await db.tableExists(tasksTable);
3259
3274
  const needsHistory = recreate ? true : !await db.tableExists(historyTable);
3260
3275
  const needsRegistry = recreate ? true : !await db.tableExists(registryTable);
3276
+ if (dryRun) {
3277
+ const plan = [];
3278
+ if (recreate) {
3279
+ plan.push(`DROP TABLE IF EXISTS ${historyTable}, ${tasksTable}, ${registryTable}`);
3280
+ }
3281
+ if (needsTasks) plan.push(`CREATE TABLE ${tasksTable} (tasks queue)`);
3282
+ if (needsHistory) plan.push(`CREATE TABLE ${historyTable} (history mirror)`);
3283
+ if (needsRegistry) plan.push(`CREATE TABLE ${registryTable} (services registry)`);
3284
+ if (plan.length === 0) {
3285
+ log.info?.(`[tasks-schema] dryRun \u2014 queue "${queueName}" already up to date; no DDL`);
3286
+ } else {
3287
+ log.info?.(`[tasks-schema] dryRun \u2014 would run ${plan.length} statement(s) for queue "${queueName}":`);
3288
+ for (const s of plan) log.info?.(` - ${s}`);
3289
+ }
3290
+ return;
3291
+ }
3261
3292
  if (recreate) {
3262
3293
  await db.schema.dropTableIfExists(historyTable);
3263
3294
  await db.schema.dropTableIfExists(tasksTable);
@@ -5680,9 +5711,8 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
5680
5711
  const dbName = String(context?.params?.get?.("dbName") || "local");
5681
5712
  const tableName = String(context?.params?.get?.("table") || "tasks");
5682
5713
  const fallbackRecoverCommand = [
5683
- "npx",
5684
- "tsx",
5685
- "examples/tasks/recover-task.ts",
5714
+ "node",
5715
+ "examples/tasks/recover-task.js",
5686
5716
  `--dbName='${dbName.replace(/'/g, `'\\''`)}'`,
5687
5717
  `--table='${tableName.replace(/'/g, `'\\''`)}'`,
5688
5718
  `--id='${String(row.id).replace(/'/g, `'\\''`)}'`
@@ -5748,6 +5778,8 @@ async function claimNextRunnableTask(context, tasksTable, serviceGroup, registry
5748
5778
  const db = getDb3(context);
5749
5779
  let query = db(tasksTable).where({ status: "idle" }).where(function() {
5750
5780
  this.whereNull("service_group").orWhere({ service_group: serviceGroup });
5781
+ }).where(function() {
5782
+ this.whereNull("next_run_at").orWhere("next_run_at", "<=", db.fn.now());
5751
5783
  }).orderByRaw("CASE WHEN past_due IS NULL THEN 1 ELSE 0 END ASC").orderBy([{ column: "priority", order: "asc" }]).orderByRaw("CASE WHEN completed_at IS NULL THEN 0 ELSE 1 END ASC").orderBy([{ column: "completed_at", order: "asc" }, { column: "created_at", order: "asc" }]).limit(scanLimit);
5752
5784
  if (taskNames && taskNames.length > 0) {
5753
5785
  query = query.whereIn("name", taskNames);