@haltcase/run 3.0.1 → 5.0.0-beta.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.
Files changed (51) hide show
  1. package/dist/cli/bin.d.mts +1 -0
  2. package/dist/cli/bin.mjs +7 -0
  3. package/dist/cli/handler.mjs +110 -0
  4. package/dist/cli/main.mjs +56 -0
  5. package/dist/cli/parseOptions.d.mts +7 -0
  6. package/dist/cli/parseOptions.mjs +36 -0
  7. package/dist/cli/spinner.mjs +128 -0
  8. package/dist/config.d.mts +18 -0
  9. package/dist/config.mjs +19 -0
  10. package/dist/index.d.mts +5 -0
  11. package/dist/index.mjs +2 -0
  12. package/dist/tasks/executeTask.mjs +83 -0
  13. package/dist/tasks/guards.mjs +4 -0
  14. package/dist/tasks/task.d.mts +10 -0
  15. package/dist/tasks/task.mjs +40 -0
  16. package/dist/tasks/types.d.mts +60 -0
  17. package/dist/util/getSchemaProperties.mjs +34 -0
  18. package/dist/util/loadTaskFile.mjs +31 -0
  19. package/dist/util/resolveTaskFile.mjs +13 -0
  20. package/package.json +51 -42
  21. package/dist/cli/bin.d.ts +0 -2
  22. package/dist/cli/bin.js +0 -7
  23. package/dist/cli/handler.d.ts +0 -28
  24. package/dist/cli/handler.js +0 -105
  25. package/dist/cli/main.d.ts +0 -21
  26. package/dist/cli/main.js +0 -64
  27. package/dist/cli/parseOptions.d.ts +0 -13
  28. package/dist/cli/parseOptions.js +0 -48
  29. package/dist/config.d.ts +0 -16
  30. package/dist/config.js +0 -16
  31. package/dist/index.d.ts +0 -4
  32. package/dist/index.js +0 -1
  33. package/dist/tasks/executeTask.d.ts +0 -12
  34. package/dist/tasks/executeTask.js +0 -45
  35. package/dist/tasks/guards.d.ts +0 -3
  36. package/dist/tasks/guards.js +0 -2
  37. package/dist/tasks/task.d.ts +0 -14
  38. package/dist/tasks/task.js +0 -55
  39. package/dist/tasks/types.d.ts +0 -58
  40. package/dist/tasks/types.js +0 -1
  41. package/dist/tsconfig.build.tsbuildinfo +0 -1
  42. package/dist/util/getSchemaProperties.d.ts +0 -2
  43. package/dist/util/getSchemaProperties.js +0 -40
  44. package/dist/util/loadTaskFile.d.ts +0 -8
  45. package/dist/util/loadTaskFile.js +0 -30
  46. package/dist/util/resolveTaskFile.d.ts +0 -3
  47. package/dist/util/resolveTaskFile.js +0 -17
  48. package/dist/util/result.d.ts +0 -9
  49. package/dist/util/result.js +0 -1
  50. /package/{license → LICENSE} +0 -0
  51. /package/{readme.md → README.md} +0 -0
@@ -0,0 +1,13 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { SUPPORTED_EXTENSIONS } from "c12";
4
+ //#region src/util/resolveTaskFile.ts
5
+ const extensions = SUPPORTED_EXTENSIONS.filter((extension) => extension.endsWith("js") || extension.endsWith("ts"));
6
+ const resolveTaskFile = (context) => {
7
+ if (context.taskFile.ext) return context.taskFile.path;
8
+ const foundConfigs = extensions.map((it) => join(context.taskFile.dir, `${context.taskFile.name}${it}`)).filter((path) => existsSync(path));
9
+ if (foundConfigs.length === 1) return foundConfigs[0];
10
+ return foundConfigs;
11
+ };
12
+ //#endregion
13
+ export { extensions, resolveTaskFile };
package/package.json CHANGED
@@ -1,77 +1,86 @@
1
1
  {
2
2
  "name": "@haltcase/run",
3
- "version": "3.0.1",
3
+ "version": "5.0.0-beta.1",
4
4
  "description": "Flexible, function-based task runner where command line options are props",
5
5
  "keywords": [
6
- "task",
7
- "runner",
6
+ "arktype",
8
7
  "command",
9
- "function",
10
- "typescript",
11
8
  "execa",
12
- "arktype"
9
+ "function",
10
+ "runner",
11
+ "task",
12
+ "typescript"
13
13
  ],
14
14
  "homepage": "https://github.com/haltcase/run#readme",
15
15
  "bugs": "https://github.com/haltcase/run/issues",
16
+ "license": "MIT",
17
+ "author": "Bo Lingen <bo@haltcase.dev> (https://haltcase.dev)",
16
18
  "repository": {
17
19
  "type": "git",
18
20
  "url": "https://github.com/haltcase/run.git"
19
21
  },
20
- "license": "MIT",
21
- "author": "Bo Lingen <bo@haltcase.dev> (https://haltcase.dev)",
22
- "type": "module",
23
- "exports": {
24
- ".": "./dist/index.js"
25
- },
26
- "main": "./dist/index.js",
27
- "types": "./dist/index.d.ts",
28
22
  "bin": {
29
- "hr": "./dist/cli/bin.js"
23
+ "hr": "./dist/cli/bin.mjs"
30
24
  },
31
25
  "files": [
32
26
  "dist"
33
27
  ],
34
- "lint-staged": {
35
- "*.{js,jsx,ts,tsx,css,yml,yaml}": "pnpm prettier --write"
28
+ "type": "module",
29
+ "main": "./dist/index.mjs",
30
+ "types": "./dist/index.d.mts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.mts",
34
+ "default": "./dist/index.mjs"
35
+ }
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
36
39
  },
37
- "prettier": "@haltcase/style/prettier",
38
40
  "dependencies": {
39
- "@favware/colorette-spinner": "^1.0.1",
40
- "arktype": "^2.1.17",
41
- "c12": "^3.0.3",
41
+ "arktype": "^2.2.0",
42
+ "c12": "^4.0.0-beta.5",
42
43
  "cliui": "^9.0.1",
43
- "colorette": "^2.0.20",
44
- "execa": "^9.5.1"
44
+ "execa": "^9.6.1"
45
45
  },
46
46
  "devDependencies": {
47
+ "@arethetypeswrong/core": "^0.18.3",
47
48
  "@haltcase/style": "^7.3.1",
48
- "@types/node": "^22.8.6",
49
- "eslint": "^9.24.0",
50
- "husky": "^9.1.7",
51
- "lint-staged": "^15.5.1",
52
- "prettier": "^3.5.3",
53
- "tsx": "^4.19.3",
54
- "typescript": "^5.8.3",
55
- "vitest": "^3.1.1"
49
+ "@types/node": "^24.13.1",
50
+ "@typescript/native-preview": "7.0.0-dev.20260608.1",
51
+ "eslint": "^9.39.4",
52
+ "jiti": "^2.7.0",
53
+ "oxlint-tsgolint": "^0.23.0",
54
+ "tsx": "^4.22.4",
55
+ "typescript": "^6.0.3",
56
+ "vite": "npm:@voidzero-dev/vite-plus-core@0.1.24",
57
+ "vite-plus": "0.1.24",
58
+ "vitest": "npm:@voidzero-dev/vite-plus-test@0.1.24"
56
59
  },
57
- "engines": {
58
- "node": ">=20"
60
+ "peerDependencies": {
61
+ "jiti": "^2.7.0"
59
62
  },
60
- "publishConfig": {
61
- "access": "public"
63
+ "peerDependenciesMeta": {
64
+ "jiti": {
65
+ "optional": true
66
+ }
67
+ },
68
+ "engines": {
69
+ "node": ">=22"
62
70
  },
63
71
  "haltcase.run": {
64
72
  "taskDirectory": "./scripts",
65
- "quiet": true
73
+ "quiet": false
66
74
  },
67
75
  "scripts": {
68
- "build": "tsc -p tsconfig.build.json",
69
- "build:watch": "tsc -p tsconfig.build.json --watch",
76
+ "build": "vp pack",
77
+ "build:watch": "vp pack --watch",
78
+ "check": "pnpm eslint:check && vp check --no-lint",
70
79
  "eslint:check": "eslint src",
71
- "format": "eslint --fix src && prettier src --write",
80
+ "format": "eslint --fix src && vp check --fix --no-lint",
72
81
  "hr": "tsx ./src/cli/bin.ts",
73
- "prettier:check": "prettier --check src",
74
- "test": "vitest",
75
- "typescript:check": "tsc --noEmit && echo \"No errors reported by tsc.\""
82
+ "test": "vp test watch",
83
+ "test:run": "vp test",
84
+ "types": "vp check --no-fmt --no-lint"
76
85
  }
77
86
  }
package/dist/cli/bin.d.ts DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
package/dist/cli/bin.js DELETED
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env node
2
- import { createHandler } from "./handler.js";
3
- import { main } from "./main.js";
4
- const handler = createHandler();
5
- void main({
6
- handler
7
- });
@@ -1,28 +0,0 @@
1
- import { Spinner } from "@favware/colorette-spinner";
2
- import type { AppConfig } from "../config.js";
3
- import type { MainContextWithData } from "./main.js";
4
- export declare const failWith: (spinner: Spinner, message: unknown) => never;
5
- export declare const write: (message: string) => void;
6
- interface HelpContextCommand {
7
- command: string;
8
- }
9
- interface HelpContextFile {
10
- taskFileList: true;
11
- config: AppConfig;
12
- }
13
- interface HelpContextScript extends MainContextWithData {
14
- taskList: true;
15
- }
16
- type HelpContext = HelpContextCommand | HelpContextFile | HelpContextScript;
17
- export declare const commandHandler: (_context: HelpContextCommand) => string;
18
- export declare const taskFileListHandler: (context: HelpContextFile) => string;
19
- export declare const taskListHandler: (context: HelpContextScript) => string;
20
- export declare const help: (context: HelpContext) => string;
21
- export interface Handler {
22
- spinner: Spinner;
23
- help: (context: HelpContext) => string;
24
- write: (message: string) => void;
25
- failWith: (message: unknown) => never;
26
- }
27
- export declare const createHandler: () => Handler;
28
- export {};
@@ -1,105 +0,0 @@
1
- import { readdirSync } from "node:fs";
2
- import { extname } from "node:path";
3
- import { Spinner } from "@favware/colorette-spinner";
4
- import cliui from "cliui";
5
- import { bold, gray, yellow } from "colorette";
6
- import { isBrandedTask } from "../tasks/guards.js";
7
- import { getSchemaProperties } from "../util/getSchemaProperties.js";
8
- import { extensions } from "../util/resolveTaskFile.js";
9
- export const failWith = (spinner, message) => {
10
- let text;
11
- if (Array.isArray(message)) {
12
- text = message.map(String).join("\n");
13
- }
14
- if (text == null || typeof text !== "string") {
15
- text = String(message);
16
- }
17
- spinner.error({
18
- text
19
- });
20
- process.exit(1);
21
- };
22
- export const write = (message) => {
23
- process.stdout.write(`${message}\n`);
24
- };
25
- const usage = `Usage: ${yellow("hr")} <taskFile> [task]`;
26
- export const commandHandler = (_context) => {
27
- return usage;
28
- };
29
- export const taskFileListHandler = (context) => {
30
- const fileList = readdirSync(context.config.taskDirectory).filter((file) => extensions.includes(extname(file)));
31
- const content = [
32
- usage,
33
- "Available task files:",
34
- fileList.map((it) => ` ${it}`).join("\n")
35
- ].join("\n\n");
36
- return `${content}\n`;
37
- };
38
- export const taskListHandler = (context) => {
39
- const ui = cliui({
40
- width: Math.max(0, process.stdout.columns - 4),
41
- wrap: true
42
- });
43
- const { config } = context.taskFile.data;
44
- const baseUsage = usage.replace("<taskFile>", context.taskFile.name);
45
- ui.div(`${baseUsage} <parameter> [...options]`);
46
- ui.div({
47
- text: "Available tasks:",
48
- padding: [1, 0, 1, 0]
49
- });
50
- const taskListPaddingX = 2;
51
- const nameColumnWidth = Object.keys(config).reduce((previous, current) => Math.max(previous, current.length), 1) +
52
- 2 +
53
- taskListPaddingX * 2;
54
- for (const [name, value] of Object.entries(config)) {
55
- if (!value) {
56
- continue;
57
- }
58
- const formattedName = bold(name);
59
- if (isBrandedTask(value)) {
60
- if (value.kind === "strictTask") {
61
- const properties = getSchemaProperties(value.schema) || gray("not available");
62
- ui.div({
63
- text: formattedName,
64
- width: nameColumnWidth,
65
- padding: [0, taskListPaddingX, 0, taskListPaddingX]
66
- }, properties);
67
- continue;
68
- }
69
- ui.div({
70
- text: formattedName,
71
- width: nameColumnWidth,
72
- padding: [0, taskListPaddingX, 0, taskListPaddingX]
73
- });
74
- continue;
75
- }
76
- ui.div({
77
- text: formattedName,
78
- width: nameColumnWidth,
79
- padding: [0, taskListPaddingX, 0, taskListPaddingX]
80
- });
81
- }
82
- ui.div("");
83
- return ui.toString();
84
- };
85
- export const help = (context) => {
86
- if ("command" in context) {
87
- return commandHandler(context);
88
- }
89
- if ("taskFileList" in context) {
90
- return taskFileListHandler(context);
91
- }
92
- if ("taskList" in context) {
93
- return taskListHandler(context);
94
- }
95
- return "";
96
- };
97
- export const createHandler = () => {
98
- const spinner = new Spinner();
99
- return {
100
- spinner,
101
- help,
102
- write,
103
- failWith: (message) => failWith(spinner, message)
104
- };
105
- };
@@ -1,21 +0,0 @@
1
- import type { ParsedPath } from "node:path";
2
- import type { AppConfig } from "../config.js";
3
- import type { TaskCollection } from "../tasks/types.js";
4
- import type { ResolvedConfigWithFile } from "../util/loadTaskFile.js";
5
- import type { Handler } from "./handler.js";
6
- export interface MainProps {
7
- handler: Handler;
8
- }
9
- export interface MainContext extends MainProps {
10
- config: AppConfig;
11
- taskFile: ParsedPath & {
12
- path: string;
13
- };
14
- taskName: string;
15
- }
16
- export interface MainContextWithData extends MainContext {
17
- taskFile: MainContext["taskFile"] & {
18
- data: ResolvedConfigWithFile<TaskCollection>;
19
- };
20
- }
21
- export declare const main: (props: MainProps) => Promise<void>;
package/dist/cli/main.js DELETED
@@ -1,64 +0,0 @@
1
- import { parse, resolve } from "node:path";
2
- import { getAppConfig } from "../config.js";
3
- import { executeTask } from "../tasks/executeTask.js";
4
- import { loadTaskFile } from "../util/loadTaskFile.js";
5
- import { resolveTaskFile } from "../util/resolveTaskFile.js";
6
- import { parseOptions } from "./parseOptions.js";
7
- export const main = async (props) => {
8
- const { config } = await getAppConfig();
9
- const inputFileName = process.argv[2];
10
- const taskName = process.argv[3] ?? "";
11
- if (!inputFileName) {
12
- props.handler.write(props.handler.help({
13
- taskFileList: true,
14
- config
15
- }));
16
- props.handler.failWith("Task file name is required");
17
- }
18
- const fullFilePath = resolve(config.taskDirectory, inputFileName);
19
- const context = {
20
- ...props,
21
- config,
22
- taskFile: {
23
- ...parse(fullFilePath),
24
- path: fullFilePath
25
- },
26
- taskName
27
- };
28
- const resolutions = resolveTaskFile(context);
29
- if (Array.isArray(resolutions)) {
30
- props.handler.failWith([
31
- `Found multiple task files with the name '${context.taskFile.name}'`,
32
- `Rename the ambiguous files or specify an extension and try again`,
33
- resolutions.map((it) => ` ${it}`).join("\n")
34
- ]);
35
- }
36
- // update context with the fully resolved file path
37
- context.taskFile = {
38
- ...parse(resolutions),
39
- path: resolutions
40
- };
41
- const taskFileResult = await loadTaskFile(context);
42
- if (!taskFileResult.ok) {
43
- props.handler.failWith(taskFileResult.error);
44
- }
45
- const contextWithData = {
46
- ...context,
47
- taskFile: {
48
- ...context.taskFile,
49
- data: taskFileResult.value
50
- }
51
- };
52
- try {
53
- const options = parseOptions(process.argv.slice(4));
54
- await executeTask(contextWithData, options);
55
- if (!config.quiet) {
56
- props.handler.spinner.success({
57
- text: "Success"
58
- });
59
- }
60
- }
61
- catch (error) {
62
- props.handler.failWith([`Failed to execute task`, String(error)]);
63
- }
64
- };
@@ -1,13 +0,0 @@
1
- export interface ParsedOptions {
2
- _: string[];
3
- [key: string]: unknown;
4
- }
5
- export declare const reservedNames: Set<string>;
6
- /**
7
- * Wrapper around Node's {@link parseArgs} that treats options as strings
8
- * by default, instead of boolean flags.
9
- *
10
- * @param args - argv-like string
11
- * @returns
12
- */
13
- export declare const parseOptions: (args: string[]) => ParsedOptions;
@@ -1,48 +0,0 @@
1
- import { parseArgs } from "node:util";
2
- export const reservedNames = new Set(["_", "env"]);
3
- /**
4
- * Wrapper around Node's {@link parseArgs} that treats options as strings
5
- * by default, instead of boolean flags.
6
- *
7
- * @param args - argv-like string
8
- * @returns
9
- */
10
- export const parseOptions = (args) => {
11
- const { tokens } = parseArgs({
12
- args,
13
- allowPositionals: true,
14
- strict: false,
15
- tokens: true
16
- });
17
- const options = {
18
- _: []
19
- };
20
- for (let index = 0; index < tokens.length; index++) {
21
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
22
- const token = tokens[index];
23
- if (token.kind === "option-terminator") {
24
- continue;
25
- }
26
- if (token.kind === "option") {
27
- if (reservedNames.has(token.name)) {
28
- throw new Error(`Reserved name '${token.name}' cannot be used as option`);
29
- }
30
- if (index + 1 < tokens.length) {
31
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
32
- const next = tokens[index + 1];
33
- if (next.kind !== "positional") {
34
- throw new Error(`Expected value for option ${token.rawName}`);
35
- }
36
- options[token.name] = next.value;
37
- index++;
38
- }
39
- else {
40
- throw new Error(`Expected option ${token.rawName} to be followed by a value`);
41
- }
42
- }
43
- if (token.kind === "positional") {
44
- options._.push(token.value);
45
- }
46
- }
47
- return options;
48
- };
package/dist/config.d.ts DELETED
@@ -1,16 +0,0 @@
1
- export interface AppConfig {
2
- /**
3
- * Directory containing task files. Defaults to the `scripts` folder within
4
- * the current working directory.
5
- *
6
- * @default `<cwd>/scripts`
7
- */
8
- taskDirectory: string;
9
- /**
10
- * Print no output unless errors occur.
11
- *
12
- * @default false
13
- */
14
- quiet: boolean;
15
- }
16
- export declare const getAppConfig: () => Promise<import("c12").ResolvedConfig<AppConfig, import("c12").ConfigLayerMeta>>;
package/dist/config.js DELETED
@@ -1,16 +0,0 @@
1
- import { join } from "node:path";
2
- import { cwd } from "node:process";
3
- import { loadConfig } from "c12";
4
- export const getAppConfig = async () => {
5
- const configName = "haltcase.run";
6
- return loadConfig({
7
- name: configName,
8
- configFile: configName,
9
- packageJson: configName,
10
- rcFile: configName,
11
- defaults: {
12
- taskDirectory: join(cwd(), "scripts"),
13
- quiet: false
14
- }
15
- });
16
- };
package/dist/index.d.ts DELETED
@@ -1,4 +0,0 @@
1
- export type { ParsedOptions } from "./cli/parseOptions.js";
2
- export type { AppConfig as HaltcaseRunConfig } from "./config.js";
3
- export { task } from "./tasks/task.js";
4
- export type { Task } from "./tasks/types.js";
package/dist/index.js DELETED
@@ -1 +0,0 @@
1
- export { task } from "./tasks/task.js";
@@ -1,12 +0,0 @@
1
- import type { MainContextWithData } from "../cli/main.js";
2
- import type { ParsedOptions } from "../cli/parseOptions.js";
3
- export declare const taskUtilities: {
4
- command: import("execa").ExecaMethod<{}>;
5
- $: import("execa").ExecaScriptMethod<{}>;
6
- exec: import("execa").ExecaMethod<{
7
- stdin: "inherit";
8
- stdout: "inherit";
9
- stderr: "inherit";
10
- }>;
11
- };
12
- export declare const executeTask: (context: MainContextWithData, options: ParsedOptions) => Promise<void>;
@@ -1,45 +0,0 @@
1
- import { $, execa } from "execa";
2
- const execaInherited = execa({
3
- stdin: "inherit",
4
- stdout: "inherit",
5
- stderr: "inherit"
6
- });
7
- export const taskUtilities = {
8
- command: execa,
9
- $,
10
- exec: execaInherited
11
- };
12
- export const executeTask = async (context, options) => {
13
- const taskFunction = context.taskFile.data.config[context.taskName];
14
- const specifier = `${context.taskFile.name}::${context.taskName}`;
15
- if (taskFunction == null) {
16
- context.handler.write(context.handler.help({
17
- taskList: true,
18
- ...context
19
- }));
20
- if (!context.taskName) {
21
- context.handler.failWith(`Task name is required.`);
22
- }
23
- context.handler.failWith([
24
- `Task file '${context.taskFile.name}' does not export '${context.taskName}'`,
25
- `Resolved to: ${context.taskFile.path}`
26
- ]);
27
- }
28
- try {
29
- const optionsWithEnvironment = {
30
- ...options,
31
- env: process.env
32
- };
33
- await taskFunction(optionsWithEnvironment, taskUtilities);
34
- }
35
- catch (error) {
36
- const message = error instanceof Error ? error.message : String(error);
37
- if (message.includes("is not a function")) {
38
- context.handler.failWith([
39
- `Failed to execute ${specifier}`,
40
- `Exported value '${context.taskName}' is not a function`
41
- ]);
42
- }
43
- context.handler.failWith([`Failed to execute ${specifier}`, message]);
44
- }
45
- };
@@ -1,3 +0,0 @@
1
- import type { BrandedTask, BrandedTaskStrict, Task } from "./types.js";
2
- export declare const isBrandedTask: (task: Task) => task is BrandedTask;
3
- export declare const isBrandedTaskStrict: <T>(task: Task<T>) => task is BrandedTaskStrict<T>;
@@ -1,2 +0,0 @@
1
- export const isBrandedTask = (task) => "kind" in task;
2
- export const isBrandedTaskStrict = (task) => "kind" in task && task.kind === "strictTask";
@@ -1,14 +0,0 @@
1
- import type { Type } from "arktype";
2
- import { type } from "arktype";
3
- import type { BrandedTask, BrandedTaskStrict, DefaultOptionsInput, Task } from "./types.js";
4
- export declare const task: {
5
- <T = DefaultOptionsInput>(fn: Task<T>): BrandedTask<T>;
6
- strict<const TShape>(shape: type.validate<TShape>, fn: Task<NoInfer<Type<type.infer<TShape>>["infer"]>>): BrandedTaskStrict<Type<type.infer<TShape>>["inferIn"]>;
7
- };
8
- /**
9
- * Default schema for the options received by a {@link task}.
10
- */
11
- export declare const defaultOptionsInput: import("arktype/internal/methods/object.ts").ObjectType<{
12
- _: string[];
13
- env: Record<string, string | undefined>;
14
- }, {}>;
@@ -1,55 +0,0 @@
1
- import { type } from "arktype";
2
- import { red } from "colorette";
3
- const formatValidationIssues = (errors) => errors
4
- .map((error) => {
5
- const propertyName = String(error.path[0]);
6
- const dottedPath = error.path.join(".");
7
- if (!propertyName) {
8
- return error.message;
9
- }
10
- const messageWithoutProperty = error.message.startsWith(dottedPath)
11
- ? error.message.slice(dottedPath.length + 1)
12
- : error.message;
13
- if (propertyName === "env") {
14
- const prefix = red(`Environment variable '${String(error.path[1])}'`);
15
- return `${prefix}: ${messageWithoutProperty}`;
16
- }
17
- const optionText = propertyName === "_" ? "Positionals" : `--${propertyName}`;
18
- if (error.code === "predicate" && error.expected === "removed") {
19
- return `${red(optionText)}: unknown option`;
20
- }
21
- // we currently assume there is only ever one path segment
22
- return `${red(optionText)}: ${messageWithoutProperty}`;
23
- })
24
- .join("\n");
25
- export const task = (fn) => {
26
- fn.kind = "task";
27
- return fn;
28
- };
29
- /**
30
- * Default schema for the options received by a {@link task}.
31
- */
32
- export const defaultOptionsInput = type({
33
- _: "string[]",
34
- env: "Record<string, string | undefined>"
35
- });
36
- /**
37
- * Create a task that validates its input against a schema.
38
- */
39
- task.strict = (shape, fn) => {
40
- const schema = defaultOptionsInput
41
- .merge(type.raw(shape))
42
- .onUndeclaredKey("reject");
43
- const taskFunction = (options, utilities) => {
44
- const validationResult = schema(options);
45
- if (validationResult instanceof type.errors) {
46
- throw new TypeError(formatValidationIssues(validationResult), {
47
- cause: validationResult
48
- });
49
- }
50
- return fn(validationResult, utilities);
51
- };
52
- taskFunction.kind = "strictTask";
53
- taskFunction.schema = schema;
54
- return taskFunction;
55
- };