bet-cli 0.1.4 → 0.3.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 +26 -10
- package/dist/commands/edit.js +48 -0
- package/dist/commands/ignore.js +1 -1
- package/dist/commands/info.js +16 -16
- package/dist/commands/list.js +32 -17
- package/dist/commands/update.js +13 -6
- package/dist/index.js +8 -29
- package/dist/lib/config.js +12 -0
- package/dist/lib/editor.js +97 -0
- package/dist/lib/git.js +14 -8
- package/dist/lib/help.js +7 -3
- package/dist/lib/readme.js +5 -2
- package/dist/main.js +50 -0
- package/dist/ui/markdown.js +8 -9
- package/dist/ui/search.js +6 -7
- package/dist/ui/select.js +9 -11
- package/package.json +4 -1
- package/src/commands/edit.ts +56 -0
- package/src/commands/ignore.ts +1 -1
- package/src/commands/info.tsx +225 -136
- package/src/commands/list.ts +81 -53
- package/src/commands/update.ts +203 -148
- package/src/index.ts +8 -34
- package/src/lib/config.ts +20 -1
- package/src/lib/editor.ts +131 -0
- package/src/lib/git.ts +20 -10
- package/src/lib/help.ts +15 -15
- package/src/lib/readme.ts +6 -1
- package/src/lib/types.ts +4 -0
- package/src/main.ts +57 -0
- package/src/ui/markdown.tsx +14 -0
- package/src/ui/search.tsx +55 -24
- package/src/ui/select.tsx +48 -27
- package/tests/config.test.ts +71 -0
- package/tests/editor.test.ts +167 -0
package/src/commands/list.ts
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
import chalk from
|
|
2
|
-
import { Command } from
|
|
3
|
-
import { readConfig } from
|
|
4
|
-
import path from
|
|
5
|
-
import { listProjects } from
|
|
6
|
-
import type { Project, RootConfig } from
|
|
7
|
-
import { emitSelection } from
|
|
8
|
-
import { promptSelect } from
|
|
9
|
-
import { SelectRow } from
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { readConfig } from "../lib/config.js";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { listProjects } from "../lib/projects.js";
|
|
6
|
+
import type { Project, RootConfig } from "../lib/types.js";
|
|
7
|
+
import { emitSelection } from "../utils/output.js";
|
|
8
|
+
import { promptSelect } from "../ui/prompt.js";
|
|
9
|
+
import { SelectRow } from "../ui/select.js";
|
|
10
10
|
|
|
11
11
|
const MAX_ROWS = 18;
|
|
12
|
-
const GROUP_COLORS = [
|
|
12
|
+
const GROUP_COLORS = [
|
|
13
|
+
"#22d3ee",
|
|
14
|
+
"#34d399",
|
|
15
|
+
"#facc15",
|
|
16
|
+
"#c084fc",
|
|
17
|
+
"#60a5fa",
|
|
18
|
+
"#f87171",
|
|
19
|
+
];
|
|
13
20
|
|
|
14
21
|
function groupColor(index: number): string {
|
|
15
22
|
return GROUP_COLORS[index % GROUP_COLORS.length];
|
|
@@ -28,7 +35,10 @@ function orderedGroupsByConfigRoots(
|
|
|
28
35
|
projects: Project[],
|
|
29
36
|
roots: RootConfig[],
|
|
30
37
|
): { rootName: string; items: Project[] }[] {
|
|
31
|
-
const groupsByRoot = new Map<
|
|
38
|
+
const groupsByRoot = new Map<
|
|
39
|
+
string,
|
|
40
|
+
{ rootName: string; items: Project[] }
|
|
41
|
+
>();
|
|
32
42
|
for (const project of projects) {
|
|
33
43
|
const key = project.root;
|
|
34
44
|
if (!groupsByRoot.has(key)) {
|
|
@@ -60,58 +70,76 @@ function orderedGroupsByConfigRoots(
|
|
|
60
70
|
|
|
61
71
|
export function registerList(program: Command): void {
|
|
62
72
|
program
|
|
63
|
-
.command(
|
|
64
|
-
.
|
|
65
|
-
.
|
|
66
|
-
.option(
|
|
67
|
-
.option(
|
|
68
|
-
.
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
.command("list")
|
|
74
|
+
.alias("ls")
|
|
75
|
+
.description("List projects")
|
|
76
|
+
.option("--plain", "Print a non-interactive list")
|
|
77
|
+
.option("--json", "Print JSON output")
|
|
78
|
+
.option("--print", "Print selected path only")
|
|
79
|
+
.action(
|
|
80
|
+
async (options: { plain?: boolean; json?: boolean; print?: boolean }) => {
|
|
81
|
+
const config = await readConfig();
|
|
82
|
+
const projects = listProjects(config);
|
|
71
83
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
84
|
+
if (options.json) {
|
|
85
|
+
process.stdout.write(JSON.stringify(projects, null, 2));
|
|
86
|
+
process.stdout.write("\n");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
77
89
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
90
|
+
if (!process.stdin.isTTY || options.plain) {
|
|
91
|
+
if (projects.length === 0) {
|
|
92
|
+
process.stdout.write("No projects indexed. Run bet update.\n");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const orderedGroups = orderedGroupsByConfigRoots(
|
|
96
|
+
projects,
|
|
97
|
+
config.roots,
|
|
98
|
+
);
|
|
99
|
+
let groupIndex = 0;
|
|
100
|
+
for (const { rootName, items } of orderedGroups) {
|
|
101
|
+
const color = groupColor(groupIndex++);
|
|
102
|
+
const label = chalk.hex(color).bold(`[${rootName}]`);
|
|
103
|
+
process.stdout.write(`${label}\n`);
|
|
104
|
+
for (const project of items) {
|
|
105
|
+
const rel = relativePath(project.path, project.root);
|
|
106
|
+
process.stdout.write(
|
|
107
|
+
` ${chalk.reset(formatLabel(project.slug, project.rootName, rel))}\n`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
81
111
|
return;
|
|
82
112
|
}
|
|
83
|
-
|
|
113
|
+
|
|
114
|
+
const orderedGroups = orderedGroupsByConfigRoots(
|
|
115
|
+
projects,
|
|
116
|
+
config.roots,
|
|
117
|
+
);
|
|
118
|
+
const rows: SelectRow<Project>[] = [];
|
|
84
119
|
let groupIndex = 0;
|
|
85
120
|
for (const { rootName, items } of orderedGroups) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
121
|
+
rows.push({
|
|
122
|
+
type: "group",
|
|
123
|
+
label: rootName,
|
|
124
|
+
color: groupColor(groupIndex++),
|
|
125
|
+
});
|
|
89
126
|
for (const project of items) {
|
|
90
127
|
const rel = relativePath(project.path, project.root);
|
|
91
|
-
|
|
128
|
+
rows.push({
|
|
129
|
+
type: "item",
|
|
130
|
+
label: formatLabel(project.slug, project.rootName, rel),
|
|
131
|
+
value: project,
|
|
132
|
+
});
|
|
92
133
|
}
|
|
93
134
|
}
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const orderedGroups = orderedGroupsByConfigRoots(projects, config.roots);
|
|
98
|
-
const rows: SelectRow<Project>[] = [];
|
|
99
|
-
let groupIndex = 0;
|
|
100
|
-
for (const { rootName, items } of orderedGroups) {
|
|
101
|
-
rows.push({ type: 'group', label: rootName, color: groupColor(groupIndex++) });
|
|
102
|
-
for (const project of items) {
|
|
103
|
-
const rel = relativePath(project.path, project.root);
|
|
104
|
-
rows.push({
|
|
105
|
-
type: 'item',
|
|
106
|
-
label: formatLabel(project.slug, project.rootName, rel),
|
|
107
|
-
value: project,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
135
|
|
|
112
|
-
|
|
113
|
-
|
|
136
|
+
const selected = await promptSelect(rows, {
|
|
137
|
+
title: "Projects",
|
|
138
|
+
maxRows: MAX_ROWS,
|
|
139
|
+
});
|
|
140
|
+
if (!selected) return;
|
|
114
141
|
|
|
115
|
-
|
|
116
|
-
|
|
142
|
+
emitSelection(selected.value, { printOnly: options.print });
|
|
143
|
+
},
|
|
144
|
+
);
|
|
117
145
|
}
|
package/src/commands/update.ts
CHANGED
|
@@ -3,7 +3,12 @@ import readline from "node:readline";
|
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { readConfig, resolveRoots, writeConfig } from "../lib/config.js";
|
|
5
5
|
import { normalizeAbsolute } from "../utils/paths.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
installUpdateCron,
|
|
8
|
+
uninstallUpdateCron,
|
|
9
|
+
parseCronSchedule,
|
|
10
|
+
formatScheduleLabel,
|
|
11
|
+
} from "../lib/cron.js";
|
|
7
12
|
import { scanRoots } from "../lib/scan.js";
|
|
8
13
|
import { computeMetadata } from "../lib/metadata.js";
|
|
9
14
|
import { getEffectiveIgnores, isPathIgnored } from "../lib/ignore.js";
|
|
@@ -30,16 +35,16 @@ export function willOverrideRoots(
|
|
|
30
35
|
providedRootConfigs: RootConfig[] | undefined,
|
|
31
36
|
configRoots: RootConfig[],
|
|
32
37
|
): boolean {
|
|
33
|
-
return !!(
|
|
34
|
-
providedRootConfigs !== undefined &&
|
|
35
|
-
configRoots.length > 0
|
|
36
|
-
);
|
|
38
|
+
return !!(providedRootConfigs !== undefined && configRoots.length > 0);
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
const DEFAULT_SLUG_PARENT_FOLDERS = ["src", "app"];
|
|
40
42
|
|
|
41
43
|
export { DEFAULT_SLUG_PARENT_FOLDERS };
|
|
42
|
-
export function projectSlug(
|
|
44
|
+
export function projectSlug(
|
|
45
|
+
pathName: string,
|
|
46
|
+
slugParentFolders: string[],
|
|
47
|
+
): string {
|
|
43
48
|
const folderName = path.basename(pathName);
|
|
44
49
|
if (slugParentFolders.includes(folderName)) {
|
|
45
50
|
return path.basename(path.dirname(pathName));
|
|
@@ -47,8 +52,14 @@ export function projectSlug(pathName: string, slugParentFolders: string[]): stri
|
|
|
47
52
|
return folderName;
|
|
48
53
|
}
|
|
49
54
|
|
|
50
|
-
async function promptYesNo(
|
|
51
|
-
|
|
55
|
+
async function promptYesNo(
|
|
56
|
+
question: string,
|
|
57
|
+
defaultNo = true,
|
|
58
|
+
): Promise<boolean> {
|
|
59
|
+
const rl = readline.createInterface({
|
|
60
|
+
input: process.stdin,
|
|
61
|
+
output: process.stderr,
|
|
62
|
+
});
|
|
52
63
|
return new Promise((resolve) => {
|
|
53
64
|
const defaultHint = defaultNo ? "y/N" : "Y/n";
|
|
54
65
|
rl.question(question + " [" + defaultHint + "] ", (answer) => {
|
|
@@ -69,165 +80,209 @@ export function registerUpdate(program: Command): void {
|
|
|
69
80
|
.description("Scan roots and update the project index")
|
|
70
81
|
.option("--roots <paths>", "Comma-separated list of roots to scan")
|
|
71
82
|
.option("--force", "Allow overriding configured roots when not in TTY")
|
|
72
|
-
.option(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
83
|
+
.option(
|
|
84
|
+
"--cron [frequency]",
|
|
85
|
+
"Run update on a schedule: Nm/Nh/Nd e.g. 5m, 1h, 2d (default 1h), or 0/false to disable",
|
|
86
|
+
)
|
|
87
|
+
.action(
|
|
88
|
+
async (options: {
|
|
89
|
+
roots?: string;
|
|
90
|
+
force?: boolean;
|
|
91
|
+
cron?: boolean | string;
|
|
92
|
+
}) => {
|
|
93
|
+
try {
|
|
94
|
+
const config = await readConfig();
|
|
95
|
+
const providedPaths = parseRoots(options.roots);
|
|
96
|
+
const providedRootConfigs = providedPaths
|
|
97
|
+
? pathsToRootConfigs(providedPaths)
|
|
98
|
+
: undefined;
|
|
99
|
+
const configRoots =
|
|
100
|
+
config.roots.length > 0 ? config.roots : undefined;
|
|
101
|
+
const rootsToUse = providedRootConfigs ?? configRoots;
|
|
92
102
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (willOverride) {
|
|
96
|
-
log.warn("--roots overrides configured roots", "configured:", configRoots!.map((r) => r.path).join(", "), "provided:", providedRootConfigs!.map((r) => r.path).join(", "));
|
|
97
|
-
process.stderr.write(
|
|
98
|
-
"Warning: --roots will override your configured roots.\n" +
|
|
99
|
-
" Configured: " +
|
|
100
|
-
configRoots!.map((r) => r.path).join(", ") +
|
|
101
|
-
"\n Provided: " +
|
|
102
|
-
providedRootConfigs!.map((r) => r.path).join(", ") +
|
|
103
|
-
"\n",
|
|
104
|
-
);
|
|
105
|
-
if (!process.stdin.isTTY) {
|
|
106
|
-
if (!options.force) {
|
|
107
|
-
log.error("update failed: refusing to override roots without confirmation (use --force when not in TTY)");
|
|
103
|
+
if (!rootsToUse || rootsToUse.length === 0) {
|
|
104
|
+
log.error("update failed: no roots specified");
|
|
108
105
|
process.stderr.write(
|
|
109
|
-
"Error:
|
|
106
|
+
"Error: No roots specified. Please provide roots using --roots option.\n" +
|
|
107
|
+
"Example: bet update --roots /path/to/your/code\n",
|
|
110
108
|
);
|
|
111
109
|
process.exitCode = 1;
|
|
112
110
|
return;
|
|
113
111
|
}
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
112
|
+
|
|
113
|
+
const willOverride = willOverrideRoots(
|
|
114
|
+
providedRootConfigs,
|
|
115
|
+
config.roots,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (willOverride) {
|
|
119
|
+
log.warn(
|
|
120
|
+
"--roots overrides configured roots",
|
|
121
|
+
"configured:",
|
|
122
|
+
configRoots!.map((r) => r.path).join(", "),
|
|
123
|
+
"provided:",
|
|
124
|
+
providedRootConfigs!.map((r) => r.path).join(", "),
|
|
125
|
+
);
|
|
126
|
+
process.stderr.write(
|
|
127
|
+
"Warning: --roots will override your configured roots.\n" +
|
|
128
|
+
" Configured: " +
|
|
129
|
+
configRoots!.map((r) => r.path).join(", ") +
|
|
130
|
+
"\n Provided: " +
|
|
131
|
+
providedRootConfigs!.map((r) => r.path).join(", ") +
|
|
132
|
+
"\n",
|
|
133
|
+
);
|
|
134
|
+
if (!process.stdin.isTTY) {
|
|
135
|
+
if (!options.force) {
|
|
136
|
+
log.error(
|
|
137
|
+
"update failed: refusing to override roots without confirmation (use --force when not in TTY)",
|
|
138
|
+
);
|
|
139
|
+
process.stderr.write(
|
|
140
|
+
"Error: Refusing to override without confirmation. Run interactively or use --force.\n",
|
|
141
|
+
);
|
|
142
|
+
process.exitCode = 1;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
const confirmed = await promptYesNo("Continue?", true);
|
|
147
|
+
if (!confirmed) {
|
|
148
|
+
log.info("update aborted by user");
|
|
149
|
+
process.stderr.write("Aborted.\n");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
120
153
|
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
154
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
155
|
+
const rootsResolved = resolveRoots(rootsToUse);
|
|
156
|
+
const rootPaths = rootsResolved.map((r) => r.path);
|
|
157
|
+
log.info("update started", "roots=" + rootPaths.join(", "));
|
|
158
|
+
log.debug("scanning roots", rootPaths.length, "root(s)");
|
|
159
|
+
const ignores = getEffectiveIgnores(config);
|
|
160
|
+
const candidates = await scanRoots(rootPaths, ignores);
|
|
161
|
+
const ignoredPaths = config.ignoredPaths ?? [];
|
|
162
|
+
const filteredCandidates = candidates.filter(
|
|
163
|
+
(c) => !isPathIgnored(c.path, ignoredPaths),
|
|
164
|
+
);
|
|
165
|
+
log.debug(
|
|
166
|
+
"found",
|
|
167
|
+
filteredCandidates.length,
|
|
168
|
+
"candidate(s) after ignoring paths",
|
|
169
|
+
);
|
|
170
|
+
const projects: Record<string, Project> = {};
|
|
171
|
+
|
|
172
|
+
for (const candidate of filteredCandidates) {
|
|
173
|
+
const hasGit = await isInsideGitRepo(candidate.path);
|
|
174
|
+
const auto = await computeMetadata(candidate.path, hasGit, ignores);
|
|
175
|
+
const slug = projectSlug(
|
|
176
|
+
candidate.path,
|
|
177
|
+
config.slugParentFolders ?? DEFAULT_SLUG_PARENT_FOLDERS,
|
|
178
|
+
);
|
|
179
|
+
const existing = config.projects[candidate.path];
|
|
180
|
+
const rootConfig = rootsResolved.find(
|
|
181
|
+
(r) => r.path === candidate.root,
|
|
182
|
+
);
|
|
183
|
+
const rootName = rootConfig?.name ?? path.basename(candidate.root);
|
|
184
|
+
|
|
185
|
+
const project: Project = {
|
|
186
|
+
id: candidate.path,
|
|
187
|
+
slug,
|
|
188
|
+
name: slug,
|
|
189
|
+
path: candidate.path,
|
|
190
|
+
root: candidate.root,
|
|
191
|
+
rootName,
|
|
192
|
+
hasGit,
|
|
193
|
+
hasReadme: candidate.hasReadme,
|
|
194
|
+
auto,
|
|
195
|
+
user: existing?.user,
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
projects[candidate.path] = project;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const nextConfig: Config = {
|
|
202
|
+
version: config.version ?? 1,
|
|
203
|
+
roots: rootsResolved,
|
|
204
|
+
projects,
|
|
205
|
+
updatedAt: new Date().toISOString(),
|
|
206
|
+
...(config.ignores !== undefined && { ignores: config.ignores }),
|
|
207
|
+
...(config.ignoredPaths !== undefined && {
|
|
208
|
+
ignoredPaths: config.ignoredPaths,
|
|
209
|
+
}),
|
|
210
|
+
...(config.slugParentFolders !== undefined && {
|
|
211
|
+
slugParentFolders: config.slugParentFolders,
|
|
212
|
+
}),
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
await writeConfig(nextConfig);
|
|
216
|
+
|
|
217
|
+
const projectCount = Object.keys(projects).length;
|
|
218
|
+
const rootCount = rootsResolved.length;
|
|
219
|
+
log.info(
|
|
220
|
+
"update completed",
|
|
221
|
+
"projects=" + projectCount,
|
|
222
|
+
"roots=" + rootCount,
|
|
223
|
+
);
|
|
160
224
|
|
|
161
|
-
const nextConfig: Config = {
|
|
162
|
-
version: config.version ?? 1,
|
|
163
|
-
roots: rootsResolved,
|
|
164
|
-
projects,
|
|
165
|
-
...(config.ignores !== undefined && { ignores: config.ignores }),
|
|
166
|
-
...(config.ignoredPaths !== undefined && { ignoredPaths: config.ignoredPaths }),
|
|
167
|
-
...(config.slugParentFolders !== undefined && { slugParentFolders: config.slugParentFolders }),
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
await writeConfig(nextConfig);
|
|
171
|
-
|
|
172
|
-
const projectCount = Object.keys(projects).length;
|
|
173
|
-
const rootCount = rootsResolved.length;
|
|
174
|
-
log.info("update completed", "projects=" + projectCount, "roots=" + rootCount);
|
|
175
|
-
|
|
176
|
-
process.stdout.write(
|
|
177
|
-
"Indexed " +
|
|
178
|
-
projectCount +
|
|
179
|
-
" projects from " +
|
|
180
|
-
rootCount +
|
|
181
|
-
" root(s).\n",
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
if (options.cron !== undefined && options.cron !== false) {
|
|
185
|
-
const entryScriptPath = path.isAbsolute(process.argv[1] ?? "")
|
|
186
|
-
? process.argv[1]
|
|
187
|
-
: path.resolve(process.cwd(), process.argv[1] ?? "dist/index.js");
|
|
188
|
-
const cronOpt = options.cron;
|
|
189
|
-
if (cronOpt === true) {
|
|
190
|
-
const { wrapperPath, logPath } = await installUpdateCron({
|
|
191
|
-
nodePath: process.execPath,
|
|
192
|
-
entryScriptPath,
|
|
193
|
-
schedule: "1h",
|
|
194
|
-
});
|
|
195
|
-
process.stdout.write("Installed cron for bet update (every hour).\n");
|
|
196
225
|
process.stdout.write(
|
|
197
|
-
|
|
226
|
+
"Indexed " +
|
|
227
|
+
projectCount +
|
|
228
|
+
" projects from " +
|
|
229
|
+
rootCount +
|
|
230
|
+
" root(s).\n",
|
|
198
231
|
);
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const parsed = parseCronSchedule(cronOpt);
|
|
232
|
+
|
|
233
|
+
if (options.cron !== undefined && options.cron !== false) {
|
|
234
|
+
const entryScriptPath = path.isAbsolute(process.argv[1] ?? "")
|
|
235
|
+
? process.argv[1]
|
|
236
|
+
: path.resolve(process.cwd(), process.argv[1] ?? "dist/index.js");
|
|
237
|
+
const cronOpt = options.cron;
|
|
238
|
+
if (cronOpt === true) {
|
|
207
239
|
const { wrapperPath, logPath } = await installUpdateCron({
|
|
208
240
|
nodePath: process.execPath,
|
|
209
241
|
entryScriptPath,
|
|
210
|
-
schedule:
|
|
242
|
+
schedule: "1h",
|
|
211
243
|
});
|
|
212
|
-
|
|
213
|
-
|
|
244
|
+
process.stdout.write(
|
|
245
|
+
"Installed cron for bet update (every hour).\n",
|
|
246
|
+
);
|
|
214
247
|
process.stdout.write(
|
|
215
248
|
` Wrapper script: ${wrapperPath}\n Log file: ${logPath}\n To view or edit crontab: crontab -l / crontab -e\n`,
|
|
216
249
|
);
|
|
217
|
-
}
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
250
|
+
} else if (typeof cronOpt === "string") {
|
|
251
|
+
const normalized = cronOpt.trim().toLowerCase();
|
|
252
|
+
if (normalized === "0" || normalized === "false") {
|
|
253
|
+
await uninstallUpdateCron();
|
|
254
|
+
process.stdout.write("Removed cron for bet update.\n");
|
|
255
|
+
} else {
|
|
256
|
+
try {
|
|
257
|
+
const parsed = parseCronSchedule(cronOpt);
|
|
258
|
+
const { wrapperPath, logPath } = await installUpdateCron({
|
|
259
|
+
nodePath: process.execPath,
|
|
260
|
+
entryScriptPath,
|
|
261
|
+
schedule: cronOpt,
|
|
262
|
+
});
|
|
263
|
+
const label = formatScheduleLabel(parsed);
|
|
264
|
+
process.stdout.write(
|
|
265
|
+
`Installed cron for bet update (${label}).\n`,
|
|
266
|
+
);
|
|
267
|
+
process.stdout.write(
|
|
268
|
+
` Wrapper script: ${wrapperPath}\n Log file: ${logPath}\n To view or edit crontab: crontab -l / crontab -e\n`,
|
|
269
|
+
);
|
|
270
|
+
} catch (err) {
|
|
271
|
+
const message =
|
|
272
|
+
err instanceof Error ? err.message : String(err);
|
|
273
|
+
log.error(err instanceof Error ? err : new Error(message));
|
|
274
|
+
process.stderr.write(`Error: ${message}\n`);
|
|
275
|
+
process.exitCode = 1;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
222
278
|
}
|
|
223
279
|
}
|
|
280
|
+
} catch (err) {
|
|
281
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
282
|
+
log.error(err instanceof Error ? err : new Error(message));
|
|
283
|
+
process.stderr.write(`Error: ${message}\n`);
|
|
284
|
+
process.exitCode = 1;
|
|
224
285
|
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
228
|
-
log.error(err instanceof Error ? err : new Error(message));
|
|
229
|
-
process.stderr.write(`Error: ${message}\n`);
|
|
230
|
-
process.exitCode = 1;
|
|
231
|
-
}
|
|
232
|
-
});
|
|
286
|
+
},
|
|
287
|
+
);
|
|
233
288
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,35 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
import { registerShell } from "./commands/shell.js";
|
|
11
|
-
import { registerCompletion } from "./commands/completion.js";
|
|
12
|
-
import { registerIgnore } from "./commands/ignore.js";
|
|
13
|
-
|
|
14
|
-
const program = new Command();
|
|
15
|
-
|
|
16
|
-
program.createHelp = function createHelp(this: Command) {
|
|
17
|
-
return Object.assign(new GroupedHelp(), this.configureHelp());
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
program
|
|
21
|
-
.name("bet")
|
|
22
|
-
.description("Explore and jump between local projects.")
|
|
23
|
-
.version("0.1.4");
|
|
24
|
-
|
|
25
|
-
registerUpdate(program);
|
|
26
|
-
registerList(program);
|
|
27
|
-
registerSearch(program);
|
|
28
|
-
registerInfo(program);
|
|
29
|
-
registerGo(program);
|
|
30
|
-
registerPath(program);
|
|
31
|
-
registerShell(program);
|
|
32
|
-
registerCompletion(program);
|
|
33
|
-
registerIgnore(program);
|
|
34
|
-
|
|
35
|
-
program.parseAsync(process.argv);
|
|
2
|
+
/**
|
|
3
|
+
* Set FORCE_COLOR before any module (including Ink/chalk) is loaded so that
|
|
4
|
+
* list/search/info render with colors. ESM hoists static imports, so we must
|
|
5
|
+
* use a dynamic import here.
|
|
6
|
+
*/
|
|
7
|
+
process.env.FORCE_COLOR = "1";
|
|
8
|
+
|
|
9
|
+
await import("./main.js");
|