hereby 1.11.1 → 1.13.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.
@@ -1,29 +1,57 @@
1
- import commandLineUsage from "command-line-usage";
2
1
  import pc from "picocolors";
2
+ import Wordwrap from "wordwrapjs";
3
3
  import { compareTaskNames } from "./utils.js";
4
- export function formatTasks(format, tasks, defaultTask) {
4
+ export function formatTasks(format, tasks, defaultTask, columns) {
5
5
  const visibleTasks = [...tasks].filter(isTaskVisible).sort(compareTaskNames);
6
6
  if (format === "simple") {
7
7
  return visibleTasks.map((task) => task.options.name).join("\n");
8
8
  }
9
- return commandLineUsage({
10
- header: "Available tasks",
11
- content: visibleTasks.map((task) => {
12
- var _a;
13
- const name = task === defaultTask
14
- ? `${pc.green(task.options.name)} (default)`
15
- : pc.blue(task.options.name);
16
- let descriptionParts = task.options.description ? [task.options.description] : undefined;
17
- const deps = (_a = task.options.dependencies) === null || _a === void 0 ? void 0 : _a.filter(isTaskVisible).sort(compareTaskNames);
18
- if (deps === null || deps === void 0 ? void 0 : deps.length) {
19
- const depNames = deps.map((task) => pc.blue(task.options.name));
20
- (descriptionParts !== null && descriptionParts !== void 0 ? descriptionParts : (descriptionParts = [])).push(`Depends on: ${depNames.join(", ")}`);
21
- }
22
- return { name, description: descriptionParts === null || descriptionParts === void 0 ? void 0 : descriptionParts.join("\n") };
23
- }),
9
+ const names = visibleTasks.map((task) => task === defaultTask ? `${pc.green(task.options.name)} (default)` : pc.blue(task.options.name));
10
+ const descriptions = visibleTasks.map((task) => {
11
+ var _a, _b;
12
+ let parts = task.options.description ? [task.options.description] : undefined;
13
+ const deps = (_a = task.options.dependencies) === null || _a === void 0 ? void 0 : _a.filter(isTaskVisible).sort(compareTaskNames);
14
+ if (deps === null || deps === void 0 ? void 0 : deps.length) {
15
+ const depNames = deps.map((task) => pc.blue(task.options.name));
16
+ (parts !== null && parts !== void 0 ? parts : (parts = [])).push(`Depends on: ${depNames.join(", ")}`);
17
+ }
18
+ return (_b = parts === null || parts === void 0 ? void 0 : parts.join("\n")) !== null && _b !== void 0 ? _b : "";
24
19
  });
20
+ // There's a 2 space indent plus 3 spaces between columns, hence take away 5
21
+ // padding spaces from the available width
22
+ const maxTotalWidth = columns - 5;
23
+ const maxNameWidth = Math.max(...names.map(visibleLength));
24
+ // Check the name doesn't take up more than half the space
25
+ const nameWidth = Math.min(maxNameWidth, maxTotalWidth >> 1);
26
+ const descriptionWidth = maxTotalWidth - nameWidth;
27
+ const formatted = names.map((name, i) => formatAsColumns(" ", name, nameWidth, descriptions[i], descriptionWidth));
28
+ return `
29
+ ${pc.bold(pc.underline("Available tasks"))}
30
+
31
+ ${formatted.join("")}`;
25
32
  }
26
33
  function isTaskVisible(task) {
27
34
  return !task.options.hiddenFromTaskList;
28
35
  }
36
+ function formatAsColumns(indent, leftText, leftWidth, rightText, rightWidth) {
37
+ const leftLines = wrapText(leftText, leftWidth);
38
+ const rightLines = wrapText(rightText, rightWidth);
39
+ const maxLines = Math.max(leftLines.length, rightLines.length);
40
+ let result = "";
41
+ for (let i = 0; i < maxLines; i++) {
42
+ const leftPart = leftLines[i] || "";
43
+ const rightPart = rightLines[i] || "";
44
+ const paddedLeft = leftPart.padEnd(leftWidth, " ");
45
+ result += `${indent}${paddedLeft} ${rightPart}\n`;
46
+ }
47
+ return result;
48
+ }
49
+ // eslint-disable-next-line no-control-regex
50
+ const ANSI_REGEX = /\u001B\[([0-9]{1,2})m/g;
51
+ function visibleLength(str) {
52
+ return str.replace(ANSI_REGEX, "").length;
53
+ }
54
+ function wrapText(text, maxWidth) {
55
+ return Wordwrap.lines(text, { width: maxWidth, break: true });
56
+ }
29
57
  //# sourceMappingURL=formatTasks.js.map
package/dist/cli/index.js CHANGED
@@ -7,7 +7,7 @@ import { findHerebyfile, loadHerebyfile } from "./loadHerebyfile.js";
7
7
  import { getUsage, parseArgs } from "./parseArgs.js";
8
8
  import { reexec } from "./reexec.js";
9
9
  import { Runner } from "./runner.js";
10
- import { UserError } from "./utils.js";
10
+ import { prettyMilliseconds, UserError } from "./utils.js";
11
11
  export async function main(d) {
12
12
  try {
13
13
  await mainWorker(d);
@@ -42,7 +42,7 @@ async function mainWorker(d) {
42
42
  d.chdir(path.dirname(herebyfilePath));
43
43
  const herebyfile = await loadHerebyfile(herebyfilePath);
44
44
  if (args.printTasks) {
45
- d.log(formatTasks(args.printTasks, herebyfile.tasks.values(), herebyfile.defaultTask));
45
+ d.log(formatTasks(args.printTasks, herebyfile.tasks.values(), herebyfile.defaultTask, d.columns()));
46
46
  return;
47
47
  }
48
48
  const tasks = await selectTasks(d, herebyfile, herebyfilePath, args.run);
@@ -62,7 +62,7 @@ async function mainWorker(d) {
62
62
  finally {
63
63
  const took = performance.now() - start;
64
64
  const failed = runner.failedTasks.length > 0;
65
- d.log(`Completed ${taskNames}${failed ? pc.red(" with errors") : ""} in ${d.prettyMilliseconds(took)}`);
65
+ d.log(`Completed ${taskNames}${failed ? pc.red(" with errors") : ""} in ${prettyMilliseconds(took)}`);
66
66
  if (failed) {
67
67
  const names = runner.failedTasks.sort().map((task) => pc.red(task)).join(", ");
68
68
  d.log(`Failed tasks: ${names}`);
@@ -1,5 +1,5 @@
1
- import commandLineUsage from "command-line-usage";
2
1
  import minimist from "minimist";
2
+ import pc from "picocolors";
3
3
  export function parseArgs(argv) {
4
4
  let parseUnknownAsTask = true;
5
5
  const options = minimist(argv, {
@@ -21,54 +21,29 @@ export function parseArgs(argv) {
21
21
  };
22
22
  }
23
23
  export function getUsage() {
24
- const usage = commandLineUsage([
25
- {
26
- header: "hereby",
27
- content: "A simple task runner.",
28
- },
29
- {
30
- header: "Synopsis",
31
- content: "$ hereby <task>",
32
- },
33
- {
34
- header: "Options",
35
- optionList: [
36
- {
37
- name: "help",
38
- description: "Display this usage guide.",
39
- alias: "h",
40
- type: Boolean,
41
- },
42
- {
43
- name: "herebyfile",
44
- description: "A path to a Herebyfile. Optional.",
45
- type: String,
46
- defaultOption: true,
47
- typeLabel: "{underline path}",
48
- },
49
- {
50
- name: "tasks",
51
- description: "Print a listing of the available tasks.",
52
- alias: "T",
53
- type: Boolean,
54
- },
55
- {
56
- name: "version",
57
- description: "Print the current hereby version.",
58
- type: Boolean,
59
- },
60
- ],
61
- },
62
- {
63
- header: "Example usage",
64
- content: [
65
- "$ hereby build",
66
- "$ hereby build lint",
67
- "$ hereby test --skip someTest --lint=false",
68
- "$ hereby --tasks",
69
- ],
70
- },
71
- ]);
72
- return usage;
24
+ const header = (text) => pc.bold(pc.underline(text));
25
+ return `
26
+ ${header("hereby")}
27
+
28
+ A simple task runner.
29
+
30
+ ${header("Synopsis")}
31
+
32
+ $ hereby <task>
33
+
34
+ ${header("Options")}
35
+
36
+ ${pc.bold("-h, --help")} Display this usage guide.
37
+ ${pc.bold("--herebyfile")} ${pc.underline("path")} A path to a Herebyfile. Optional.
38
+ ${pc.bold("-T, --tasks")} Print a listing of the available tasks.
39
+ ${pc.bold("--version")} Print the current hereby version.
40
+
41
+ ${header("Example usage")}
42
+
43
+ $ hereby build
44
+ $ hereby build lint
45
+ $ hereby test --skip someTest --lint=false
46
+ $ hereby --tasks
47
+ `;
73
48
  }
74
49
  //# sourceMappingURL=parseArgs.js.map
@@ -1,11 +1,11 @@
1
1
  import { performance } from "node:perf_hooks";
2
2
  import pc from "picocolors";
3
+ import { prettyMilliseconds } from "./utils.js";
3
4
  export class Runner {
4
5
  constructor(_d) {
5
6
  this._d = _d;
6
7
  this._addedTasks = new Map();
7
8
  this.failedTasks = [];
8
- this._startTimes = new Map();
9
9
  }
10
10
  async runTasks(...tasks) {
11
11
  // Using allSettled here so that we don't immediately exit; it could be
@@ -32,34 +32,25 @@ export class Runner {
32
32
  }
33
33
  if (!run)
34
34
  return;
35
+ const start = performance.now();
35
36
  try {
36
- this.onTaskStart(task);
37
+ if (this.failedTasks.length === 0) {
38
+ this._d.log(`Starting ${pc.blue(task.options.name)}`);
39
+ }
37
40
  await run();
38
- this.onTaskFinish(task);
41
+ if (this.failedTasks.length === 0) {
42
+ const took = performance.now() - start;
43
+ this._d.log(`Finished ${pc.green(task.options.name)} in ${prettyMilliseconds(took)}`);
44
+ }
39
45
  }
40
46
  catch (e) {
41
- this.onTaskError(task, e);
47
+ this.failedTasks.push(task.options.name);
48
+ if (this.failedTasks.length === 1) {
49
+ const took = performance.now() - start;
50
+ this._d.error(`Error in ${pc.red(task.options.name)} in ${prettyMilliseconds(took)}\n${e}`);
51
+ }
42
52
  throw e;
43
53
  }
44
54
  }
45
- onTaskStart(task) {
46
- this._startTimes.set(task, performance.now());
47
- if (this.failedTasks.length > 0)
48
- return; // Skip logging.
49
- this._d.log(`Starting ${pc.blue(task.options.name)}`);
50
- }
51
- onTaskFinish(task) {
52
- if (this.failedTasks.length > 0)
53
- return; // Skip logging.
54
- const took = performance.now() - this._startTimes.get(task);
55
- this._d.log(`Finished ${pc.green(task.options.name)} in ${this._d.prettyMilliseconds(took)}`);
56
- }
57
- onTaskError(task, e) {
58
- this.failedTasks.push(task.options.name);
59
- if (this.failedTasks.length > 1)
60
- return; // Skip logging.
61
- const took = performance.now() - this._startTimes.get(task);
62
- this._d.error(`Error in ${pc.red(task.options.name)} in ${this._d.prettyMilliseconds(took)}\n${e}`);
63
- }
64
55
  }
65
56
  //# sourceMappingURL=runner.js.map
package/dist/cli/utils.js CHANGED
@@ -28,16 +28,34 @@ export function findUp(dir, predicate) {
28
28
  }
29
29
  return undefined;
30
30
  }
31
+ export function prettyMilliseconds(ms) {
32
+ if (ms < 1000)
33
+ return `${Math.ceil(ms)}ms`;
34
+ const seconds = (ms / 1000) % 60;
35
+ const minutes = Math.floor(ms / 60000) % 60;
36
+ const hours = Math.floor(ms / 3600000);
37
+ // Round to one decimal, with an epsilon to avoid floating point errors (e.g. 5.0000001 -> 5).
38
+ const roundedSeconds = Math.floor(seconds * 10 + 1e-7) / 10;
39
+ const parts = [];
40
+ if (hours > 0)
41
+ parts.push(`${hours}h`);
42
+ if (minutes > 0)
43
+ parts.push(`${minutes}m`);
44
+ if (roundedSeconds > 0) {
45
+ parts.push(roundedSeconds % 1 === 0 ? `${roundedSeconds}s` : `${roundedSeconds.toFixed(1)}s`);
46
+ }
47
+ return parts.join(" ");
48
+ }
31
49
  /**
32
50
  * UserError is a special error that, when caught in the CLI will be printed
33
51
  * as a message only, without stacktrace. Use this instead of process.exit.
34
52
  */
35
53
  export class UserError extends Error {
36
54
  }
37
- export async function real() {
38
- const { default: prettyMilliseconds } = await import("pretty-ms");
55
+ export function real() {
39
56
  /* eslint-disable no-restricted-globals */
40
57
  return {
58
+ columns: () => process.stdout.isTTY && process.stdout.columns || 80,
41
59
  log: console.log,
42
60
  error: console.error,
43
61
  // eslint-disable-next-line @typescript-eslint/unbound-method
@@ -54,7 +72,6 @@ export async function real() {
54
72
  const packageJson = fs.readFileSync(packageJsonURL, "utf8");
55
73
  return JSON.parse(packageJson).version;
56
74
  },
57
- prettyMilliseconds,
58
75
  };
59
76
  /* eslint-enable no-restricted-globals */
60
77
  }
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ if (module.enableCompileCache) {
6
6
  async function run() {
7
7
  const { main } = await import("./cli/index.js");
8
8
  const { real } = await import("./cli/utils.js");
9
- await main(await real());
9
+ await main(real());
10
10
  }
11
11
  void run();
12
12
  //# sourceMappingURL=cli.js.map
package/dist/index.d.ts CHANGED
@@ -27,7 +27,7 @@ export interface TaskOptions {
27
27
  hiddenFromTaskList?: boolean | undefined;
28
28
  }
29
29
  /**
30
- * A hereby Task. To get an instance, call `test`.
30
+ * A hereby Task. To get an instance, call `task`.
31
31
  */
32
32
  export declare class Task {
33
33
  private _;
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
+ import assert from "node:assert";
1
2
  /**
2
- * A hereby Task. To get an instance, call `test`.
3
+ * A hereby Task. To get an instance, call `task`.
3
4
  */
4
5
  export class Task {
5
6
  /* @internal */
@@ -13,34 +14,19 @@ export class Task {
13
14
  // typechecking, so this is helpful.
14
15
  var _a, _b;
15
16
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
16
- if (typeof options.name !== "string") {
17
- throw new TypeError("Task name is not a string.");
18
- }
19
- if (typeof options.description !== "string" && options.description !== undefined) {
20
- throw new TypeError("Task description is not a string or undefined.");
21
- }
22
- if (!Array.isArray(options.dependencies) && options.dependencies !== undefined) {
23
- throw new TypeError("Task dependencies is not an array or undefined.");
24
- }
17
+ assert.ok(typeof options.name === "string", "Task name is not a string.");
18
+ assert.ok(typeof options.description === "string" || options.description === undefined, "Task description is not a string or undefined.");
19
+ assert.ok(Array.isArray(options.dependencies) || options.dependencies === undefined, "Task dependencies is not an array or undefined.");
25
20
  for (const dep of (_a = options.dependencies) !== null && _a !== void 0 ? _a : []) {
26
- if (!(dep instanceof Task)) {
27
- throw new TypeError("Task dependency is not a task.");
28
- }
29
- }
30
- if (typeof options.run !== "function" && options.run !== undefined) {
31
- throw new TypeError("Task run is not a function or undefined.");
21
+ assert.ok(dep instanceof Task, "Task dependency is not a task.");
32
22
  }
23
+ assert.ok(typeof options.run === "function" || options.run === undefined, "Task run is not a function or undefined.");
33
24
  /* eslint-enable @typescript-eslint/no-unnecessary-condition */
34
25
  // Non-type checks.
35
- if (!options.name) {
36
- throw new Error("Task name must not be empty.");
37
- }
38
- if (options.name.startsWith("-")) {
39
- throw new Error('Task name must not start with "-".');
40
- }
41
- if (!((_b = options.dependencies) === null || _b === void 0 ? void 0 : _b.length) && !options.run) {
42
- throw new Error("Task must have a run function or dependencies.");
43
- }
26
+ assert.ok(options.name !== "", "Task name must not be empty.");
27
+ assert.ok(!options.name.startsWith("-"), 'Task name must not start with "-".');
28
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
29
+ assert.ok(!!(((_b = options.dependencies) === null || _b === void 0 ? void 0 : _b.length) || options.run), "Task must have a run function or dependencies.");
44
30
  this.options = options;
45
31
  }
46
32
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hereby",
3
- "version": "1.11.1",
3
+ "version": "1.13.0",
4
4
  "description": "A simple task runner",
5
5
  "repository": "github:jakebailey/hereby",
6
6
  "type": "module",
@@ -39,38 +39,35 @@
39
39
  "./dist/index.d.ts"
40
40
  ],
41
41
  "dependencies": {
42
- "command-line-usage": "^6.1.3",
43
42
  "fastest-levenshtein": "^1.0.16",
44
43
  "minimist": "^1.2.8",
45
44
  "picocolors": "^1.1.0",
46
- "pretty-ms": "^8.0.0"
45
+ "wordwrapjs": "^5.1.1"
47
46
  },
48
47
  "devDependencies": {
49
48
  "@ava/typescript": "^3.0.1",
50
- "@changesets/cli": "^2.29.7",
51
- "@codspeed/tinybench-plugin": "^4.0.1",
52
- "@fast-check/ava": "2.0.1",
53
- "@tsconfig/node12": "^12.1.5",
54
- "@types/command-line-usage": "^5.0.4",
49
+ "@changesets/cli": "^2.30.0",
50
+ "@eslint/js": "^10.0.1",
51
+ "@tsconfig/node12": "^12.1.7",
55
52
  "@types/minimist": "^1.2.5",
56
- "@types/node": "^24.7.2",
53
+ "@types/node": "^25.3.3",
57
54
  "@types/tmp": "^0.2.6",
55
+ "@types/wordwrapjs": "^5.1.2",
58
56
  "ava": "~5.0.1",
59
- "c8": "^10.1.3",
60
- "dprint": "^0.50.2",
61
- "eslint": "^9.37.0",
62
- "eslint-plugin-ava": "^15.1.0",
57
+ "c8": "^11.0.0",
58
+ "dprint": "^0.52.0",
59
+ "eslint": "^10.0.2",
60
+ "eslint-plugin-ava": "^16.0.0",
63
61
  "eslint-plugin-simple-import-sort": "^12.1.1",
64
- "eslint-plugin-unicorn": "^61.0.2",
62
+ "eslint-plugin-unicorn": "^63.0.0",
65
63
  "execa": "^6.1.0",
66
- "globals": "^16.4.0",
64
+ "globals": "^17.4.0",
67
65
  "monocart-coverage-reports": "^2.12.9",
68
66
  "moq.ts": "^10.1.0",
69
67
  "rimraf": "^5.0.10",
70
- "tinybench": "~2.8.0",
71
68
  "tmp": "0.2.1",
72
69
  "typescript": "^5.9.3",
73
- "typescript-eslint": "^8.46.0"
70
+ "typescript-eslint": "^8.56.1"
74
71
  },
75
72
  "overrides": {
76
73
  "ava": {