@nmakarov/cli-toolkit 0.27.0 → 0.29.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.
@@ -1761,7 +1761,8 @@ var Params = class _Params {
1761
1761
  /**
1762
1762
  * Resolved early in constructor so cleanup does not read params lazily.
1763
1763
  * One of: false (off) | "end" (print at exit) | "top" (print after init,
1764
- * via context.showUsedParamsIfNeeded()).
1764
+ * via context.showUsedParamsIfNeeded()) | "stop" (print after init, then
1765
+ * exit the process — also via context.showUsedParamsIfNeeded()).
1765
1766
  */
1766
1767
  _showUsedParamsMode = false;
1767
1768
  /** Guard so the dump prints at most once (top OR end, never both). */
@@ -1785,8 +1786,9 @@ var Params = class _Params {
1785
1786
  * (absent) / --no-showUsedParams / =false -> false (off)
1786
1787
  * --showUsedParams / =true -> "end" (print at exit)
1787
1788
  * --showUsedParams=top -> "top" (print after init)
1788
- * Read raw (uncoerced) from args so the string "top" isn't forced to a
1789
- * boolean, then track it under the "script" module for the dump itself.
1789
+ * --showUsedParams=stop -> "stop" (print after init, then exit)
1790
+ * Read raw (uncoerced) from args so the string "top"/"stop" isn't forced to
1791
+ * a boolean, then track it under the "script" module for the dump itself.
1790
1792
  */
1791
1793
  _resolveShowUsedParams() {
1792
1794
  const raw = this.args.get("showUsedParams");
@@ -1796,6 +1798,8 @@ var Params = class _Params {
1796
1798
  mode = false;
1797
1799
  } else if (typeof raw === "string" && raw.trim().toLowerCase() === "top") {
1798
1800
  mode = "top";
1801
+ } else if (typeof raw === "string" && raw.trim().toLowerCase() === "stop") {
1802
+ mode = "stop";
1799
1803
  } else {
1800
1804
  const s = typeof raw === "string" ? raw.trim().toLowerCase() : raw;
1801
1805
  const falsey = s === false || s === "false" || s === "0" || s === "no" || s === "off";
@@ -1809,7 +1813,7 @@ var Params = class _Params {
1809
1813
  getShowUsedParams() {
1810
1814
  return this._showUsedParamsMode !== false;
1811
1815
  }
1812
- /** Resolved mode: false | "end" | "top". */
1816
+ /** Resolved mode: false | "end" | "top" | "stop". */
1813
1817
  getShowUsedParamsMode() {
1814
1818
  return this._showUsedParamsMode;
1815
1819
  }
@@ -2524,9 +2528,18 @@ function setup(opts = {}) {
2524
2528
  // the used-params list now (after the script has initialized all its
2525
2529
  // own components), instead of at exit. No-op for the default mode,
2526
2530
  // which prints at exit via the cleanup registered by Params.
2531
+ //
2532
+ // --showUsedParams=stop behaves like "top" but then exits immediately —
2533
+ // a quick "show me the figured params and quit" that skips the flow's
2534
+ // actual work. Like --stopAfter=init, this is a hard exit(0) (registered
2535
+ // cleanups are skipped); call it once components/params are resolved.
2527
2536
  showUsedParamsIfNeeded: () => {
2528
- if (params.getShowUsedParamsMode?.() === "top") {
2529
- params.printUsedParams(logger);
2537
+ const mode = params.getShowUsedParamsMode?.();
2538
+ if (mode !== "top" && mode !== "stop") return;
2539
+ params.printUsedParams(logger);
2540
+ if (mode === "stop") {
2541
+ logger.debug?.("[showUsedParams=stop] params printed \u2014 exiting");
2542
+ process.exit(0);
2530
2543
  }
2531
2544
  }
2532
2545
  };
@@ -3245,7 +3258,7 @@ function defineTasksTable(t, db, tableNameForIndex) {
3245
3258
  t.timestamp("past_due").defaultTo(null);
3246
3259
  t.text("name").notNullable();
3247
3260
  t.text("opid");
3248
- t.json("params");
3261
+ t.jsonb("params");
3249
3262
  t.text("service_group");
3250
3263
  t.integer("instance_number");
3251
3264
  t.text("service_name");
@@ -3254,7 +3267,7 @@ function defineTasksTable(t, db, tableNameForIndex) {
3254
3267
  t.timestamp("status_changed_at").defaultTo(null);
3255
3268
  t.text("progress");
3256
3269
  t.boolean("success");
3257
- t.json("results");
3270
+ t.jsonb("results");
3258
3271
  t.index(["service_group", "status", "priority", "created_at"], `${tableNameForIndex}_claim_idx`);
3259
3272
  t.index(["service_group", "name"], `${tableNameForIndex}_group_name_idx`);
3260
3273
  }
@@ -3269,11 +3282,29 @@ function taskHistoryInsertFromQueueRow(row, overrides) {
3269
3282
  async function ensureTaskTables(context, options = {}) {
3270
3283
  const queueName = options.queueName ?? "tasks";
3271
3284
  const recreate = options.recreate ?? false;
3285
+ const dryRun = options.dryRun ?? false;
3272
3286
  const db = getDb(context);
3287
+ const log = context.logger ?? console;
3273
3288
  const { tasksTable, historyTable, registryTable } = queueToTableNames(queueName);
3274
3289
  const needsTasks = recreate ? true : !await db.tableExists(tasksTable);
3275
3290
  const needsHistory = recreate ? true : !await db.tableExists(historyTable);
3276
3291
  const needsRegistry = recreate ? true : !await db.tableExists(registryTable);
3292
+ if (dryRun) {
3293
+ const plan = [];
3294
+ if (recreate) {
3295
+ plan.push(`DROP TABLE IF EXISTS ${historyTable}, ${tasksTable}, ${registryTable}`);
3296
+ }
3297
+ if (needsTasks) plan.push(`CREATE TABLE ${tasksTable} (tasks queue)`);
3298
+ if (needsHistory) plan.push(`CREATE TABLE ${historyTable} (history mirror)`);
3299
+ if (needsRegistry) plan.push(`CREATE TABLE ${registryTable} (services registry)`);
3300
+ if (plan.length === 0) {
3301
+ log.info?.(`[tasks-schema] dryRun \u2014 queue "${queueName}" already up to date; no DDL`);
3302
+ } else {
3303
+ log.info?.(`[tasks-schema] dryRun \u2014 would run ${plan.length} statement(s) for queue "${queueName}":`);
3304
+ for (const s of plan) log.info?.(` - ${s}`);
3305
+ }
3306
+ return;
3307
+ }
3277
3308
  if (recreate) {
3278
3309
  await db.schema.dropTableIfExists(historyTable);
3279
3310
  await db.schema.dropTableIfExists(tasksTable);
@@ -5696,9 +5727,8 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
5696
5727
  const dbName = String(context?.params?.get?.("dbName") || "local");
5697
5728
  const tableName = String(context?.params?.get?.("table") || "tasks");
5698
5729
  const fallbackRecoverCommand = [
5699
- "npx",
5700
- "tsx",
5701
- "examples/tasks/recover-task.ts",
5730
+ "node",
5731
+ "examples/tasks/recover-task.js",
5702
5732
  `--dbName='${dbName.replace(/'/g, `'\\''`)}'`,
5703
5733
  `--table='${tableName.replace(/'/g, `'\\''`)}'`,
5704
5734
  `--id='${String(row.id).replace(/'/g, `'\\''`)}'`
@@ -5764,6 +5794,8 @@ async function claimNextRunnableTask(context, tasksTable, serviceGroup, registry
5764
5794
  const db = getDb3(context);
5765
5795
  let query = db(tasksTable).where({ status: "idle" }).where(function() {
5766
5796
  this.whereNull("service_group").orWhere({ service_group: serviceGroup });
5797
+ }).where(function() {
5798
+ this.whereNull("next_run_at").orWhere("next_run_at", "<=", db.fn.now());
5767
5799
  }).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);
5768
5800
  if (taskNames && taskNames.length > 0) {
5769
5801
  query = query.whereIn("name", taskNames);