gh-coderakr 0.0.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/dist/app.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ type Props = {
3
+ repo?: string;
4
+ };
5
+ export default function App({ repo }: Props): React.JSX.Element;
6
+ export {};
package/dist/app.js ADDED
@@ -0,0 +1,103 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { Box, Text, useInput } from 'ink';
3
+ import TextInput from 'ink-text-input';
4
+ import fetch from 'node-fetch';
5
+ import { AppLayout } from './components/layout.js';
6
+ import { Spinner } from './components/spinner.js';
7
+ import { ErrorMessage } from './components/error-message.js';
8
+ import { RepoDetails } from './components/repo-details.js';
9
+ import { normalizeRepoInput } from './github.js';
10
+ export default function App({ repo }) {
11
+ const initialTarget = repo ? normalizeRepoInput(repo) : '';
12
+ const [input, setInput] = useState('');
13
+ const [targetRepo, setTargetRepo] = useState(initialTarget);
14
+ const [data, setData] = useState();
15
+ const [error, setError] = useState();
16
+ const [loading, setLoading] = useState(false);
17
+ const [reloadToken, setReloadToken] = useState(0);
18
+ useEffect(() => {
19
+ if (!targetRepo || targetRepo.trim() === '') {
20
+ setLoading(false);
21
+ setData(undefined);
22
+ return;
23
+ }
24
+ let didCancel = false;
25
+ async function load() {
26
+ setLoading(true);
27
+ setError(undefined);
28
+ try {
29
+ const response = await fetch(`https://api.github.com/repos/${targetRepo}`, {
30
+ headers: {
31
+ Accept: 'application/vnd.github+json',
32
+ 'User-Agent': 'inkcli',
33
+ },
34
+ });
35
+ if (!response.ok) {
36
+ const message = response.status === 404 ? 'Repository not found.' : `GitHub API error: ${response.status} ${response.statusText}`;
37
+ if (!didCancel) {
38
+ setError(message);
39
+ setData(undefined);
40
+ }
41
+ return;
42
+ }
43
+ const json = (await response.json());
44
+ if (!didCancel) {
45
+ setData(json);
46
+ }
47
+ }
48
+ catch (error_) {
49
+ if (!didCancel) {
50
+ setError(error_ instanceof Error ? error_.message : 'Unknown error while fetching repository.');
51
+ setData(undefined);
52
+ }
53
+ }
54
+ finally {
55
+ if (!didCancel) {
56
+ setLoading(false);
57
+ }
58
+ }
59
+ }
60
+ load();
61
+ return () => {
62
+ didCancel = true;
63
+ };
64
+ }, [targetRepo, reloadToken]);
65
+ const handleSubmit = (value) => {
66
+ const raw = value || input;
67
+ const normalized = normalizeRepoInput(raw);
68
+ if (!normalized) {
69
+ setError('Please enter a GitHub URL or owner/repo.');
70
+ setData(undefined);
71
+ return;
72
+ }
73
+ setError(undefined);
74
+ setTargetRepo(normalized);
75
+ };
76
+ useInput((inputKey, key) => {
77
+ if (key.escape || inputKey === 'q') {
78
+ process.exit(0);
79
+ }
80
+ if (inputKey === 'r') {
81
+ if (targetRepo && targetRepo.trim() !== '') {
82
+ setReloadToken(previous => previous + 1);
83
+ }
84
+ }
85
+ });
86
+ return (React.createElement(AppLayout, { title: "GitHub Repo Viewer", subtitle: "Enter URL or owner/repo, then press Enter", footer: React.createElement(Text, { color: "gray" }, "Keys: Enter=load r=reload q/Esc=quit Ctrl+C=force quit") },
87
+ React.createElement(Box, { marginBottom: 1 },
88
+ React.createElement(Text, { color: "gray" },
89
+ "Input:",
90
+ ' '),
91
+ React.createElement(TextInput, { value: input, onChange: setInput, onSubmit: handleSubmit, showCursor: false, placeholder: "owner/repo or GitHub URL" })),
92
+ targetRepo && targetRepo.trim() !== '' && (React.createElement(Box, { marginBottom: 1 },
93
+ React.createElement(Text, { color: "gray" },
94
+ "Current:",
95
+ ' '),
96
+ React.createElement(Text, { color: "greenBright" }, targetRepo))),
97
+ React.createElement(Box, { flexDirection: "column", flexGrow: 1 },
98
+ loading && (React.createElement(Box, { marginBottom: 1 },
99
+ React.createElement(Spinner, { label: `Fetching ${targetRepo} from GitHub...` }))),
100
+ error && React.createElement(ErrorMessage, { targetRepo: targetRepo, error: error }),
101
+ !loading && !error && data && React.createElement(RepoDetails, { repo: data }),
102
+ !loading && !error && !data && (React.createElement(Text, { color: "gray" }, "Paste a GitHub URL or owner/repo above and press Enter to load details.")))));
103
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import React from 'react';
3
+ import { render } from 'ink';
4
+ import meow from 'meow';
5
+ import App from './app.js';
6
+ const cli = meow(`
7
+ Usage
8
+ $ inkcli <owner>/<repo>|<github-url>
9
+
10
+ Examples
11
+ $ inkcli kubernetes/kubernetes
12
+ $ inkcli coderakr/CarRental
13
+ $ inkcli https://github.com/kubernetes/kubernetes
14
+ $ inkcli https://api.github.com/repos/kubernetes/kubernetes
15
+
16
+ If no argument is provided, it defaults to kubernetes/kubernetes.
17
+ `, {
18
+ importMeta: import.meta,
19
+ });
20
+ const [repo] = cli.input;
21
+ render(React.createElement(App, { repo: repo }));
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ type ErrorMessageProps = {
3
+ targetRepo?: string;
4
+ error: string;
5
+ };
6
+ export declare function ErrorMessage({ targetRepo, error }: ErrorMessageProps): React.JSX.Element;
7
+ export {};
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ export function ErrorMessage({ targetRepo, error }) {
4
+ return (React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
5
+ React.createElement(Text, { color: "redBright" }, "\u2716 Failed to load repository"),
6
+ targetRepo && (React.createElement(Text, { color: "gray" },
7
+ "Target: ",
8
+ targetRepo)),
9
+ React.createElement(Text, { color: "red" }, error)));
10
+ }
@@ -0,0 +1,9 @@
1
+ import React, { type ReactNode } from 'react';
2
+ type AppLayoutProps = {
3
+ title: string;
4
+ subtitle?: string;
5
+ footer?: ReactNode;
6
+ children: ReactNode;
7
+ };
8
+ export declare function AppLayout({ title, subtitle, footer, children }: AppLayoutProps): React.JSX.Element;
9
+ export {};
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ export function AppLayout({ title, subtitle, footer, children }) {
4
+ return (React.createElement(Box, { paddingX: 1, paddingY: 1 },
5
+ React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyanBright", paddingX: 2, paddingY: 1 },
6
+ React.createElement(Box, { marginBottom: 1, justifyContent: "space-between" },
7
+ React.createElement(Text, { color: "cyanBright" }, title),
8
+ subtitle && (React.createElement(Text, { color: "gray" }, subtitle))),
9
+ React.createElement(Box, { flexDirection: "column" }, children),
10
+ footer && (React.createElement(Box, { marginTop: 1 }, footer)))));
11
+ }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import type { GithubRepo } from '../github.js';
3
+ type RepoDetailsProps = {
4
+ repo: GithubRepo;
5
+ };
6
+ export declare function RepoDetails({ repo }: RepoDetailsProps): React.JSX.Element;
7
+ export {};
@@ -0,0 +1,86 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ export function RepoDetails({ repo }) {
4
+ const createdAt = new Date(repo.created_at);
5
+ const updatedAt = new Date(repo.updated_at);
6
+ return (React.createElement(Box, { flexDirection: "column" },
7
+ React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
8
+ React.createElement(Text, null,
9
+ React.createElement(Text, { color: "gray" },
10
+ "Name:",
11
+ ' '),
12
+ React.createElement(Text, { color: "greenBright" }, repo.full_name)),
13
+ React.createElement(Text, null,
14
+ React.createElement(Text, { color: "gray" },
15
+ "Owner:",
16
+ ' '),
17
+ React.createElement(Text, { color: "magentaBright" }, repo.owner.login)),
18
+ repo.description && (React.createElement(Text, { wrap: "truncate" },
19
+ React.createElement(Text, { color: "gray" },
20
+ "Description:",
21
+ ' '),
22
+ repo.description))),
23
+ React.createElement(Box, { flexDirection: "row", marginBottom: 1, flexWrap: "wrap" },
24
+ React.createElement(Box, { flexDirection: "column", marginRight: 4 },
25
+ React.createElement(Text, { color: "yellowBright" }, "\u2605 Stars:"),
26
+ React.createElement(Text, null, repo.stargazers_count)),
27
+ React.createElement(Box, { flexDirection: "column", marginRight: 4 },
28
+ React.createElement(Text, { color: "cyanBright" }, "\u2442 Forks:"),
29
+ React.createElement(Text, null, repo.forks_count)),
30
+ React.createElement(Box, { flexDirection: "column", marginRight: 4 },
31
+ React.createElement(Text, { color: "redBright" }, "\u26A0 Issues:"),
32
+ React.createElement(Text, null, repo.open_issues_count)),
33
+ React.createElement(Box, { flexDirection: "column" },
34
+ React.createElement(Text, { color: "blueBright" }, "\uD83D\uDC41 Watchers:"),
35
+ React.createElement(Text, null, repo.watchers_count))),
36
+ React.createElement(Box, { flexDirection: "row", marginBottom: 1, flexWrap: "wrap" },
37
+ React.createElement(Box, { marginRight: 4 },
38
+ React.createElement(Text, null,
39
+ React.createElement(Text, { color: "gray" },
40
+ "Language:",
41
+ ' '),
42
+ repo.language ?? 'N/A')),
43
+ React.createElement(Box, { marginRight: 4 },
44
+ React.createElement(Text, null,
45
+ React.createElement(Text, { color: "gray" },
46
+ "License:",
47
+ ' '),
48
+ repo.license?.name ?? 'Unlicensed')),
49
+ React.createElement(Box, null,
50
+ React.createElement(Text, null,
51
+ React.createElement(Text, { color: "gray" },
52
+ "Default branch:",
53
+ ' '),
54
+ repo.default_branch))),
55
+ React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
56
+ React.createElement(Text, null,
57
+ React.createElement(Text, { color: "gray" },
58
+ "Created:",
59
+ ' '),
60
+ createdAt.toLocaleString()),
61
+ React.createElement(Text, null,
62
+ React.createElement(Text, { color: "gray" },
63
+ "Last updated:",
64
+ ' '),
65
+ updatedAt.toLocaleString())),
66
+ React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
67
+ React.createElement(Text, null,
68
+ React.createElement(Text, { color: "gray" },
69
+ "GitHub:",
70
+ ' '),
71
+ React.createElement(Text, { color: "blueBright" }, repo.html_url)),
72
+ repo.homepage && (React.createElement(Text, null,
73
+ React.createElement(Text, { color: "gray" },
74
+ "Homepage:",
75
+ ' '),
76
+ React.createElement(Text, { color: "greenBright" }, repo.homepage)))),
77
+ repo.topics && repo.topics.length > 0 && (React.createElement(Box, { flexDirection: "row", flexWrap: "wrap" },
78
+ React.createElement(Text, { color: "gray" },
79
+ "Topics:",
80
+ ' '),
81
+ React.createElement(Box, { marginLeft: 1, flexDirection: "row", flexWrap: "wrap" }, repo.topics.map(topic => (React.createElement(Box, { key: topic, marginRight: 1 },
82
+ React.createElement(Text, { color: "black", backgroundColor: "gray" },
83
+ ' ',
84
+ topic,
85
+ ' ')))))))));
86
+ }
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ type SpinnerProps = {
3
+ label?: string;
4
+ };
5
+ export declare function Spinner({ label }: SpinnerProps): React.JSX.Element;
6
+ export {};
@@ -0,0 +1,19 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { Box, Text } from 'ink';
3
+ const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
4
+ export function Spinner({ label }) {
5
+ const [frame, setFrame] = useState(0);
6
+ useEffect(() => {
7
+ const timer = setInterval(() => {
8
+ setFrame(previous => (previous + 1) % SPINNER_FRAMES.length);
9
+ }, 80);
10
+ return () => {
11
+ clearInterval(timer);
12
+ };
13
+ }, []);
14
+ return (React.createElement(Box, null,
15
+ React.createElement(Text, { color: "cyan" },
16
+ SPINNER_FRAMES[frame],
17
+ " ",
18
+ label)));
19
+ }
@@ -0,0 +1,22 @@
1
+ export type GithubRepo = {
2
+ full_name: string;
3
+ description: string | null;
4
+ html_url: string;
5
+ homepage: string | null;
6
+ language: string | null;
7
+ stargazers_count: number;
8
+ forks_count: number;
9
+ open_issues_count: number;
10
+ watchers_count: number;
11
+ created_at: string;
12
+ updated_at: string;
13
+ default_branch: string;
14
+ license: {
15
+ name: string;
16
+ } | null;
17
+ owner: {
18
+ login: string;
19
+ };
20
+ topics?: string[];
21
+ };
22
+ export declare function normalizeRepoInput(raw?: string): string;
package/dist/github.js ADDED
@@ -0,0 +1,29 @@
1
+ export function normalizeRepoInput(raw) {
2
+ if (!raw) {
3
+ return '';
4
+ }
5
+ const input = raw.trim();
6
+ if (input === '') {
7
+ return '';
8
+ }
9
+ if (input.startsWith('http://') || input.startsWith('https://')) {
10
+ try {
11
+ const url = new URL(input);
12
+ const host = url.hostname.toLowerCase();
13
+ const segments = url.pathname.split('/').filter(Boolean);
14
+ if (host === 'github.com' && segments.length >= 2) {
15
+ return `${segments[0]}/${segments[1]}`;
16
+ }
17
+ if (host === 'api.github.com') {
18
+ const startIndex = segments[0] === 'repos' ? 1 : 0;
19
+ if (segments.length >= startIndex + 2) {
20
+ return `${segments[startIndex]}/${segments[startIndex + 1]}`;
21
+ }
22
+ }
23
+ }
24
+ catch {
25
+ // Ignore malformed URLs and fall through.
26
+ }
27
+ }
28
+ return input;
29
+ }
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "description": "Beautiful GitHub repo CLI built with Ink",
3
+ "author": "Ayush Kumar",
4
+ "keywords": [
5
+ "cli",
6
+ "ink",
7
+ "github",
8
+ "tui"
9
+ ],
10
+ "name": "gh-coderakr",
11
+ "version": "0.0.0",
12
+ "license": "MIT",
13
+ "bin": "dist/cli.js",
14
+ "type": "module",
15
+ "engines": {
16
+ "node": ">=16"
17
+ },
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "dev": "tsc --watch",
21
+ "test": "prettier --check . && xo && ava"
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "dependencies": {
27
+ "ink": "^4.1.0",
28
+ "meow": "^11.0.0",
29
+ "react": "^18.2.0",
30
+ "node-fetch": "^3.3.2",
31
+ "ink-text-input": "^5.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "@sindresorhus/tsconfig": "^3.0.1",
35
+ "@types/react": "^18.0.32",
36
+ "@vdemedes/prettier-config": "^2.0.1",
37
+ "ava": "^5.2.0",
38
+ "chalk": "^5.2.0",
39
+ "eslint-config-xo-react": "^0.27.0",
40
+ "eslint-plugin-react": "^7.32.2",
41
+ "eslint-plugin-react-hooks": "^4.6.0",
42
+ "ink-testing-library": "^3.0.0",
43
+ "prettier": "^2.8.7",
44
+ "ts-node": "^10.9.1",
45
+ "typescript": "^5.0.3",
46
+ "xo": "^0.53.1"
47
+ },
48
+ "ava": {
49
+ "extensions": {
50
+ "ts": "module",
51
+ "tsx": "module"
52
+ },
53
+ "nodeArguments": [
54
+ "--loader=ts-node/esm"
55
+ ]
56
+ },
57
+ "xo": {
58
+ "extends": "xo-react",
59
+ "prettier": true,
60
+ "rules": {
61
+ "react/prop-types": "off"
62
+ }
63
+ },
64
+ "prettier": "@vdemedes/prettier-config"
65
+ }
package/readme.md ADDED
@@ -0,0 +1,25 @@
1
+ # inkcli
2
+
3
+ > This readme is automatically generated by [create-ink-app](https://github.com/vadimdemedes/create-ink-app)
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ $ npm install --global inkcli
9
+ ```
10
+
11
+ ## CLI
12
+
13
+ ```
14
+ $ inkcli --help
15
+
16
+ Usage
17
+ $ inkcli
18
+
19
+ Options
20
+ --name Your name
21
+
22
+ Examples
23
+ $ inkcli --name=Jane
24
+ Hello, Jane
25
+ ```