hereby 1.14.0 → 1.15.1

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,31 +1,32 @@
1
- import pc from "picocolors";
1
+ import * as style from "./style.js";
2
2
  import { compareTaskNames } from "./utils.js";
3
3
  export function formatTasks(format, tasks, defaultTask, columns) {
4
4
  const visibleTasks = [...tasks].filter(isTaskVisible).sort(compareTaskNames);
5
5
  if (format === "simple") {
6
6
  return visibleTasks.map((task) => task.options.name).join("\n");
7
7
  }
8
- const names = visibleTasks.map((task) => task === defaultTask ? `${pc.green(task.options.name)} (default)` : pc.blue(task.options.name));
8
+ const names = visibleTasks.map((task) => task === defaultTask ? `${style.green(task.options.name)} (default)` : style.blue(task.options.name));
9
9
  const descriptions = visibleTasks.map((task) => {
10
10
  var _a, _b;
11
11
  let parts = task.options.description ? [task.options.description] : undefined;
12
12
  const deps = (_a = task.options.dependencies) === null || _a === void 0 ? void 0 : _a.filter(isTaskVisible).sort(compareTaskNames);
13
13
  if (deps === null || deps === void 0 ? void 0 : deps.length) {
14
- const depNames = deps.map((task) => pc.blue(task.options.name));
14
+ const depNames = deps.map((task) => style.blue(task.options.name));
15
15
  (parts !== null && parts !== void 0 ? parts : (parts = [])).push(`Depends on: ${depNames.join(", ")}`);
16
16
  }
17
17
  return (_b = parts === null || parts === void 0 ? void 0 : parts.join("\n")) !== null && _b !== void 0 ? _b : "";
18
18
  });
19
19
  // There's a 2 space indent plus 3 spaces between columns, hence take away 5
20
- // padding spaces from the available width
21
- const maxTotalWidth = columns - 5;
22
- const maxNameWidth = Math.max(...names.map(visibleLength));
20
+ // padding spaces from the available width. Keep widths positive so narrow
21
+ // terminals still wrap safely.
22
+ const maxTotalWidth = Math.max(1, columns - 5);
23
+ const maxNameWidth = Math.max(1, ...names.map(visibleLength));
23
24
  // Check the name doesn't take up more than half the space
24
- const nameWidth = Math.min(maxNameWidth, maxTotalWidth >> 1);
25
- const descriptionWidth = maxTotalWidth - nameWidth;
25
+ const nameWidth = Math.min(maxNameWidth, Math.max(1, maxTotalWidth >> 1));
26
+ const descriptionWidth = Math.max(1, maxTotalWidth - nameWidth);
26
27
  const formatted = names.map((name, i) => formatAsColumns(" ", name, nameWidth, descriptions[i], descriptionWidth));
27
28
  return `
28
- ${pc.bold(pc.underline("Available tasks"))}
29
+ ${style.bold(style.underline("Available tasks"))}
29
30
 
30
31
  ${formatted.join("")}`;
31
32
  }
@@ -54,6 +55,7 @@ const TOKEN_REGEX = /[^\s-]+?-\b|\S+|\s+/g;
54
55
  function wrapText(text, maxWidth) {
55
56
  var _a;
56
57
  const result = [];
58
+ maxWidth = Math.max(1, maxWidth);
57
59
  for (const line of text.split(/\r?\n/)) {
58
60
  let current = "";
59
61
  for (const token of (_a = line.match(TOKEN_REGEX)) !== null && _a !== void 0 ? _a : []) {
package/dist/cli/index.js CHANGED
@@ -1,19 +1,19 @@
1
1
  import path from "node:path";
2
2
  import { performance } from "node:perf_hooks";
3
3
  import { types } from "node:util";
4
- import pc from "picocolors";
5
4
  import { findHerebyfile, loadHerebyfile } from "./loadHerebyfile.js";
6
5
  import { getUsage, parseArgs } from "./parseArgs.js";
7
6
  import { reexec } from "./reexec.js";
8
7
  import { Runner } from "./runner.js";
9
- import { prettyMilliseconds, UserError } from "./utils.js";
8
+ import * as style from "./style.js";
9
+ import { findSimilar, prettyMilliseconds, UserError } from "./utils.js";
10
10
  export async function main(d) {
11
11
  try {
12
12
  await mainWorker(d);
13
13
  }
14
14
  catch (e) {
15
15
  if (e instanceof UserError) {
16
- d.error(`${pc.red("Error")}: ${e.message}`);
16
+ d.error(`${style.red("Error")}: ${e.message}`);
17
17
  }
18
18
  else if (types.isNativeError(e) && e.stack) { // eslint-disable-line @typescript-eslint/no-deprecated
19
19
  d.error(e.stack);
@@ -46,8 +46,8 @@ async function mainWorker(d) {
46
46
  return;
47
47
  }
48
48
  const tasks = await selectTasks(d, herebyfile, herebyfilePath, args.run);
49
- const taskNames = tasks.map((task) => pc.blue(task.options.name)).join(", ");
50
- d.log(`Using ${pc.yellow(d.simplifyPath(herebyfilePath))} to run ${taskNames}`);
49
+ const taskNames = tasks.map((task) => style.blue(task.options.name)).join(", ");
50
+ d.log(`Using ${style.yellow(d.simplifyPath(herebyfilePath))} to run ${taskNames}`);
51
51
  const start = performance.now();
52
52
  const runner = new Runner(d);
53
53
  try {
@@ -62,9 +62,9 @@ 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 ${prettyMilliseconds(took)}`);
65
+ d.log(`Completed ${taskNames}${failed ? style.red(" with errors") : ""} in ${prettyMilliseconds(took)}`);
66
66
  if (failed) {
67
- const names = runner.failedTasks.sort().map((task) => pc.red(task)).join(", ");
67
+ const names = runner.failedTasks.sort().map((task) => style.red(task)).join(", ");
68
68
  d.log(`Failed tasks: ${names}`);
69
69
  }
70
70
  }
@@ -76,20 +76,13 @@ export async function selectTasks(d, herebyfile, herebyfilePath, taskNames) {
76
76
  return [herebyfile.defaultTask];
77
77
  throw new UserError(`No default task has been exported from ${d.simplifyPath(herebyfilePath)}; please specify a task name.`);
78
78
  }
79
- const tasks = [];
80
- for (const name of taskNames) {
79
+ return taskNames.map((name) => {
81
80
  const task = herebyfile.tasks.get(name);
82
- if (!task) {
83
- let message = `Task "${name}" does not exist or is not exported from ${d.simplifyPath(herebyfilePath)}.`;
84
- const { closest, distance } = await import("fastest-levenshtein");
85
- const candidate = closest(name, [...herebyfile.tasks.keys()]);
86
- if (distance(name, candidate) < name.length * 0.4) {
87
- message += ` Did you mean "${candidate}"?`;
88
- }
89
- throw new UserError(message);
90
- }
91
- tasks.push(task);
92
- }
93
- return tasks;
81
+ if (task)
82
+ return task;
83
+ const suggestion = findSimilar(name, herebyfile.tasks.keys());
84
+ throw new UserError(`Task "${name}" does not exist or is not exported from ${d.simplifyPath(herebyfilePath)}.`
85
+ + (suggestion ? ` Did you mean "${suggestion}"?` : ""));
86
+ });
94
87
  }
95
88
  //# sourceMappingURL=index.js.map
@@ -1,8 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
- import pc from "picocolors";
5
4
  import { Task } from "../index.js";
5
+ import * as style from "./style.js";
6
6
  import { findUp, UserError } from "./utils.js";
7
7
  const herebyfileRegExp = /^herebyfile\.m?[jt]s$/i;
8
8
  export function findHerebyfile(dir) {
@@ -42,7 +42,7 @@ export async function loadHerebyfile(herebyfilePath) {
42
42
  defaultTask = value;
43
43
  }
44
44
  else if (exportedTasks.has(value)) {
45
- throw new UserError(`Task "${pc.blue(value.options.name)}" has been exported twice.`);
45
+ throw new UserError(`Task "${style.blue(value.options.name)}" has been exported twice.`);
46
46
  }
47
47
  else {
48
48
  exportedTasks.add(value);
@@ -60,30 +60,24 @@ export async function loadHerebyfile(herebyfilePath) {
60
60
  const tasks = new Map([...exportedTasks].map((task) => [task.options.name, task]));
61
61
  return { tasks, defaultTask };
62
62
  }
63
- function checkTaskInvariants(tasks) {
64
- const checkedTasks = new Set();
65
- const taskStack = new Set();
66
- const seenNames = new Set();
67
- checkTaskInvariantsWorker(tasks);
68
- function checkTaskInvariantsWorker(tasks) {
69
- for (const task of tasks) {
70
- if (checkedTasks.has(task))
71
- continue;
72
- if (taskStack.has(task)) {
73
- throw new UserError(`Task "${pc.blue(task.options.name)}" references itself.`);
74
- }
75
- const name = task.options.name;
76
- if (seenNames.has(name)) {
77
- throw new UserError(`Task "${pc.blue(name)}" was declared twice.`);
78
- }
79
- seenNames.add(name);
80
- if (task.options.dependencies) {
81
- taskStack.add(task);
82
- checkTaskInvariantsWorker(task.options.dependencies);
83
- taskStack.delete(task);
84
- }
85
- checkedTasks.add(task);
63
+ function checkTaskInvariants(tasks, checkedTasks = new Set(), taskStack = new Set(), seenNames = new Set()) {
64
+ for (const task of tasks) {
65
+ if (checkedTasks.has(task))
66
+ continue;
67
+ if (taskStack.has(task)) {
68
+ throw new UserError(`Task "${style.blue(task.options.name)}" references itself.`);
69
+ }
70
+ const name = task.options.name;
71
+ if (seenNames.has(name)) {
72
+ throw new UserError(`Task "${style.blue(name)}" was declared twice.`);
73
+ }
74
+ seenNames.add(name);
75
+ if (task.options.dependencies) {
76
+ taskStack.add(task);
77
+ checkTaskInvariants(task.options.dependencies, checkedTasks, taskStack, seenNames);
78
+ taskStack.delete(task);
86
79
  }
80
+ checkedTasks.add(task);
87
81
  }
88
82
  }
89
83
  //# sourceMappingURL=loadHerebyfile.js.map
@@ -1,27 +1,46 @@
1
- import minimist from "minimist";
2
- import pc from "picocolors";
1
+ import * as style from "./style.js";
2
+ import { UserError } from "./utils.js";
3
3
  export function parseArgs(argv) {
4
- let parseUnknownAsTask = true;
5
- const options = minimist(argv, {
6
- "--": true,
7
- string: ["herebyfile"],
8
- boolean: ["tasks", "tasks-simple", "help", "version"],
9
- alias: {
10
- "h": "help",
11
- "T": "tasks",
12
- },
13
- unknown: (name) => parseUnknownAsTask && (parseUnknownAsTask = !name.startsWith("-")),
14
- });
15
- return {
16
- help: options["help"],
17
- run: options._,
18
- herebyfile: options["herebyfile"],
19
- printTasks: options["tasks"] ? "normal" : (options["tasks-simple"] ? "simple" : undefined),
20
- version: options["version"],
21
- };
4
+ const run = [];
5
+ let help = false, version = false, collect = true, tasks = false, tasksSimple = false;
6
+ let herebyfile;
7
+ for (let i = 0; i < argv.length; i++) {
8
+ const arg = argv[i];
9
+ if (arg === "--")
10
+ break;
11
+ if (!arg.startsWith("-") || arg === "-") {
12
+ if (collect)
13
+ run.push(arg);
14
+ continue;
15
+ }
16
+ const eq = arg.indexOf("=");
17
+ const name = (eq === -1 ? arg : arg.slice(0, eq)).replace(/^--?/, "");
18
+ const peek = eq !== -1 ? arg.slice(eq + 1) : argv[i + 1];
19
+ const consume = (pred) => eq !== -1 || (pred(peek) && ++i) ? peek : undefined;
20
+ const bool = () => consume((s) => s === "true" || s === "false") !== "false";
21
+ const str = () => {
22
+ const v = consume((s) => s !== undefined && !s.startsWith("-"));
23
+ if (!v)
24
+ throw new UserError(`Option --${name} requires a value.`);
25
+ return v;
26
+ };
27
+ if (name === "h" || name === "help")
28
+ help = bool();
29
+ else if (name === "T" || name === "tasks")
30
+ tasks = bool();
31
+ else if (name === "tasks-simple")
32
+ tasksSimple = bool();
33
+ else if (name === "version")
34
+ version = bool();
35
+ else if (name === "herebyfile")
36
+ herebyfile = str();
37
+ else
38
+ collect = false;
39
+ }
40
+ return { help, run, herebyfile, printTasks: tasks ? "normal" : tasksSimple ? "simple" : undefined, version };
22
41
  }
23
42
  export function getUsage() {
24
- const header = (text) => pc.bold(pc.underline(text));
43
+ const header = (text) => style.bold(style.underline(text));
25
44
  return `
26
45
  ${header("hereby")}
27
46
 
@@ -33,10 +52,10 @@ ${header("Synopsis")}
33
52
 
34
53
  ${header("Options")}
35
54
 
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.
55
+ ${style.bold("-h, --help")} Display this usage guide.
56
+ ${style.bold("--herebyfile")} ${style.underline("path")} A path to a Herebyfile. Optional.
57
+ ${style.bold("-T, --tasks")} Print a listing of the available tasks.
58
+ ${style.bold("--version")} Print the current hereby version.
40
59
 
41
60
  ${header("Example usage")}
42
61
 
@@ -1,5 +1,5 @@
1
1
  import { performance } from "node:perf_hooks";
2
- import pc from "picocolors";
2
+ import * as style from "./style.js";
3
3
  import { prettyMilliseconds } from "./utils.js";
4
4
  export class Runner {
5
5
  constructor(_d) {
@@ -35,19 +35,19 @@ export class Runner {
35
35
  const start = performance.now();
36
36
  try {
37
37
  if (this.failedTasks.length === 0) {
38
- this._d.log(`Starting ${pc.blue(task.options.name)}`);
38
+ this._d.log(`Starting ${style.blue(task.options.name)}`);
39
39
  }
40
40
  await run();
41
41
  if (this.failedTasks.length === 0) {
42
42
  const took = performance.now() - start;
43
- this._d.log(`Finished ${pc.green(task.options.name)} in ${prettyMilliseconds(took)}`);
43
+ this._d.log(`Finished ${style.green(task.options.name)} in ${prettyMilliseconds(took)}`);
44
44
  }
45
45
  }
46
46
  catch (e) {
47
47
  this.failedTasks.push(task.options.name);
48
48
  if (this.failedTasks.length === 1) {
49
49
  const took = performance.now() - start;
50
- this._d.error(`Error in ${pc.red(task.options.name)} in ${prettyMilliseconds(took)}\n${e}`);
50
+ this._d.error(`Error in ${style.red(task.options.name)} in ${prettyMilliseconds(took)}\n${e}`);
51
51
  }
52
52
  throw e;
53
53
  }
@@ -0,0 +1,25 @@
1
+ /* eslint-disable no-restricted-globals, @typescript-eslint/prefer-nullish-coalescing */
2
+ // NO_COLOR (https://no-color.org) takes precedence over everything when set
3
+ // to any non-empty value. FORCE_COLOR=0/false explicitly disables; any other
4
+ // truthy value forces colors on. Otherwise we enable colors for TTY stdout
5
+ // (excluding TERM=dumb) or when running in CI.
6
+ export function isColorEnabled(env, isTTY, platform) {
7
+ if (env["NO_COLOR"])
8
+ return false;
9
+ const forceColor = env["FORCE_COLOR"];
10
+ if (forceColor === "0" || forceColor === "false")
11
+ return false;
12
+ return !!(forceColor || platform === "win32" || (isTTY && env["TERM"] !== "dumb") || env["CI"]);
13
+ }
14
+ export const enabled = isColorEnabled(process.env, process.stdout.isTTY, process.platform);
15
+ /* eslint-enable no-restricted-globals, @typescript-eslint/prefer-nullish-coalescing */
16
+ export function wrap(enabled, open, close) {
17
+ return enabled ? (s) => open + s + close : (s) => s;
18
+ }
19
+ export const red = wrap(enabled, "\u001B[31m", "\u001B[39m");
20
+ export const green = wrap(enabled, "\u001B[32m", "\u001B[39m");
21
+ export const yellow = wrap(enabled, "\u001B[33m", "\u001B[39m");
22
+ export const blue = wrap(enabled, "\u001B[34m", "\u001B[39m");
23
+ export const bold = wrap(enabled, "\u001B[1m", "\u001B[22m");
24
+ export const underline = wrap(enabled, "\u001B[4m", "\u001B[24m");
25
+ //# sourceMappingURL=style.js.map
package/dist/cli/utils.js CHANGED
@@ -8,13 +8,9 @@ export function compareTaskNames(a, b) {
8
8
  const compareStrings = new Intl.Collator(undefined, { numeric: true }).compare;
9
9
  // Exported for testing.
10
10
  export function simplifyPath(p) {
11
- p = path.normalize(p);
11
+ const normalized = path.normalize(p);
12
12
  const homedir = path.normalize(os.homedir() + path.sep);
13
- if (p.startsWith(homedir)) {
14
- p = p.slice(homedir.length);
15
- return `~${path.sep}${p}`;
16
- }
17
- return p;
13
+ return normalized.startsWith(homedir) ? `~${path.sep}${normalized.slice(homedir.length)}` : normalized;
18
14
  }
19
15
  export function findUp(dir, predicate) {
20
16
  const root = path.parse(dir).root;
@@ -36,15 +32,11 @@ export function prettyMilliseconds(ms) {
36
32
  const hours = Math.floor(ms / 3600000);
37
33
  // Round to one decimal, with an epsilon to avoid floating point errors (e.g. 5.0000001 -> 5).
38
34
  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(" ");
35
+ return [
36
+ hours > 0 && `${hours}h`,
37
+ minutes > 0 && `${minutes}m`,
38
+ roundedSeconds > 0 && (roundedSeconds % 1 === 0 ? `${roundedSeconds}s` : `${roundedSeconds.toFixed(1)}s`),
39
+ ].filter(Boolean).join(" ");
48
40
  }
49
41
  /**
50
42
  * UserError is a special error that, when caught in the CLI will be printed
@@ -52,6 +44,31 @@ export function prettyMilliseconds(ms) {
52
44
  */
53
45
  export class UserError extends Error {
54
46
  }
47
+ /**
48
+ * Returns the candidate most similar to `target`, if any is close enough
49
+ * to be a plausible typo correction.
50
+ */
51
+ export function findSimilar(target, candidates) {
52
+ let best;
53
+ let bestDistance = Math.ceil(target.length * 0.4);
54
+ for (const candidate of candidates) {
55
+ const row = Array.from({ length: candidate.length + 1 }, (_, j) => j);
56
+ for (let i = 1; i <= target.length; i++) {
57
+ let diag = row[0];
58
+ row[0] = i;
59
+ for (let j = 1; j <= candidate.length; j++) {
60
+ const tmp = row[j];
61
+ row[j] = Math.min(row[j - 1] + 1, tmp + 1, diag + (target[i - 1] === candidate[j - 1] ? 0 : 1));
62
+ diag = tmp;
63
+ }
64
+ }
65
+ if (row[candidate.length] < bestDistance) {
66
+ best = candidate;
67
+ bestDistance = row[candidate.length];
68
+ }
69
+ }
70
+ return best;
71
+ }
55
72
  export function real() {
56
73
  /* eslint-disable no-restricted-globals */
57
74
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hereby",
3
- "version": "1.14.0",
3
+ "version": "1.15.1",
4
4
  "description": "A simple task runner",
5
5
  "repository": "github:jakebailey/hereby",
6
6
  "type": "module",
@@ -38,69 +38,26 @@
38
38
  "!**/__tests__/**",
39
39
  "./dist/index.d.ts"
40
40
  ],
41
- "dependencies": {
42
- "fastest-levenshtein": "^1.0.16",
43
- "minimist": "^1.2.8",
44
- "picocolors": "^1.1.1"
45
- },
46
41
  "devDependencies": {
47
- "@ava/typescript": "^3.0.1",
48
- "@changesets/cli": "^2.30.0",
42
+ "@changesets/cli": "^2.31.0",
49
43
  "@eslint/js": "^10.0.1",
50
- "@tsconfig/node12": "^12.1.7",
51
- "@types/minimist": "^1.2.5",
52
- "@types/node": "^25.3.5",
53
- "ava": "~5.0.1",
54
- "c8": "^11.0.0",
55
- "dprint": "^0.52.0",
56
- "eslint": "^10.0.2",
57
- "eslint-plugin-ava": "^16.0.0",
58
- "eslint-plugin-simple-import-sort": "^12.1.1",
59
- "eslint-plugin-unicorn": "^63.0.0",
60
- "execa": "^6.1.0",
61
- "globals": "^17.4.0",
62
- "monocart-coverage-reports": "^2.12.9",
63
- "moq.ts": "^10.1.0",
64
- "rimraf": "^6.1.3",
65
- "typescript": "^5.9.3",
66
- "typescript-eslint": "^8.56.1"
67
- },
68
- "overrides": {
69
- "ava": {
70
- "emittery": "1.0.0"
71
- }
44
+ "@types/node": "^25.6.0",
45
+ "dprint": "^0.54.0",
46
+ "eslint": "^10.2.1",
47
+ "eslint-plugin-simple-import-sort": "^13.0.0",
48
+ "globals": "^17.5.0",
49
+ "monocart-coverage-reports": "^2.12.11",
50
+ "typescript": "^6.0.3",
51
+ "typescript-eslint": "^8.59.0"
72
52
  },
73
53
  "packageManager": "npm@8.19.4",
74
54
  "scripts": {
75
55
  "ci": "npm ci",
76
56
  "build": "tsc",
77
57
  "watch": "tsc --watch",
78
- "test": "ava",
79
- "coverage": "c8 --experimental-monocart ava",
80
- "prepack": "rimraf dist && npm run build",
58
+ "test": "node dist/__tests__/runTests.js",
59
+ "coverage": "mcr node dist/__tests__/runTests.js",
60
+ "prepack": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\" && npm run build",
81
61
  "version": "changeset version && npm install"
82
- },
83
- "ava": {
84
- "files": [
85
- "**/*.test.ts"
86
- ],
87
- "typescript": {
88
- "rewritePaths": {
89
- "src/": "dist/"
90
- },
91
- "compile": false
92
- },
93
- "environmentVariables": {
94
- "NO_COLOR": "1"
95
- }
96
- },
97
- "c8": {
98
- "all": true,
99
- "include": "dist",
100
- "reporter": [
101
- "text",
102
- "html",
103
- "lcov"
104
- ]
105
62
  }
106
63
  }