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/dist/main.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { GroupedHelp } from "./lib/help.js";
|
|
3
|
+
import { registerUpdate } from "./commands/update.js";
|
|
4
|
+
import { registerList } from "./commands/list.js";
|
|
5
|
+
import { registerSearch } from "./commands/search.js";
|
|
6
|
+
import { registerInfo } from "./commands/info.js";
|
|
7
|
+
import { registerGo } from "./commands/go.js";
|
|
8
|
+
import { registerPath } from "./commands/path.js";
|
|
9
|
+
import { registerShell } from "./commands/shell.js";
|
|
10
|
+
import { registerCompletion } from "./commands/completion.js";
|
|
11
|
+
import { registerIgnore } from "./commands/ignore.js";
|
|
12
|
+
import { registerEdit } from "./commands/edit.js";
|
|
13
|
+
const ASCII_HEADER = `
|
|
14
|
+
░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░
|
|
15
|
+
░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░
|
|
16
|
+
░░░░░░░░░░░░░ ░░░░░░ ░░░░░░
|
|
17
|
+
░░░░░░░░░░░░░░░░░ ░░░░░░ ░░░░░░
|
|
18
|
+
░░░░░░░░░░░░░ ░░░░░ ░░░░░░ ░░░░░░
|
|
19
|
+
░░░░░░░░░░░░░ ░░░░░ ░░░░░░ ░░░░░░
|
|
20
|
+
░░░░░░░░░░░░ ░░░░░ ░░░░░░ ░░░░░░
|
|
21
|
+
░░░░░░░░░░░░░░░░ ░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░
|
|
22
|
+
░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░
|
|
23
|
+
░░░░░░░░░░░░░░ ░░░░░░ ░░░░░░
|
|
24
|
+
░░░░░░░░░ ░░░░░░ ░░░░░░
|
|
25
|
+
░░░░░ ░░░░░░ ░░░░░░
|
|
26
|
+
░░░░░ ░░░░░░ ░░░░░░
|
|
27
|
+
░░░░░ ░░░░░░ ░░░░░░
|
|
28
|
+
░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░
|
|
29
|
+
░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░
|
|
30
|
+
`;
|
|
31
|
+
const program = new Command();
|
|
32
|
+
program.createHelp = function createHelp() {
|
|
33
|
+
return Object.assign(new GroupedHelp(), this.configureHelp());
|
|
34
|
+
};
|
|
35
|
+
program
|
|
36
|
+
.name("bet")
|
|
37
|
+
.description("Explore and jump between local projects.")
|
|
38
|
+
.version("0.2.0");
|
|
39
|
+
registerUpdate(program);
|
|
40
|
+
registerList(program);
|
|
41
|
+
registerSearch(program);
|
|
42
|
+
registerInfo(program);
|
|
43
|
+
registerGo(program);
|
|
44
|
+
registerEdit(program);
|
|
45
|
+
registerPath(program);
|
|
46
|
+
registerShell(program);
|
|
47
|
+
registerCompletion(program);
|
|
48
|
+
registerIgnore(program);
|
|
49
|
+
program.addHelpText("before", ASCII_HEADER);
|
|
50
|
+
program.parseAsync(process.argv);
|
package/dist/ui/markdown.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
marked.
|
|
8
|
-
|
|
9
|
-
return _jsx(Text, { children: output });
|
|
1
|
+
import { Text } from "ink";
|
|
2
|
+
import { marked } from "marked";
|
|
3
|
+
import { markedTerminal } from "marked-terminal";
|
|
4
|
+
import React from "react";
|
|
5
|
+
export default function Markdown({ children, ...options }) {
|
|
6
|
+
marked.use(markedTerminal(options));
|
|
7
|
+
const parsedMarkdown = marked.parse(children, { async: false });
|
|
8
|
+
return React.createElement(Text, null, parsedMarkdown.trim());
|
|
10
9
|
}
|
package/dist/ui/search.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo, useState } from 'react';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
3
|
import { Box, Text, useInput } from 'ink';
|
|
5
4
|
const DEFAULT_MAX_ROWS = 18;
|
|
6
5
|
export function SearchSelect({ title, allItems, filter, onSelect, onCancel, maxRows = DEFAULT_MAX_ROWS, initialQuery = '', showCount = true, }) {
|
|
@@ -37,7 +36,7 @@ export function SearchSelect({ title, allItems, filter, onSelect, onCancel, maxR
|
|
|
37
36
|
}
|
|
38
37
|
});
|
|
39
38
|
if (items.length === 0) {
|
|
40
|
-
return (_jsxs(Box, { flexDirection: "column", children: [title
|
|
39
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", padding: 1, children: [title ? (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: title }) })) : null, _jsxs(Box, { marginBottom: 1, flexDirection: "row", children: [_jsx(Text, { bold: true, color: "yellow", children: "Search: " }), _jsx(Text, { color: "cyan", children: query || '…' })] }), _jsx(Text, { color: "yellow", children: "No results." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "Press Esc to exit." }) })] }));
|
|
41
40
|
}
|
|
42
41
|
const selectedRowIndex = Math.min(cursor, items.length - 1);
|
|
43
42
|
const totalRows = items.length;
|
|
@@ -45,9 +44,9 @@ export function SearchSelect({ title, allItems, filter, onSelect, onCancel, maxR
|
|
|
45
44
|
const windowStart = Math.min(Math.max(0, selectedRowIndex - Math.floor(effectiveMaxRows / 2)), Math.max(0, totalRows - effectiveMaxRows));
|
|
46
45
|
const windowEnd = Math.min(totalRows, windowStart + effectiveMaxRows);
|
|
47
46
|
const windowed = items.slice(windowStart, windowEnd);
|
|
48
|
-
return (_jsxs(Box, { flexDirection: "column", children: [title
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", padding: 1, children: [title ? (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: title }) })) : null, _jsxs(Box, { marginBottom: 1, flexDirection: "row", children: [_jsx(Text, { bold: true, color: "yellow", children: "Search: " }), _jsx(Text, { color: "green", children: query || '…' }), showCount ? (_jsxs(Text, { color: "cyan", children: [" \u00B7 ", items.length, " result", items.length !== 1 ? 's' : ''] })) : null] }), _jsx(Box, { flexDirection: "column", children: windowed.map((row, idx) => {
|
|
48
|
+
const absoluteIndex = windowStart + idx;
|
|
49
|
+
const selected = absoluteIndex === selectedRowIndex;
|
|
50
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: selected ? 'green' : undefined, bold: selected, children: [selected ? '› ' : ' ', row.label] }), row.hint ? _jsxs(Text, { color: "gray", children: [" ", row.hint] }) : null] }, `item-${absoluteIndex}`));
|
|
51
|
+
}) }), _jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsx(Text, { color: "yellow", children: "Type to filter" }), _jsx(Text, { color: "gray", children: " \u00B7 " }), _jsx(Text, { color: "yellow", children: "\u2191/\u2193 or j/k" }), _jsx(Text, { color: "gray", children: " \u00B7 " }), _jsx(Text, { color: "green", children: "Enter" }), _jsx(Text, { color: "gray", children: " to select \u00B7 " }), _jsx(Text, { color: "red", children: "Esc" }), _jsx(Text, { color: "gray", children: " to cancel" })] })] }));
|
|
53
52
|
}
|
package/dist/ui/select.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo, useState } from 'react';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
3
|
import { Box, Text, useInput } from 'ink';
|
|
5
4
|
const DEFAULT_MAX_ROWS = 18;
|
|
6
5
|
export function SelectList({ title, items, onSelect, onCancel, maxRows = DEFAULT_MAX_ROWS, }) {
|
|
@@ -31,7 +30,7 @@ export function SelectList({ title, items, onSelect, onCancel, maxRows = DEFAULT
|
|
|
31
30
|
}
|
|
32
31
|
});
|
|
33
32
|
if (items.length === 0) {
|
|
34
|
-
return (_jsxs(Box, { flexDirection: "column", children: [title
|
|
33
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", padding: 1, children: [title ? (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: title }) })) : null, _jsx(Text, { color: "yellow", children: "No results." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "Press Esc to exit." }) })] }));
|
|
35
34
|
}
|
|
36
35
|
const selectedRowIndex = selectableIndices[cursor] ?? 0;
|
|
37
36
|
const totalRows = items.length;
|
|
@@ -39,13 +38,12 @@ export function SelectList({ title, items, onSelect, onCancel, maxRows = DEFAULT
|
|
|
39
38
|
const windowStart = Math.min(Math.max(0, selectedRowIndex - Math.floor(effectiveMaxRows / 2)), Math.max(0, totalRows - effectiveMaxRows));
|
|
40
39
|
const windowEnd = Math.min(totalRows, windowStart + effectiveMaxRows);
|
|
41
40
|
const windowed = items.slice(windowStart, windowEnd);
|
|
42
|
-
return (_jsxs(Box, { flexDirection: "column", children: [title
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: chalk.dim('Use ↑/↓ or j/k. Enter to select. Esc to cancel.') }) })] }));
|
|
41
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", padding: 1, children: [title ? (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: title }) })) : null, _jsx(Box, { flexDirection: "column", children: windowed.map((row, idx) => {
|
|
42
|
+
const absoluteIndex = windowStart + idx;
|
|
43
|
+
const selected = row.type === 'item' && absoluteIndex === selectedRowIndex;
|
|
44
|
+
if (row.type === 'group') {
|
|
45
|
+
return (_jsx(Box, { marginTop: idx === 0 ? 0 : 1, children: _jsxs(Text, { bold: true, color: row.color ?? 'cyan', children: ["[", row.label, "]"] }) }, `group-${absoluteIndex}`));
|
|
46
|
+
}
|
|
47
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: selected ? 'green' : undefined, bold: selected, children: [selected ? '› ' : ' ', row.label] }), row.hint ? (_jsxs(Text, { color: "gray", children: [" ", row.hint] })) : null] }, `item-${absoluteIndex}`));
|
|
48
|
+
}) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "yellow", children: "\u2191/\u2193 or j/k" }), _jsx(Text, { color: "gray", children: " \u00B7 " }), _jsx(Text, { color: "green", children: "Enter" }), _jsx(Text, { color: "gray", children: " to select \u00B7 " }), _jsx(Text, { color: "red", children: "Esc" }), _jsx(Text, { color: "gray", children: " to cancel" })] })] }));
|
|
51
49
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bet-cli",
|
|
3
3
|
"description": "Explore and jump between local projects.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"author": "Chris Mckenzie",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"node": ">=20"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
+
"@types/marked-terminal": "^6.1.1",
|
|
26
27
|
"@types/object-hash": "^3.0.6",
|
|
27
28
|
"chalk": "^5.3.0",
|
|
28
29
|
"commander": "^12.0.0",
|
|
@@ -31,6 +32,8 @@
|
|
|
31
32
|
"ink": "^6.6.0",
|
|
32
33
|
"ink-markdown": "^1.0.4",
|
|
33
34
|
"ink-table": "^3.1.0",
|
|
35
|
+
"marked": "^17.0.3",
|
|
36
|
+
"marked-terminal": "^7.3.0",
|
|
34
37
|
"object-hash": "^3.0.0",
|
|
35
38
|
"react": "^19.2.4"
|
|
36
39
|
},
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { readConfig } from "../lib/config.js";
|
|
3
|
+
import { openProjectInEditor } from "../lib/editor.js";
|
|
4
|
+
import { findBySlug, listProjects, projectLabel } from "../lib/projects.js";
|
|
5
|
+
import { promptSelect } from "../ui/prompt.js";
|
|
6
|
+
import { SelectEntry } from "../ui/select.js";
|
|
7
|
+
|
|
8
|
+
export function registerEdit(program: Command): void {
|
|
9
|
+
program
|
|
10
|
+
.command("edit <slug>")
|
|
11
|
+
.description("Open a project in your editor")
|
|
12
|
+
.action(async (slug: string) => {
|
|
13
|
+
try {
|
|
14
|
+
const config = await readConfig();
|
|
15
|
+
const projects = listProjects(config);
|
|
16
|
+
const matches = findBySlug(projects, slug);
|
|
17
|
+
|
|
18
|
+
if (matches.length === 0) {
|
|
19
|
+
process.stderr.write(`No project found for slug "${slug}".\n`);
|
|
20
|
+
process.exitCode = 1;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let project = matches[0];
|
|
25
|
+
if (matches.length > 1) {
|
|
26
|
+
if (!process.stdin.isTTY) {
|
|
27
|
+
process.stderr.write(`Slug "${slug}" is ambiguous. Matches:\n`);
|
|
28
|
+
for (const item of matches) {
|
|
29
|
+
process.stderr.write(` ${projectLabel(item)} ${item.path}\n`);
|
|
30
|
+
}
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const items: SelectEntry<(typeof matches)[number]>[] = matches.map(
|
|
36
|
+
(item) => ({
|
|
37
|
+
label: projectLabel(item),
|
|
38
|
+
hint: item.path,
|
|
39
|
+
value: item,
|
|
40
|
+
type: "item",
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const selected = await promptSelect(items, { title: `Select ${slug}` });
|
|
45
|
+
if (!selected) return;
|
|
46
|
+
project = selected.value;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await openProjectInEditor(project.path, config.editor);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
52
|
+
process.stderr.write(`Error: ${message}\n`);
|
|
53
|
+
process.exitCode = 1;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
package/src/commands/ignore.ts
CHANGED
|
@@ -38,7 +38,7 @@ export function registerIgnore(program: Command): void {
|
|
|
38
38
|
const rootPaths = config.roots.map((r) => r.path);
|
|
39
39
|
if (!isPathUnderAnyRoot(normalized, rootPaths)) {
|
|
40
40
|
process.stderr.write(
|
|
41
|
-
`Error: Path must be under a configured root.\n Path: ${normalized}\n Roots: ${rootPaths.join(", ")}\n`,
|
|
41
|
+
`Error: Path must be under a configured root. Use an absolute path (e.g. /path/to/project), not a project slug.\n Path: ${normalized}\n Roots: ${rootPaths.join(", ")}\n`,
|
|
42
42
|
);
|
|
43
43
|
process.exitCode = 1;
|
|
44
44
|
return;
|
package/src/commands/info.tsx
CHANGED
|
@@ -1,168 +1,257 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import
|
|
3
|
+
import React from "react";
|
|
4
4
|
import { render, Box, Text } from "ink";
|
|
5
|
+
import { readConfig } from "../lib/config.js";
|
|
5
6
|
import { findBySlug, listProjects, projectLabel } from "../lib/projects.js";
|
|
6
7
|
import { getDirtyStatus, isInsideGitRepo } from "../lib/git.js";
|
|
7
8
|
import { formatDate } from "../utils/format.js";
|
|
8
9
|
import { promptSelect } from "../ui/prompt.js";
|
|
9
10
|
import { SelectEntry } from "../ui/select.js";
|
|
10
11
|
import { readReadmeContent } from "../lib/readme.js";
|
|
11
|
-
import
|
|
12
|
+
import Markdown from "../ui/markdown.js";
|
|
13
|
+
|
|
14
|
+
type MetaRowProps = {
|
|
15
|
+
label: string;
|
|
16
|
+
value: string;
|
|
17
|
+
valueColor?:
|
|
18
|
+
| "green"
|
|
19
|
+
| "red"
|
|
20
|
+
| "yellow"
|
|
21
|
+
| "blue"
|
|
22
|
+
| "cyan"
|
|
23
|
+
| "magenta"
|
|
24
|
+
| "gray";
|
|
25
|
+
};
|
|
12
26
|
|
|
13
|
-
const
|
|
27
|
+
const MetaRow: React.FC<MetaRowProps> = ({ label, value, valueColor }) => (
|
|
28
|
+
<Box>
|
|
29
|
+
<Text bold color="gray">{`${label}: `}</Text>
|
|
30
|
+
<Text color={valueColor} bold={!!valueColor}>
|
|
31
|
+
{value}
|
|
32
|
+
</Text>
|
|
33
|
+
</Box>
|
|
34
|
+
);
|
|
14
35
|
|
|
15
36
|
export function registerInfo(program: Command): void {
|
|
16
37
|
program
|
|
17
38
|
.command("info <slug>")
|
|
18
39
|
.description("Show project details")
|
|
19
40
|
.option("--json", "Print JSON output")
|
|
20
|
-
.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let project = matches[0];
|
|
32
|
-
|
|
33
|
-
if (matches.length > 1) {
|
|
34
|
-
if (!process.stdin.isTTY) {
|
|
35
|
-
process.stderr.write(`Slug "${slug}" is ambiguous. Matches:\n`);
|
|
36
|
-
for (const item of matches) {
|
|
37
|
-
process.stderr.write(` ${projectLabel(item)} ${item.path}\n`);
|
|
38
|
-
}
|
|
41
|
+
.option("--full", "Show full README content")
|
|
42
|
+
.action(
|
|
43
|
+
async (slug: string, options: { json?: boolean; full?: boolean }) => {
|
|
44
|
+
const config = await readConfig();
|
|
45
|
+
const projects = listProjects(config);
|
|
46
|
+
const matches = findBySlug(projects, slug);
|
|
47
|
+
|
|
48
|
+
if (matches.length === 0) {
|
|
49
|
+
process.stderr.write(`No project found for slug "${slug}".\n`);
|
|
39
50
|
process.exitCode = 1;
|
|
40
51
|
return;
|
|
41
52
|
}
|
|
42
53
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
let project = matches[0];
|
|
55
|
+
|
|
56
|
+
if (matches.length > 1) {
|
|
57
|
+
if (!process.stdin.isTTY) {
|
|
58
|
+
process.stderr.write(`Slug "${slug}" is ambiguous. Matches:\n`);
|
|
59
|
+
for (const item of matches) {
|
|
60
|
+
process.stderr.write(` ${projectLabel(item)} ${item.path}\n`);
|
|
61
|
+
}
|
|
62
|
+
process.exitCode = 1;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const items: SelectEntry<(typeof matches)[number]>[] = matches.map(
|
|
67
|
+
(item) => ({
|
|
68
|
+
label: projectLabel(item),
|
|
69
|
+
hint: item.path,
|
|
70
|
+
value: item,
|
|
71
|
+
type: "item",
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
51
74
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (options.json) {
|
|
58
|
-
process.stdout.write(JSON.stringify(project, null, 2));
|
|
59
|
-
process.stdout.write("\n");
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const description =
|
|
64
|
-
project.user?.description ?? project.auto.description ?? "—";
|
|
65
|
-
// Compute git status live
|
|
66
|
-
const hasGit = await isInsideGitRepo(project.path);
|
|
67
|
-
const dirty = hasGit ? await getDirtyStatus(project.path) : undefined;
|
|
68
|
-
|
|
69
|
-
if (process.stdin.isTTY) {
|
|
70
|
-
const readme = await readReadmeContent(project.path);
|
|
71
|
-
const markdown = readme ?? description;
|
|
72
|
-
|
|
73
|
-
let Markdown: React.FC<{ children: string }> | null = null;
|
|
74
|
-
try {
|
|
75
|
-
const markdownModule = await import("ink-markdown");
|
|
76
|
-
Markdown = (markdownModule.default ??
|
|
77
|
-
markdownModule) as unknown as React.FC<{
|
|
78
|
-
children: string;
|
|
79
|
-
}>;
|
|
80
|
-
} catch {
|
|
81
|
-
Markdown = null;
|
|
75
|
+
const selected = await promptSelect(items, {
|
|
76
|
+
title: `Select ${slug}`,
|
|
77
|
+
});
|
|
78
|
+
if (!selected) return;
|
|
79
|
+
project = selected.value;
|
|
82
80
|
}
|
|
83
81
|
|
|
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
|
-
|
|
82
|
+
if (options.json) {
|
|
83
|
+
process.stdout.write(JSON.stringify(project, null, 2));
|
|
84
|
+
process.stdout.write("\n");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const description =
|
|
89
|
+
project.user?.description ?? project.auto.description ?? "—";
|
|
90
|
+
// Compute git status live
|
|
91
|
+
const hasGit = await isInsideGitRepo(project.path);
|
|
92
|
+
const dirty = hasGit ? await getDirtyStatus(project.path) : undefined;
|
|
93
|
+
|
|
94
|
+
if (process.stdin.isTTY) {
|
|
95
|
+
const readme = options.full
|
|
96
|
+
? await readReadmeContent(project.path, { full: true })
|
|
97
|
+
: null;
|
|
98
|
+
const markdown = readme ?? description;
|
|
99
|
+
|
|
100
|
+
const view = (
|
|
101
|
+
<Box flexDirection="column" width="100%">
|
|
102
|
+
<Box
|
|
103
|
+
width="100%"
|
|
104
|
+
borderStyle="single"
|
|
105
|
+
borderColor="green"
|
|
106
|
+
paddingX={1}
|
|
107
|
+
paddingY={1}
|
|
108
|
+
marginBottom={1}
|
|
109
|
+
flexDirection="column"
|
|
110
|
+
>
|
|
111
|
+
<Box width="100%" paddingBottom={1}>
|
|
112
|
+
<Text color="green" bold>
|
|
113
|
+
{project.slug}
|
|
114
|
+
</Text>
|
|
115
|
+
</Box>
|
|
116
|
+
<Box width="100%">
|
|
117
|
+
<Text color="cyan">{project.path}</Text>
|
|
118
|
+
</Box>
|
|
119
|
+
</Box>
|
|
120
|
+
<Box
|
|
121
|
+
borderStyle="round"
|
|
122
|
+
borderColor="cyan"
|
|
123
|
+
padding={1}
|
|
124
|
+
flexDirection="column"
|
|
125
|
+
marginBottom={1}
|
|
126
|
+
>
|
|
127
|
+
<Box marginBottom={1}>
|
|
128
|
+
<Text bold color="magenta">
|
|
129
|
+
Details
|
|
130
|
+
</Text>
|
|
131
|
+
</Box>
|
|
132
|
+
<Box flexDirection="column">
|
|
133
|
+
<MetaRow
|
|
134
|
+
label="Git"
|
|
135
|
+
value={hasGit ? "yes" : "no"}
|
|
136
|
+
valueColor={hasGit ? "green" : "yellow"}
|
|
137
|
+
/>
|
|
138
|
+
<MetaRow
|
|
139
|
+
label="Git dirty"
|
|
140
|
+
value={
|
|
141
|
+
dirty === undefined ? "unknown" : dirty ? "yes" : "no"
|
|
142
|
+
}
|
|
143
|
+
valueColor={
|
|
144
|
+
dirty === undefined ? "yellow" : dirty ? "red" : "green"
|
|
145
|
+
}
|
|
146
|
+
/>
|
|
147
|
+
<MetaRow
|
|
148
|
+
label="README"
|
|
149
|
+
value={project.hasReadme ? "yes" : "no"}
|
|
150
|
+
valueColor={project.hasReadme ? "green" : "yellow"}
|
|
151
|
+
/>
|
|
152
|
+
<MetaRow
|
|
153
|
+
label="Started"
|
|
154
|
+
value={formatDate(project.auto.startedAt)}
|
|
155
|
+
/>
|
|
156
|
+
<MetaRow
|
|
157
|
+
label="Last modified"
|
|
158
|
+
value={formatDate(project.auto.lastModifiedAt)}
|
|
159
|
+
/>
|
|
160
|
+
<MetaRow
|
|
161
|
+
label="Last indexed"
|
|
162
|
+
value={formatDate(project.auto.lastIndexedAt)}
|
|
163
|
+
/>
|
|
164
|
+
|
|
165
|
+
<MetaRow label="Root" value={project.rootName} />
|
|
166
|
+
<MetaRow label="Root path" value={project.root} />
|
|
167
|
+
{project.user?.tags?.length ? (
|
|
168
|
+
<Box>
|
|
169
|
+
<Text bold color="gray">{`Tags: `}</Text>
|
|
170
|
+
<Text color="magenta">
|
|
171
|
+
{project.user.tags.join(", ")}
|
|
172
|
+
</Text>
|
|
173
|
+
</Box>
|
|
174
|
+
) : null}
|
|
175
|
+
{project.user?.onEnter ? (
|
|
176
|
+
<Box>
|
|
177
|
+
<Text bold color="gray">{`On enter: `}</Text>
|
|
178
|
+
<Text color="blue">{project.user.onEnter}</Text>
|
|
179
|
+
</Box>
|
|
180
|
+
) : null}
|
|
181
|
+
</Box>
|
|
182
|
+
</Box>
|
|
183
|
+
<Box
|
|
184
|
+
borderStyle="round"
|
|
185
|
+
borderColor="magenta"
|
|
186
|
+
padding={1}
|
|
187
|
+
flexDirection="column"
|
|
188
|
+
>
|
|
189
|
+
<Box marginBottom={1}>
|
|
190
|
+
<Text bold color="magenta">
|
|
191
|
+
Description
|
|
192
|
+
</Text>
|
|
193
|
+
</Box>
|
|
118
194
|
<Markdown>{markdown}</Markdown>
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
195
|
+
</Box>
|
|
196
|
+
{!options.full && project.hasReadme ? (
|
|
197
|
+
<Box marginTop={1}>
|
|
198
|
+
<Text color="yellow">
|
|
199
|
+
Tip: Run <Text bold>bet info {project.slug} --full</Text> to
|
|
200
|
+
read the full README.
|
|
201
|
+
</Text>
|
|
202
|
+
</Box>
|
|
203
|
+
) : null}
|
|
122
204
|
</Box>
|
|
123
|
-
|
|
124
|
-
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const { unmount } = render(view, { stdout: process.stdout });
|
|
208
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
209
|
+
unmount();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
125
212
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
process.stdout.write(`${chalk.bold("Root:")} ${project.rootName}\n`);
|
|
136
|
-
process.stdout.write(`${chalk.bold("Root path:")} ${project.root}\n`);
|
|
137
|
-
process.stdout.write(`${chalk.bold("Git:")} ${hasGit ? "yes" : "no"}\n`);
|
|
138
|
-
process.stdout.write(
|
|
139
|
-
`${chalk.bold("README:")} ${project.hasReadme ? "yes" : "no"}\n\n`,
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
process.stdout.write(`${chalk.bold("Description:")} ${description}\n`);
|
|
143
|
-
process.stdout.write(
|
|
144
|
-
`${chalk.bold("Started:")} ${formatDate(project.auto.startedAt)}\n`,
|
|
145
|
-
);
|
|
146
|
-
process.stdout.write(
|
|
147
|
-
`${chalk.bold("Last modified:")} ${formatDate(project.auto.lastModifiedAt)}\n`,
|
|
148
|
-
);
|
|
149
|
-
process.stdout.write(
|
|
150
|
-
`${chalk.bold("Last indexed:")} ${formatDate(project.auto.lastIndexedAt)}\n`,
|
|
151
|
-
);
|
|
152
|
-
process.stdout.write(
|
|
153
|
-
`${chalk.bold("Dirty:")} ${dirty === undefined ? "unknown" : dirty ? "yes" : "no"}\n`,
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
if (project.user?.tags?.length) {
|
|
213
|
+
process.stdout.write(`${chalk.bold(project.slug)}\n`);
|
|
214
|
+
process.stdout.write(`${chalk.dim(project.path)}\n\n`);
|
|
215
|
+
|
|
216
|
+
process.stdout.write(`${chalk.bold("Root:")} ${project.rootName}\n`);
|
|
217
|
+
process.stdout.write(`${chalk.bold("Root path:")} ${project.root}\n`);
|
|
218
|
+
process.stdout.write(
|
|
219
|
+
`${chalk.bold("Git:")} ${hasGit ? "yes" : "no"}\n`,
|
|
220
|
+
);
|
|
157
221
|
process.stdout.write(
|
|
158
|
-
`${chalk.bold("
|
|
222
|
+
`${chalk.bold("README:")} ${project.hasReadme ? "yes" : "no"}\n\n`,
|
|
159
223
|
);
|
|
160
|
-
}
|
|
161
224
|
|
|
162
|
-
|
|
225
|
+
const descToShow =
|
|
226
|
+
options.full && project.hasReadme
|
|
227
|
+
? ((await readReadmeContent(project.path, { full: true })) ??
|
|
228
|
+
description)
|
|
229
|
+
: description;
|
|
230
|
+
process.stdout.write(`${chalk.bold("Description:")} ${descToShow}\n`);
|
|
231
|
+
process.stdout.write(
|
|
232
|
+
`${chalk.bold("Started:")} ${formatDate(project.auto.startedAt)}\n`,
|
|
233
|
+
);
|
|
234
|
+
process.stdout.write(
|
|
235
|
+
`${chalk.bold("Last modified:")} ${formatDate(project.auto.lastModifiedAt)}\n`,
|
|
236
|
+
);
|
|
237
|
+
process.stdout.write(
|
|
238
|
+
`${chalk.bold("Last indexed:")} ${formatDate(project.auto.lastIndexedAt)}\n`,
|
|
239
|
+
);
|
|
163
240
|
process.stdout.write(
|
|
164
|
-
`${chalk.bold("
|
|
241
|
+
`${chalk.bold("Dirty:")} ${dirty === undefined ? "unknown" : dirty ? "yes" : "no"}\n`,
|
|
165
242
|
);
|
|
166
|
-
|
|
167
|
-
|
|
243
|
+
|
|
244
|
+
if (project.user?.tags?.length) {
|
|
245
|
+
process.stdout.write(
|
|
246
|
+
`${chalk.bold("Tags:")} ${project.user.tags.join(", ")}\n`,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (project.user?.onEnter) {
|
|
251
|
+
process.stdout.write(
|
|
252
|
+
`${chalk.bold("On enter:")} ${project.user.onEnter}\n`,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
);
|
|
168
257
|
}
|