|
1 | 1 | /* eslint-disable no-await-in-loop */ |
| 2 | +import * as fs from "node:fs"; |
2 | 3 | import { command } from "cleye"; |
3 | 4 | import chalk from "chalk"; |
| 5 | +import { globSync } from "glob"; |
| 6 | +import * as inquirer from "@inquirer/prompts"; |
4 | 7 | import { Project } from "../Project"; |
5 | 8 | import { AutoReturnType } from "../types"; |
6 | 9 | import { tildify } from "../utils/path"; |
7 | 10 | import { dirname, resolve } from "node:path"; |
8 | | -import { globSync } from "glob"; |
9 | | -import * as inquirer from "@inquirer/prompts"; |
| 11 | +import * as log from "../utils/logger"; |
| 12 | + |
| 13 | +const toKebabCase = (str: string) => { |
| 14 | + return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); |
| 15 | +}; |
10 | 16 |
|
11 | 17 | const createRunCommand = (project: Project, scripts: AutoReturnType[]) => |
12 | | - command({ name: "run", alias: "r", parameters: ["<script id>"] }, async (argv) => { |
13 | | - const { scriptId } = argv._; |
14 | | - const script = scripts.find((t) => t.id === scriptId); |
| 18 | + command( |
| 19 | + { |
| 20 | + name: "run", |
| 21 | + alias: "r", |
| 22 | + parameters: ["<script id>"], |
| 23 | + flags: { |
| 24 | + help: { type: Boolean, alias: "h" }, |
| 25 | + }, |
| 26 | + allowUnknownFlags: true, |
| 27 | + }, |
| 28 | + async (argv) => { |
| 29 | + const { scriptId } = argv._; |
| 30 | + const script = scripts.find((t) => t.id === scriptId); |
15 | 31 |
|
16 | | - if (!script) { |
17 | | - console.error(chalk.red(`Error: script "%s" not found.`), scriptId); |
18 | | - process.exit(1); |
19 | | - } |
20 | | - if (script.isValid && !script.isValid(project)) { |
21 | | - console.error(chalk.red(`Error: script "%s" is not valid for this context.`), scriptId); |
22 | | - process.exit(1); |
23 | | - } |
| 32 | + if (!script) { |
| 33 | + console.error(chalk.red(`Error: script "%s" not found.`), scriptId); |
| 34 | + process.exit(1); |
| 35 | + } |
| 36 | + if (script.isValid && !script.isValid(project)) { |
| 37 | + console.error(chalk.red(`Error: script "%s" is not valid for this context.`), scriptId); |
| 38 | + process.exit(1); |
| 39 | + } |
24 | 40 |
|
25 | | - console.log(chalk.blue("Info:"), "Running", chalk.magenta(tildify(script.path))); |
| 41 | + console.log(chalk.blue("Info:"), "Running", chalk.magenta(tildify(script.path))); |
26 | 42 |
|
27 | | - // gather params |
28 | | - const scriptParams = script.bootstrapParams(); |
29 | | - for (const [_, param] of Object.entries(scriptParams)) { |
30 | | - // dynamic default values |
31 | | - if (typeof param.defaultValue === "function") { |
32 | | - const value = param.defaultValue({ |
33 | | - project, |
34 | | - params: Object.fromEntries( |
35 | | - Object.entries(scriptParams).map(([key, currentParam]) => { |
36 | | - return [key, currentParam.value]; |
37 | | - }) |
38 | | - ), |
39 | | - }); |
40 | | - if (value !== undefined) param.value = value; |
41 | | - } |
| 43 | + // gather params |
| 44 | + const scriptParams = script.bootstrapParams(); |
42 | 45 |
|
43 | | - // eslint-disable-next-line default-case |
44 | | - switch (param.type) { |
45 | | - case "boolean": { |
46 | | - param.value = await inquirer.confirm({ |
47 | | - message: param.title, |
48 | | - default: param.value as boolean, |
49 | | - }); |
50 | | - break; |
| 46 | + // get CLI-passed params |
| 47 | + const cliParams: Record<string, any> = {}; |
| 48 | + |
| 49 | + for (const [key, param] of Object.entries(scriptParams)) { |
| 50 | + // cli values |
| 51 | + const cliValue = (argv.unknownFlags[key] ?? argv.unknownFlags[toKebabCase(key)] ?? [])[0]; |
| 52 | + if (cliValue !== undefined) { |
| 53 | + switch (param.type) { |
| 54 | + case "boolean": |
| 55 | + param.value = cliValue === "true" || cliValue === true; |
| 56 | + break; |
| 57 | + case "number": |
| 58 | + param.value = Number(cliValue); |
| 59 | + break; |
| 60 | + case "string": |
| 61 | + param.value = cliValue; |
| 62 | + break; |
| 63 | + } |
| 64 | + cliParams[key] = cliValue; |
| 65 | + log.debug("CLI value", key, cliValue); |
| 66 | + continue; |
51 | 67 | } |
52 | | - case "number": { |
53 | | - const stringValue = await inquirer.input({ |
54 | | - message: param.title, |
55 | | - default: param.value.toString(), |
| 68 | + |
| 69 | + // dynamic default values |
| 70 | + if (typeof param.defaultValue === "function") { |
| 71 | + const value = param.defaultValue({ |
| 72 | + project, |
| 73 | + params: Object.fromEntries( |
| 74 | + Object.entries(scriptParams).map(([key, currentParam]) => { |
| 75 | + return [key, currentParam.value]; |
| 76 | + }) |
| 77 | + ), |
56 | 78 | }); |
57 | | - param.value = Number(stringValue); |
58 | | - break; |
| 79 | + if (value !== undefined) param.value = value; |
59 | 80 | } |
60 | | - case "string": { |
61 | | - param.value = await inquirer.input({ |
62 | | - message: param.title, |
63 | | - default: param.value as string, |
64 | | - }); |
65 | | - if (param.required && param.value === "") { |
66 | | - console.error(chalk.red(`Error: Parameter "%s" is required.`), param.title); |
67 | | - process.exit(1); |
| 81 | + |
| 82 | + // input values |
| 83 | + if (!(key in cliParams)) { |
| 84 | + switch (param.type) { |
| 85 | + case "boolean": { |
| 86 | + param.value = await inquirer.confirm({ |
| 87 | + message: param.title, |
| 88 | + default: param.value as boolean, |
| 89 | + }); |
| 90 | + break; |
| 91 | + } |
| 92 | + case "number": { |
| 93 | + const stringValue = await inquirer.input({ |
| 94 | + message: param.title, |
| 95 | + default: param.value.toString(), |
| 96 | + }); |
| 97 | + param.value = Number(stringValue); |
| 98 | + break; |
| 99 | + } |
| 100 | + case "string": { |
| 101 | + param.value = await inquirer.input({ |
| 102 | + message: param.title, |
| 103 | + default: param.value as string, |
| 104 | + }); |
| 105 | + if (param.required && param.value === "") { |
| 106 | + console.error(chalk.red(`Error: Parameter "%s" is required.`), param.title); |
| 107 | + process.exit(1); |
| 108 | + } |
| 109 | + break; |
| 110 | + } |
68 | 111 | } |
69 | | - break; |
70 | 112 | } |
71 | 113 | } |
72 | | - } |
73 | | - const paramValues = Object.fromEntries( |
74 | | - Object.entries(scriptParams).map(([key, param]) => { |
75 | | - return [key, param.value]; |
76 | | - }) |
77 | | - ); |
78 | 114 |
|
79 | | - const t = (text: string, params: Record<string, boolean | number | string> = paramValues) => { |
80 | | - let result = text; |
81 | | - for (const [key, value] of Object.entries(params)) { |
82 | | - result = result.replace(new RegExp(`__${key}__`, "g"), String(value)); |
83 | | - } |
| 115 | + const paramValues = Object.fromEntries( |
| 116 | + Object.entries(scriptParams).map(([key, param]) => { |
| 117 | + return [key, param.value]; |
| 118 | + }) |
| 119 | + ); |
84 | 120 |
|
85 | | - return result; |
86 | | - }; |
| 121 | + const t = (text: string, params: Record<string, boolean | number | string> = paramValues) => { |
| 122 | + let result = text; |
| 123 | + for (const [key, value] of Object.entries(params)) { |
| 124 | + result = result.replace(new RegExp(`__${key}__`, "g"), String(value)); |
| 125 | + } |
87 | 126 |
|
88 | | - // collect script files |
89 | | - const scriptDir = dirname(script.path); |
90 | | - const files = globSync("**/*", { cwd: scriptDir, dot: true, nodir: true, ignore: script.path }).map((path) => { |
91 | | - return { |
92 | | - path, |
93 | | - get content() { |
94 | | - return fs.readFileSync(resolve(scriptDir, path), "utf8"); |
95 | | - }, |
| 127 | + return result; |
96 | 128 | }; |
97 | | - }); |
98 | 129 |
|
99 | | - // run the script |
100 | | - script.run({ |
101 | | - cwd: process.cwd(), |
102 | | - files, |
103 | | - params: paramValues as Parameters<typeof script.run>[0]["params"], |
104 | | - project: Project.resolveFromPath(process.cwd()), |
105 | | - self: script, |
106 | | - get fileMap() { |
107 | | - return new Proxy( |
108 | | - {}, |
109 | | - { |
110 | | - get(_, path) { |
111 | | - if (typeof path !== "string") throw new Error("Invalid path."); |
112 | | - if (!fs.existsSync(resolve(scriptDir, path))) throw new Error(`File "${path}" not found.`); |
113 | | - return fs.readFileSync(resolve(scriptDir, path as string), "utf8"); |
114 | | - }, |
115 | | - } |
116 | | - ); |
117 | | - }, |
118 | | - t, |
119 | | - }); |
120 | | - }); |
| 130 | + // collect script files |
| 131 | + const scriptDir = dirname(script.path); |
| 132 | + const files = globSync("**/*", { cwd: scriptDir, dot: true, nodir: true, ignore: script.path }).map((path) => { |
| 133 | + return { |
| 134 | + path, |
| 135 | + get content() { |
| 136 | + return fs.readFileSync(resolve(scriptDir, path), "utf8"); |
| 137 | + }, |
| 138 | + }; |
| 139 | + }); |
| 140 | + |
| 141 | + // run the script |
| 142 | + script.run({ |
| 143 | + cwd: process.cwd(), |
| 144 | + files, |
| 145 | + params: paramValues as Parameters<typeof script.run>[0]["params"], |
| 146 | + project: Project.resolveFromPath(process.cwd()), |
| 147 | + self: script, |
| 148 | + get fileMap() { |
| 149 | + return new Proxy( |
| 150 | + {}, |
| 151 | + { |
| 152 | + get(_, path) { |
| 153 | + if (typeof path !== "string") throw new Error("Invalid path."); |
| 154 | + if (!fs.existsSync(resolve(scriptDir, path))) throw new Error(`File "${path}" not found.`); |
| 155 | + return fs.readFileSync(resolve(scriptDir, path as string), "utf8"); |
| 156 | + }, |
| 157 | + } |
| 158 | + ); |
| 159 | + }, |
| 160 | + t, |
| 161 | + }); |
| 162 | + } |
| 163 | + ); |
121 | 164 |
|
122 | 165 | export { createRunCommand }; |
0 commit comments