-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcli.ts
More file actions
124 lines (108 loc) · 3.43 KB
/
Copy pathcli.ts
File metadata and controls
124 lines (108 loc) · 3.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import $ from "@david/dax";
import { logger } from "./common/logger.ts";
import { parseArgs } from "@std/cli";
import { walk } from "@std/fs";
import { relative } from "@std/path";
import { mainArgs } from "./common/util.ts";
// commands ディレクトリからの相対パスでのコマンドファイル一覧を取得
const listCommandFiles = async (): Promise<string[]> => {
const commandFiles: string[] = [];
for await (
const entry of walk(new URL("./commands", import.meta.url), {
includeDirs: false,
exts: [".ts"],
// testファイルと、ファイル名の先頭が _ で始まるファイルをスキップ
skip: [/\.test\.ts$/, /\/_[^/]*\.ts$/],
})
) {
const relativePath = relative(
new URL("./commands/", import.meta.url).pathname,
entry.path,
);
commandFiles.push(relativePath);
}
return commandFiles;
};
// コマンドファイルからユーザーに選択させるオプションを取得 (1階層ずつ再帰的)
export const listOptionsFromCommandFiles = (
commandFiles: string[],
dirPath: string,
): string[] => {
const validatedDirPath = dirPath === "" || dirPath.endsWith("/")
? dirPath
: dirPath + "/";
const options: Set<string> = new Set();
for (const commandFile of commandFiles) {
if (commandFile.startsWith(validatedDirPath)) {
const splitted = commandFile.slice(validatedDirPath.length).split("/");
if (splitted[0]) {
const option = splitted.length > 1
? splitted[0] + "/"
: splitted[0].replace(/\.ts$/, "");
options.add(option);
}
}
}
return Array.from(options);
};
const selectCommand = async (
commandFiles: string[],
dirPath: string = "",
): Promise<string> => {
const options = listOptionsFromCommandFiles(commandFiles, dirPath);
// options が一つの場合は確認して選択する (select が error になるため)
if (options.length === 1) {
const command = dirPath + options[0];
const confirm = await $.confirm(
`Do you want to execute the command: ${command}?`,
);
if (confirm) {
return command;
} else {
logger.info("Command is canceled.");
Deno.exit(0);
}
}
const selectedIndex = await $.select({
message: "Select a command",
options,
});
const selected = options[selectedIndex];
if (selected.endsWith("/")) {
const nextDirPath = dirPath + selected;
// 再帰的に選択
return selectCommand(commandFiles, nextDirPath);
}
return dirPath + selected;
};
const executeCommand = async (commandFiles: string[], command: string) => {
const commandFile = commandFiles.find((file) => file.startsWith(command));
if (!commandFile) {
throw new Error(`Command not found: ${command}`);
}
const commandFullPath = new URL(
`./commands/${commandFile}`,
import.meta.url,
);
await import(commandFullPath.href);
};
export const main = async () => {
const args = parseArgs(mainArgs.get());
const path = args._[0];
const commandFiles = await listCommandFiles();
let command: string | undefined = undefined;
if (path && typeof path === "string") {
const commandFile = commandFiles.find((file) => file.startsWith(path));
if (!commandFile) {
logger.error(`Command not found: ${path}`);
Deno.exit(1);
}
command = path;
} else {
command = await selectCommand(commandFiles);
}
await executeCommand(commandFiles, command);
};
if (import.meta.main) {
main();
}