hereby 1.8.9 → 1.9.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/README.md CHANGED
@@ -6,6 +6,7 @@
6
6
  [![tokei](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/jakebailey/hereby/gh-pages/tokei.json)](https://github.com/XAMPPRocky/tokei)
7
7
  [![ci](https://github.com/jakebailey/hereby/actions/workflows/ci.yml/badge.svg)](https://github.com/jakebailey/hereby/actions/workflows/ci.yml)
8
8
  [![codecov](https://codecov.io/gh/jakebailey/hereby/branch/main/graph/badge.svg?token=YL2Z1uk5dh)](https://codecov.io/gh/jakebailey/hereby)
9
+ [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/jakebailey/hereby/badge)](https://securityscorecards.dev/viewer/?uri=github.com/jakebailey/hereby)
9
10
 
10
11
  > _I hereby declare thee built._
11
12
 
package/dist/cli/index.js CHANGED
@@ -1,25 +1,22 @@
1
1
  import path from "node:path";
2
- import util from "node:util";
2
+ import { performance } from "node:perf_hooks";
3
+ import { types } from "node:util";
3
4
  import pc from "picocolors";
4
5
  import { formatTasks } from "./formatTasks.js";
5
6
  import { findHerebyfile, loadHerebyfile } from "./loadHerebyfile.js";
6
7
  import { getUsage, parseArgs } from "./parseArgs.js";
7
8
  import { reexec } from "./reexec.js";
8
9
  import { Runner } from "./runner.js";
9
- import { ExitCodeError, UserError } from "./utils.js";
10
+ import { UserError } from "./utils.js";
10
11
  export async function main(d) {
11
12
  try {
12
13
  await mainWorker(d);
13
14
  }
14
15
  catch (e) {
15
- if (e instanceof ExitCodeError) {
16
- d.setExitCode(e.exitCode);
17
- return;
18
- }
19
16
  if (e instanceof UserError) {
20
17
  d.error(`${pc.red("Error")}: ${e.message}`);
21
18
  }
22
- else if (util.types.isNativeError(e) && e.stack) {
19
+ else if (types.isNativeError(e) && e.stack) {
23
20
  d.error(e.stack);
24
21
  }
25
22
  else {
@@ -35,13 +32,11 @@ async function mainWorker(d) {
35
32
  d.log(getUsage());
36
33
  return;
37
34
  }
38
- let herebyfilePath = (_a = args.herebyfile) !== null && _a !== void 0 ? _a : findHerebyfile(d.cwd());
39
- herebyfilePath = path.resolve(d.cwd(), herebyfilePath);
40
- if (await reexec(d, herebyfilePath)) {
35
+ const herebyfilePath = path.resolve(d.cwd(), (_a = args.herebyfile) !== null && _a !== void 0 ? _a : findHerebyfile(d.cwd()));
36
+ if (await reexec(herebyfilePath))
41
37
  return;
42
- }
43
38
  if (args.version) {
44
- d.log(`hereby ${await d.version()}`);
39
+ d.log(`hereby ${d.version()}`);
45
40
  return;
46
41
  }
47
42
  d.chdir(path.dirname(herebyfilePath));
@@ -53,31 +48,30 @@ async function mainWorker(d) {
53
48
  const tasks = await selectTasks(d, herebyfile, herebyfilePath, args.run);
54
49
  const taskNames = tasks.map((task) => pc.blue(task.options.name)).join(", ");
55
50
  d.log(`Using ${pc.yellow(d.simplifyPath(herebyfilePath))} to run ${taskNames}`);
56
- const start = Date.now();
51
+ const start = performance.now();
57
52
  let errored = false;
58
53
  try {
59
54
  const runner = new Runner(d);
60
55
  await runner.runTasks(...tasks);
61
56
  }
62
- catch (e) {
57
+ catch {
63
58
  errored = true;
64
59
  // We will have already printed some message here.
65
60
  // Set the error code and let the process run to completion,
66
61
  // so we don't end up with an unflushed output.
67
- throw new ExitCodeError(1, e);
62
+ d.setExitCode(1);
68
63
  }
69
64
  finally {
70
- const took = Date.now() - start;
65
+ const took = performance.now() - start;
71
66
  d.log(`Completed ${taskNames}${errored ? pc.red(" with errors") : ""} in ${d.prettyMilliseconds(took)}`);
72
67
  }
73
68
  }
74
69
  // Exported for testing.
75
70
  export async function selectTasks(d, herebyfile, herebyfilePath, taskNames) {
76
71
  if (taskNames.length === 0) {
77
- if (!herebyfile.defaultTask) {
78
- throw new UserError(`No default task has been exported from ${d.simplifyPath(herebyfilePath)}; please specify a task name.`);
79
- }
80
- return [herebyfile.defaultTask];
72
+ if (herebyfile.defaultTask)
73
+ return [herebyfile.defaultTask];
74
+ throw new UserError(`No default task has been exported from ${d.simplifyPath(herebyfilePath)}; please specify a task name.`);
81
75
  }
82
76
  const tasks = [];
83
77
  for (const name of taskNames) {
@@ -36,16 +36,16 @@ export async function loadHerebyfile(herebyfilePath) {
36
36
  const exportedTasks = new Set();
37
37
  let defaultTask;
38
38
  for (const [key, value] of Object.entries(herebyfile)) {
39
- if (value instanceof Task) {
40
- if (key === "default") {
41
- defaultTask = value;
42
- }
43
- else if (exportedTasks.has(value)) {
44
- throw new UserError(`Task "${pc.blue(value.options.name)}" has been exported twice.`);
45
- }
46
- else {
47
- exportedTasks.add(value);
48
- }
39
+ if (!(value instanceof Task))
40
+ continue;
41
+ if (key === "default") {
42
+ defaultTask = value;
43
+ }
44
+ else if (exportedTasks.has(value)) {
45
+ throw new UserError(`Task "${pc.blue(value.options.name)}" has been exported twice.`);
46
+ }
47
+ else {
48
+ exportedTasks.add(value);
49
49
  }
50
50
  }
51
51
  if (defaultTask) {
@@ -67,9 +67,8 @@ function checkTaskInvariants(tasks) {
67
67
  checkTaskInvariantsWorker(tasks);
68
68
  function checkTaskInvariantsWorker(tasks) {
69
69
  for (const task of tasks) {
70
- if (checkedTasks.has(task)) {
70
+ if (checkedTasks.has(task))
71
71
  continue;
72
- }
73
72
  if (taskStack.has(task)) {
74
73
  throw new UserError(`Task "${pc.blue(task.options.name)}" references itself.`);
75
74
  }
@@ -14,7 +14,7 @@ export function parseArgs(argv) {
14
14
  });
15
15
  return {
16
16
  help: options["help"],
17
- run: options["_"],
17
+ run: options._,
18
18
  herebyfile: options["herebyfile"],
19
19
  printTasks: options["tasks"] ? "normal" : (options["tasks-simple"] ? "simple" : undefined),
20
20
  version: options["version"],
@@ -1,12 +1,16 @@
1
- import { pathToFileURL } from "node:url";
2
- import { UserError } from "./utils.js";
3
- const cliExportName = "hereby/cli";
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
+ import { findUp, UserError } from "./utils.js";
5
+ const thisCLI = fileURLToPath(new URL("../cli.js", import.meta.url));
6
+ const distCLIPath = path.join("dist", "cli.js");
7
+ const expectedCLIPath = path.join("node_modules", "hereby", distCLIPath);
4
8
  /**
5
9
  * Checks to see if we need to re-exec another version of hereby.
6
10
  * If this function returns true, the caller should return immediately
7
11
  * and do no further work.
8
12
  */
9
- export async function reexec(d, herebyfilePath) {
13
+ export async function reexec(herebyfilePath) {
10
14
  // If hereby is installed globally, but run against a Herebyfile in some
11
15
  // other package, that Herebyfile's import will resolve to a different
12
16
  // installation of the hereby package. There's no guarantee that the two
@@ -15,18 +19,40 @@ export async function reexec(d, herebyfilePath) {
15
19
  // Rather than trying to fix this by messing around with Node's resolution
16
20
  // (which won't work in ESM anyway), instead opt to figure out the location
17
21
  // of hereby as imported by the Herebyfile, and then "reexec" it by importing.
18
- const thisCLI = await d.resolve(cliExportName, import.meta.url);
19
- let otherCLI;
20
- try {
21
- otherCLI = await d.resolve(cliExportName, pathToFileURL(herebyfilePath).toString());
22
- }
23
- catch {
22
+ //
23
+ // This code used to use `import.meta.resolve` to find `hereby/cli`, but
24
+ // manually encoding this behavior is faster and avoids the dependency.
25
+ // If Node ever makes the two-argument form of `import.meta.resolve` unflagged,
26
+ // we could switch to that.
27
+ const otherCLI = findUp(path.dirname(herebyfilePath), (dir) => {
28
+ const p = path.resolve(dir, expectedCLIPath);
29
+ // This is the typical case; we've walked up and found it in node_modules.
30
+ if (fs.existsSync(p))
31
+ return p;
32
+ // Otherwise, we check to see if we're self-resolving. Realistically,
33
+ // this only happens when developing hereby itself.
34
+ //
35
+ // Technically, this should go before the above check since self-resolution
36
+ // comes before node_modules resolution, but this could only happen if hereby
37
+ // happened to depend on itself somehow.
38
+ const packageJsonPath = path.join(dir, "package.json");
39
+ if (fs.existsSync(packageJsonPath)) {
40
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
41
+ if (packageJson.name === "hereby") {
42
+ return path.resolve(dir, distCLIPath);
43
+ }
44
+ }
45
+ return undefined;
46
+ });
47
+ if (!otherCLI) {
24
48
  throw new UserError("Unable to find hereby; ensure hereby is installed in your package.");
25
49
  }
26
- if (thisCLI === otherCLI) {
50
+ if (fs.realpathSync(thisCLI) === fs.realpathSync(otherCLI)) {
27
51
  return false;
28
52
  }
29
- await import(otherCLI);
53
+ // Note: calling pathToFileURL is required on Windows to disambiguate URLs
54
+ // from drive letters.
55
+ await import(pathToFileURL(otherCLI).toString());
30
56
  return true;
31
57
  }
32
58
  //# sourceMappingURL=reexec.js.map
@@ -1,3 +1,4 @@
1
+ import { performance } from "node:perf_hooks";
1
2
  import pc from "picocolors";
2
3
  export class Runner {
3
4
  constructor(_d) {
@@ -12,9 +13,8 @@ export class Runner {
12
13
  // cleanup function in a "finally" or something.
13
14
  const results = await Promise.allSettled(tasks.map((task) => {
14
15
  const cached = this._addedTasks.get(task);
15
- if (cached) {
16
+ if (cached)
16
17
  return cached;
17
- }
18
18
  const promise = this._runTask(task);
19
19
  this._addedTasks.set(task, promise);
20
20
  return promise;
@@ -30,9 +30,8 @@ export class Runner {
30
30
  if (dependencies) {
31
31
  await this.runTasks(...dependencies);
32
32
  }
33
- if (!run) {
33
+ if (!run)
34
34
  return;
35
- }
36
35
  try {
37
36
  this.onTaskStart(task);
38
37
  await run();
@@ -44,25 +43,22 @@ export class Runner {
44
43
  }
45
44
  }
46
45
  onTaskStart(task) {
47
- this._startTimes.set(task, Date.now());
48
- if (this._errored) {
46
+ this._startTimes.set(task, performance.now());
47
+ if (this._errored)
49
48
  return; // Skip logging.
50
- }
51
49
  this._d.log(`Starting ${pc.blue(task.options.name)}`);
52
50
  }
53
51
  onTaskFinish(task) {
54
- if (this._errored) {
52
+ if (this._errored)
55
53
  return; // Skip logging.
56
- }
57
- const took = Date.now() - this._startTimes.get(task);
54
+ const took = performance.now() - this._startTimes.get(task);
58
55
  this._d.log(`Finished ${pc.green(task.options.name)} in ${this._d.prettyMilliseconds(took)}`);
59
56
  }
60
57
  onTaskError(task, e) {
61
- if (this._errored) {
58
+ if (this._errored)
62
59
  return; // Skip logging.
63
- }
64
60
  this._errored = true;
65
- const took = Date.now() - this._startTimes.get(task);
61
+ const took = performance.now() - this._startTimes.get(task);
66
62
  this._d.error(`Error in ${pc.red(task.options.name)} in ${this._d.prettyMilliseconds(took)}\n${e}`);
67
63
  }
68
64
  }
package/dist/cli/utils.js CHANGED
@@ -16,15 +16,15 @@ export function simplifyPath(p) {
16
16
  }
17
17
  return p;
18
18
  }
19
- export function findUp(p, predicate) {
20
- const root = path.parse(p).root;
19
+ export function findUp(dir, predicate) {
20
+ const root = path.parse(dir).root;
21
21
  while (true) {
22
- const result = predicate(p);
22
+ const result = predicate(dir);
23
23
  if (result !== undefined)
24
24
  return result;
25
- if (p === root)
25
+ if (dir === root)
26
26
  break;
27
- p = path.dirname(p);
27
+ dir = path.dirname(dir);
28
28
  }
29
29
  return undefined;
30
30
  }
@@ -34,16 +34,6 @@ export function findUp(p, predicate) {
34
34
  */
35
35
  export class UserError extends Error {
36
36
  }
37
- /**
38
- * When thrown, ExitCodeError causes the process to exit with a specific error code,
39
- * without logging anything.
40
- */
41
- export class ExitCodeError {
42
- constructor(exitCode, reason) {
43
- this.exitCode = exitCode;
44
- this.reason = reason;
45
- }
46
- }
47
37
  export async function real() {
48
38
  const { default: prettyMilliseconds } = await import("pretty-ms");
49
39
  /* eslint-disable no-restricted-globals */
@@ -59,15 +49,11 @@ export async function real() {
59
49
  setExitCode: (code) => {
60
50
  process.exitCode = code;
61
51
  },
62
- version: async () => {
52
+ version: () => {
63
53
  const packageJsonURL = new URL("../../package.json", import.meta.url);
64
- const packageJson = await fs.promises.readFile(packageJsonURL, "utf8");
54
+ const packageJson = fs.readFileSync(packageJsonURL, "utf8");
65
55
  return JSON.parse(packageJson).version;
66
56
  },
67
- resolve: async (specifier, parent) => {
68
- const { resolve } = await import("import-meta-resolve");
69
- return resolve(specifier, parent);
70
- },
71
57
  prettyMilliseconds,
72
58
  };
73
59
  /* eslint-enable no-restricted-globals */
package/dist/index.js CHANGED
@@ -11,7 +11,8 @@ export class Task {
11
11
  constructor(options) {
12
12
  // Runtime typecheck; consumers of hereby may not have enabled
13
13
  // typechecking, so this is helpful.
14
- var _a;
14
+ var _a, _b;
15
+ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
15
16
  if (typeof options.name !== "string") {
16
17
  throw new TypeError("Task name is not a string.");
17
18
  }
@@ -21,16 +22,15 @@ export class Task {
21
22
  if (!Array.isArray(options.dependencies) && options.dependencies !== undefined) {
22
23
  throw new TypeError("Task dependencies is not an array or undefined.");
23
24
  }
24
- if (options.dependencies) {
25
- for (const dep of options.dependencies) {
26
- if (!(dep instanceof Task)) {
27
- throw new TypeError("Task dependency is not a task.");
28
- }
25
+ 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.");
29
28
  }
30
29
  }
31
30
  if (typeof options.run !== "function" && options.run !== undefined) {
32
31
  throw new TypeError("Task run is not a function or undefined.");
33
32
  }
33
+ /* eslint-enable @typescript-eslint/no-unnecessary-condition */
34
34
  // Non-type checks.
35
35
  if (!options.name) {
36
36
  throw new Error("Task name must not be empty.");
@@ -38,7 +38,7 @@ export class Task {
38
38
  if (options.name.startsWith("-")) {
39
39
  throw new Error('Task name must not start with "-".');
40
40
  }
41
- if (!((_a = options.dependencies) === null || _a === void 0 ? void 0 : _a.length) && !options.run) {
41
+ if (!((_b = options.dependencies) === null || _b === void 0 ? void 0 : _b.length) && !options.run) {
42
42
  throw new Error("Task must have a run function or dependencies.");
43
43
  }
44
44
  this.options = options;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hereby",
3
- "version": "1.8.9",
3
+ "version": "1.9.0",
4
4
  "description": "A simple task runner",
5
5
  "repository": "github:jakebailey/hereby",
6
6
  "type": "module",
@@ -41,33 +41,33 @@
41
41
  "dependencies": {
42
42
  "command-line-usage": "^6.1.3",
43
43
  "fastest-levenshtein": "^1.0.16",
44
- "import-meta-resolve": "^2.2.2",
45
44
  "minimist": "^1.2.8",
46
- "picocolors": "^1.0.0",
45
+ "picocolors": "^1.0.1",
47
46
  "pretty-ms": "^8.0.0"
48
47
  },
49
48
  "devDependencies": {
50
49
  "@ava/typescript": "^3.0.1",
51
- "@changesets/cli": "^2.27.1",
52
- "@tsconfig/node12": "^12.1.0",
50
+ "@changesets/cli": "^2.27.7",
51
+ "@tsconfig/node12": "^12.1.3",
53
52
  "@types/command-line-usage": "^5.0.4",
54
53
  "@types/minimist": "^1.2.5",
55
- "@types/node": "^20.10.6",
54
+ "@types/node": "^20.14.10",
56
55
  "@types/tmp": "^0.2.6",
57
- "@typescript-eslint/eslint-plugin": "^6.17.0",
58
- "@typescript-eslint/parser": "^6.17.0",
56
+ "@typescript-eslint/eslint-plugin": "^7.16.1",
57
+ "@typescript-eslint/parser": "^7.16.1",
59
58
  "ava": "~5.0.1",
60
- "c8": "^8.0.1",
61
- "dprint": "^0.45.0",
62
- "eslint": "^8.56.0",
59
+ "c8": "^10.1.2",
60
+ "dprint": "^0.47.2",
61
+ "eslint": "^8.57.0",
63
62
  "eslint-plugin-ava": "^14.0.0",
64
- "eslint-plugin-simple-import-sort": "^10.0.0",
65
- "eslint-plugin-unicorn": "^50.0.1",
63
+ "eslint-plugin-simple-import-sort": "^12.1.1",
64
+ "eslint-plugin-unicorn": "^54.0.0",
66
65
  "execa": "^6.1.0",
66
+ "monocart-coverage-reports": "^2.9.2",
67
67
  "moq.ts": "^10.1.0",
68
- "rimraf": "^5.0.5",
69
- "tmp": "^0.2.1",
70
- "typescript": "^5.3.3"
68
+ "rimraf": "^5.0.9",
69
+ "tmp": "0.2.1",
70
+ "typescript": "^5.5.3"
71
71
  },
72
72
  "overrides": {
73
73
  "ava": {
@@ -80,7 +80,7 @@
80
80
  "build": "tsc",
81
81
  "watch": "tsc --watch",
82
82
  "test": "ava",
83
- "coverage": "c8 ava",
83
+ "coverage": "c8 --experimental-monocart ava",
84
84
  "prepack": "rimraf dist && npm run build"
85
85
  },
86
86
  "ava": {
@@ -1,5 +0,0 @@
1
- import type { Task } from "../index.js";
2
- import { type Herebyfile } from "./loadHerebyfile.js";
3
- import { type D } from "./utils.js";
4
- export declare function main(d: D): Promise<void>;
5
- export declare function selectTasks(d: Pick<D, "simplifyPath">, herebyfile: Herebyfile, herebyfilePath: string, taskNames: string[]): Promise<Task[]>;