trident-git 0.2.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 +198 -0
- package/bin/trident-git.mjs +153 -0
- package/eslint.config.mjs +18 -0
- package/next.config.ts +30 -0
- package/package.json +60 -0
- package/postcss.config.mjs +7 -0
- package/public/favicon.png +0 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/app/api/credentials/route.ts +113 -0
- package/src/app/api/custom-scripts/route.ts +203 -0
- package/src/app/api/fs/route.ts +75 -0
- package/src/app/api/git/action/route.ts +383 -0
- package/src/app/api/git/branches/route.ts +20 -0
- package/src/app/api/git/diff/route.ts +104 -0
- package/src/app/api/git/log/route.ts +28 -0
- package/src/app/api/git/status/route.ts +28 -0
- package/src/app/api/repos/route.ts +84 -0
- package/src/app/api/settings/route.ts +37 -0
- package/src/app/credentials/page.tsx +408 -0
- package/src/app/globals.css +109 -0
- package/src/app/icon.png +0 -0
- package/src/app/layout.tsx +38 -0
- package/src/app/page.tsx +10 -0
- package/src/app/providers.tsx +21 -0
- package/src/app/workspace/changes/page.tsx +27 -0
- package/src/app/workspace/custom-scripts/page.tsx +247 -0
- package/src/app/workspace/history/page.tsx +27 -0
- package/src/app/workspace/layout.tsx +26 -0
- package/src/app/workspace/page.tsx +27 -0
- package/src/app/workspace/settings/page.tsx +233 -0
- package/src/app/workspace/stashes/page.tsx +395 -0
- package/src/components/command-palette.tsx +178 -0
- package/src/components/context-menu.tsx +200 -0
- package/src/components/fs-browser.tsx +154 -0
- package/src/components/git/diff-view.tsx +137 -0
- package/src/components/git/git-graph.tsx +489 -0
- package/src/components/git/grouped-diff-viewer.tsx +332 -0
- package/src/components/git/history-view.tsx +4862 -0
- package/src/components/git/image-diff-view.tsx +342 -0
- package/src/components/git/status-view.tsx +597 -0
- package/src/components/home-settings-modal.tsx +192 -0
- package/src/components/layout/sidebar.tsx +256 -0
- package/src/components/repo-list.tsx +206 -0
- package/src/components/theme-toggle.tsx +37 -0
- package/src/components/toaster.tsx +36 -0
- package/src/components/workspace-repo-open-tracker.tsx +39 -0
- package/src/hooks/use-credentials.ts +123 -0
- package/src/hooks/use-escape-dismiss.ts +72 -0
- package/src/hooks/use-git.ts +448 -0
- package/src/hooks/use-toast.ts +280 -0
- package/src/hooks/use-workspace-title.ts +23 -0
- package/src/lib/api-utils.ts +24 -0
- package/src/lib/branch-colors.ts +98 -0
- package/src/lib/credentials.ts +404 -0
- package/src/lib/git.ts +1510 -0
- package/src/lib/graph-utils.ts +253 -0
- package/src/lib/store.ts +145 -0
- package/src/lib/types.ts +95 -0
- package/src/lib/utils.ts +266 -0
- package/tsconfig.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# 🔱 Trident
|
|
2
|
+
|
|
3
|
+
A modern, web-based Git client built with Next.js. Manage your repositories, view commit history with a visual graph, and perform common Git operations through an intuitive interface.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Repository Management** - Add, delete, and manage multiple local Git repositories
|
|
10
|
+
- **Visual Commit Graph** - Fork-style commit visualization with branch lanes
|
|
11
|
+
- **Branch Operations** - Create, checkout, delete, rename, rebase, and merge branches
|
|
12
|
+
- **Change Staging** - Stage/unstage files individually or all at once
|
|
13
|
+
- **Stash Support** - Stash, reapply, and delete stashed changes
|
|
14
|
+
- **Diff Viewer** - Syntax-highlighted diff view for reviewing changes
|
|
15
|
+
- **Commit History** - Browse commit history with infinite scroll and branch filtering
|
|
16
|
+
- **Command Palette** - Quick access to common actions via Cmd/Ctrl+K
|
|
17
|
+
- **Settings** - Configure default repository folder and preferences
|
|
18
|
+
- **Dark/Light Mode** - Theme toggle for comfortable viewing
|
|
19
|
+
|
|
20
|
+
## Roadmap & TODO
|
|
21
|
+
|
|
22
|
+
### Implemented
|
|
23
|
+
|
|
24
|
+
#### Repository Management
|
|
25
|
+
- [x] Open local repositories from a filesystem browser
|
|
26
|
+
- [x] List recent repositories and reopen quickly (Command Palette)
|
|
27
|
+
- [x] Delete repositories from the app list (without deleting files)
|
|
28
|
+
- [x] Shared repository/settings storage across app instances
|
|
29
|
+
- [x] Set repository display name
|
|
30
|
+
- [x] Configure default root folder for browsing repositories
|
|
31
|
+
|
|
32
|
+
#### Working Directory & Staging
|
|
33
|
+
- [x] View working tree and index status
|
|
34
|
+
- [x] Stage/unstage individual files
|
|
35
|
+
- [x] Stage all / unstage all
|
|
36
|
+
- [x] Discard unstaged changes and untracked files
|
|
37
|
+
- [x] Stash changes with message
|
|
38
|
+
- [x] List/apply/pop/drop stashes
|
|
39
|
+
- [x] Inspect stash file lists and per-file stash diffs
|
|
40
|
+
|
|
41
|
+
#### Committing
|
|
42
|
+
- [x] Commit with subject + optional body
|
|
43
|
+
- [x] Amend latest commit message (reword)
|
|
44
|
+
|
|
45
|
+
#### Branching, History & Graph
|
|
46
|
+
- [x] Visual commit graph with branch lanes
|
|
47
|
+
- [x] Branch visibility filters persisted per repository
|
|
48
|
+
- [x] List local/remote branches with ahead/behind tracking info
|
|
49
|
+
- [x] Create branch from current HEAD or selected ref
|
|
50
|
+
- [x] Checkout local branch
|
|
51
|
+
- [x] Checkout remote branch to new local tracking branch
|
|
52
|
+
- [x] Delete local and remote branches
|
|
53
|
+
- [x] Rename local branches
|
|
54
|
+
- [x] Rename remote branches
|
|
55
|
+
- [x] Merge with options (rebase-before-merge, squash, fast-forward)
|
|
56
|
+
- [x] Rebase onto target branch (with optional auto-stash)
|
|
57
|
+
- [x] Preflight conflict checks for merge/rebase
|
|
58
|
+
- [x] Hard/soft/mixed reset to selected commit
|
|
59
|
+
- [x] Cherry-pick single/multiple commits and abort cherry-pick
|
|
60
|
+
- [x] Commit details and per-file commit diffs (split/inline)
|
|
61
|
+
|
|
62
|
+
#### Remote Operations
|
|
63
|
+
- [x] Fetch default remote
|
|
64
|
+
- [x] Fetch specific remote
|
|
65
|
+
- [x] Fetch all remotes (prune)
|
|
66
|
+
- [x] Pull from remote branch (rebase option)
|
|
67
|
+
- [x] Push to remote branch (force, set-upstream, rebase-first, squash, local-tags)
|
|
68
|
+
|
|
69
|
+
#### Tags, Credentials, and Automation
|
|
70
|
+
- [x] Create local tag and optionally push to remote
|
|
71
|
+
- [x] Delete local tag
|
|
72
|
+
- [x] Delete remote tag
|
|
73
|
+
- [x] Manage credentials (GitHub/GitLab) and associate per repository
|
|
74
|
+
- [x] Run repository custom bash scripts from branch context menu (with live output/cancel)
|
|
75
|
+
|
|
76
|
+
#### UX & Reliability
|
|
77
|
+
- [x] Dark/light/system theme toggle
|
|
78
|
+
- [x] Image diff support and binary file detection
|
|
79
|
+
- [x] Large-diff protection to prevent UI freeze
|
|
80
|
+
- [x] Persist key UI state (sidebar/panel sizes/folders/filters)
|
|
81
|
+
- [x] Git error handling with actionable messages + lock file cleanup action
|
|
82
|
+
|
|
83
|
+
### TODO
|
|
84
|
+
- [ ] Clone repository from URL
|
|
85
|
+
- [ ] Initialize a new repository
|
|
86
|
+
- [ ] Manage remotes (add/edit/remove remote definitions)
|
|
87
|
+
- [x] Revert commits
|
|
88
|
+
- [ ] Commit signing (GPG/SSH)
|
|
89
|
+
- [ ] Co-author commit support
|
|
90
|
+
- [ ] Interactive rebase UI
|
|
91
|
+
- [ ] Blame/annotate view
|
|
92
|
+
- [ ] Submodule management
|
|
93
|
+
- [ ] Git LFS workflows
|
|
94
|
+
|
|
95
|
+
## Tech Stack
|
|
96
|
+
|
|
97
|
+
- **Framework:** [Next.js](https://nextjs.org/) 16 (App Router)
|
|
98
|
+
- **Language:** TypeScript
|
|
99
|
+
- **UI Components:** [DaisyUI](https://daisyui.com/)
|
|
100
|
+
- **Styling:** [Tailwind CSS](https://tailwindcss.com/) 4
|
|
101
|
+
- **State Management:** [TanStack Query](https://tanstack.com/query)
|
|
102
|
+
- **Git Operations:** [simple-git](https://github.com/steveukx/git-js)
|
|
103
|
+
- **Diff Rendering:** [@alexbruf/react-diff-viewer](https://www.npmjs.com/package/@alexbruf/react-diff-viewer)
|
|
104
|
+
- **Theming:** [next-themes](https://github.com/pacocoursey/next-themes)
|
|
105
|
+
- **Validation:** [Zod](https://zod.dev/)
|
|
106
|
+
|
|
107
|
+
## Getting Started
|
|
108
|
+
|
|
109
|
+
### Prerequisites
|
|
110
|
+
|
|
111
|
+
- Node.js 18+
|
|
112
|
+
- Git installed and available in PATH
|
|
113
|
+
|
|
114
|
+
### Run with npx
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npx trident-git
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
This launches the app on an available local port (default `3100`).
|
|
121
|
+
You can also pass options:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
npx trident-git --port 3200
|
|
125
|
+
npx trident-git --dev
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Installation
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# Clone the repository
|
|
132
|
+
git clone <repository-url>
|
|
133
|
+
cd trident
|
|
134
|
+
|
|
135
|
+
# Install dependencies
|
|
136
|
+
npm install
|
|
137
|
+
|
|
138
|
+
# Start the development server
|
|
139
|
+
npm run dev
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Open [http://localhost:3000](http://localhost:3000) in your browser.
|
|
143
|
+
|
|
144
|
+
### Build for Production
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
npm run build
|
|
148
|
+
npm start
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Usage
|
|
152
|
+
|
|
153
|
+
1. **Add a Repository** - Click "Add Repository" on the home page and browse to select a local Git repository
|
|
154
|
+
2. **View Changes** - The workspace view shows staged and unstaged changes with diff previews
|
|
155
|
+
3. **Commit Changes** - Stage files and enter a commit message (Cmd/Ctrl+Enter to commit)
|
|
156
|
+
4. **Browse History** - Navigate to the History tab to view the commit graph
|
|
157
|
+
5. **Manage Branches** - Use the branch sidebar to switch, create, or manage branches
|
|
158
|
+
|
|
159
|
+
## Project Structure
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
src/
|
|
163
|
+
├── app/ # Next.js App Router pages
|
|
164
|
+
│ ├── api/ # API routes for Git operations
|
|
165
|
+
│ │ ├── git/ # Git action endpoints
|
|
166
|
+
│ │ ├── fs/ # File system endpoints
|
|
167
|
+
│ │ └── repos/ # Repository management
|
|
168
|
+
│ └── workspace/ # Workspace pages (history, changes, stashes, settings)
|
|
169
|
+
├── components/ # React components
|
|
170
|
+
│ ├── git/ # Git-specific components
|
|
171
|
+
│ │ ├── diff-view.tsx
|
|
172
|
+
│ │ ├── git-graph.tsx
|
|
173
|
+
│ │ ├── history-view.tsx
|
|
174
|
+
│ │ └── status-view.tsx
|
|
175
|
+
│ ├── layout/ # Layout components
|
|
176
|
+
│ └── ui/ # Reusable UI components (Radix-based)
|
|
177
|
+
├── hooks/ # Custom React hooks
|
|
178
|
+
├── lib/ # Utilities and services
|
|
179
|
+
│ ├── git.ts # Git service wrapper
|
|
180
|
+
│ ├── graph-utils.ts # Commit graph algorithms
|
|
181
|
+
│ ├── store.ts # State management
|
|
182
|
+
│ └── types.ts # TypeScript definitions
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Scripts
|
|
186
|
+
|
|
187
|
+
| Command | Description |
|
|
188
|
+
|---------|-------------|
|
|
189
|
+
| `npm run dev` | Start development server |
|
|
190
|
+
| `npm run build` | Build for production |
|
|
191
|
+
| `npm run start` | Start production server |
|
|
192
|
+
| `npm run lint` | Run ESLint |
|
|
193
|
+
| `npm run cli` | Start through the packaged CLI launcher |
|
|
194
|
+
| `npm run pack:preview` | Preview npm package contents |
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
MIT
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import net from "node:net";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
const APP_ROOT = path.resolve(__dirname, "..");
|
|
12
|
+
const NEXT_BIN = path.join(APP_ROOT, "node_modules", "next", "dist", "bin", "next");
|
|
13
|
+
const DEFAULT_PORT = 3100;
|
|
14
|
+
|
|
15
|
+
function printHelp() {
|
|
16
|
+
console.log(`Usage: trident-git [options]
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
-p, --port <port> Port to run on (default: 3100)
|
|
20
|
+
--dev Run in development mode
|
|
21
|
+
-h, --help Show this help message
|
|
22
|
+
`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseArgs(argv) {
|
|
26
|
+
const options = {
|
|
27
|
+
mode: "start",
|
|
28
|
+
port: undefined,
|
|
29
|
+
portExplicit: false,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
33
|
+
const arg = argv[i];
|
|
34
|
+
|
|
35
|
+
if (arg === "--help" || arg === "-h") {
|
|
36
|
+
printHelp();
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (arg === "--dev") {
|
|
41
|
+
options.mode = "dev";
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (arg === "--port" || arg === "-p") {
|
|
46
|
+
const value = argv[i + 1];
|
|
47
|
+
if (!value) {
|
|
48
|
+
throw new Error("Missing value for --port.");
|
|
49
|
+
}
|
|
50
|
+
const parsed = Number.parseInt(value, 10);
|
|
51
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
|
|
52
|
+
throw new Error(`Invalid port: ${value}`);
|
|
53
|
+
}
|
|
54
|
+
options.port = parsed;
|
|
55
|
+
options.portExplicit = true;
|
|
56
|
+
i += 1;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return options;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function checkPortAvailable(port) {
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
const server = net.createServer();
|
|
69
|
+
|
|
70
|
+
server.once("error", () => resolve(false));
|
|
71
|
+
server.once("listening", () => {
|
|
72
|
+
server.close(() => resolve(true));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
server.listen(port, "127.0.0.1");
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function findAvailablePort(startPort, maxAttempts = 20) {
|
|
80
|
+
for (let i = 0; i < maxAttempts; i += 1) {
|
|
81
|
+
const candidate = startPort + i;
|
|
82
|
+
const available = await checkPortAvailable(candidate);
|
|
83
|
+
if (available) {
|
|
84
|
+
return candidate;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
throw new Error(`Could not find an available port in range ${startPort}-${startPort + maxAttempts - 1}.`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function runNext(args) {
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
const child = spawn(process.execPath, [NEXT_BIN, ...args], {
|
|
93
|
+
cwd: APP_ROOT,
|
|
94
|
+
stdio: "inherit",
|
|
95
|
+
env: process.env,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
child.on("error", reject);
|
|
99
|
+
child.on("exit", (code, signal) => {
|
|
100
|
+
if (signal) {
|
|
101
|
+
process.kill(process.pid, signal);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
resolve(code === null ? 1 : code);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function ensureBuildExists() {
|
|
110
|
+
const buildIdPath = path.join(APP_ROOT, ".next", "BUILD_ID");
|
|
111
|
+
if (fs.existsSync(buildIdPath)) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
console.log("No production build found. Building trident-git...");
|
|
116
|
+
const exitCode = await runNext(["build", "--webpack"]);
|
|
117
|
+
if (exitCode !== 0) {
|
|
118
|
+
process.exit(exitCode);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function main() {
|
|
123
|
+
try {
|
|
124
|
+
const options = parseArgs(process.argv.slice(2));
|
|
125
|
+
const envPort = Number.parseInt(process.env.PORT || "", 10);
|
|
126
|
+
const preferredPort =
|
|
127
|
+
options.port ??
|
|
128
|
+
(Number.isInteger(envPort) && envPort > 0 && envPort <= 65535 ? envPort : DEFAULT_PORT);
|
|
129
|
+
|
|
130
|
+
let port = preferredPort;
|
|
131
|
+
if (!options.portExplicit && !process.env.PORT) {
|
|
132
|
+
port = await findAvailablePort(preferredPort);
|
|
133
|
+
if (port !== preferredPort) {
|
|
134
|
+
console.log(`Port ${preferredPort} is in use. Using ${port} instead.`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (options.mode === "dev") {
|
|
139
|
+
console.log(`Starting trident-git in development mode on http://localhost:${port}`);
|
|
140
|
+
process.exit(await runNext(["dev", "--webpack", "-p", String(port)]));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
await ensureBuildExists();
|
|
144
|
+
console.log(`Starting trident-git on http://localhost:${port}`);
|
|
145
|
+
process.exit(await runNext(["start", "-p", String(port)]));
|
|
146
|
+
} catch (error) {
|
|
147
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
148
|
+
console.error(`trident-git failed to start: ${errorMessage}`);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
main();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
2
|
+
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
3
|
+
import nextTs from "eslint-config-next/typescript";
|
|
4
|
+
|
|
5
|
+
const eslintConfig = defineConfig([
|
|
6
|
+
...nextVitals,
|
|
7
|
+
...nextTs,
|
|
8
|
+
// Override default ignores of eslint-config-next.
|
|
9
|
+
globalIgnores([
|
|
10
|
+
// Default ignores of eslint-config-next:
|
|
11
|
+
".next/**",
|
|
12
|
+
"out/**",
|
|
13
|
+
"build/**",
|
|
14
|
+
"next-env.d.ts",
|
|
15
|
+
]),
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export default eslintConfig;
|
package/next.config.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { NextConfig } from "next";
|
|
2
|
+
|
|
3
|
+
const nextConfig: NextConfig = {
|
|
4
|
+
// Disable Turbopack for production builds as keytar is a native module
|
|
5
|
+
// that requires webpack's externals configuration
|
|
6
|
+
experimental: {
|
|
7
|
+
// Use webpack for server-side code
|
|
8
|
+
},
|
|
9
|
+
// Configure webpack to handle native modules
|
|
10
|
+
webpack: (config, { isServer }) => {
|
|
11
|
+
if (isServer) {
|
|
12
|
+
// Externalize keytar - it's a native module that should be loaded at runtime
|
|
13
|
+
config.externals = config.externals || [];
|
|
14
|
+
config.externals.push('keytar');
|
|
15
|
+
}
|
|
16
|
+
return config;
|
|
17
|
+
},
|
|
18
|
+
images: {
|
|
19
|
+
remotePatterns: [
|
|
20
|
+
{
|
|
21
|
+
protocol: 'https',
|
|
22
|
+
hostname: 'www.google.com',
|
|
23
|
+
pathname: '/s2/favicons',
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
serverExternalPackages: ['keytar'],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default nextConfig;
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "trident-git",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "A modern, web-based Git client.",
|
|
6
|
+
"bin": {
|
|
7
|
+
"trident-git": "bin/trident-git.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src",
|
|
12
|
+
"public",
|
|
13
|
+
"next.config.ts",
|
|
14
|
+
"postcss.config.mjs",
|
|
15
|
+
"tsconfig.json",
|
|
16
|
+
"eslint.config.mjs",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"dev": "npx gfport 3100 -- next dev --webpack -p {PORT}",
|
|
21
|
+
"build": "next build --webpack",
|
|
22
|
+
"start": "next start -p 3100",
|
|
23
|
+
"lint": "eslint",
|
|
24
|
+
"cli": "node ./bin/trident-git.mjs",
|
|
25
|
+
"pack:preview": "npm pack --dry-run"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"git",
|
|
29
|
+
"client",
|
|
30
|
+
"nextjs"
|
|
31
|
+
],
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@alexbruf/react-diff-viewer": "^3.1.5",
|
|
35
|
+
"@tanstack/react-query": "^5.90.20",
|
|
36
|
+
"clsx": "^2.1.1",
|
|
37
|
+
"daisyui": "^5.5.18",
|
|
38
|
+
"keytar": "^7.9.0",
|
|
39
|
+
"next": "16.1.6",
|
|
40
|
+
"next-themes": "^0.4.6",
|
|
41
|
+
"react": "19.2.3",
|
|
42
|
+
"react-dom": "19.2.3",
|
|
43
|
+
"react-syntax-highlighter": "^16.1.0",
|
|
44
|
+
"simple-git": "^3.30.0",
|
|
45
|
+
"tailwind-merge": "^3.4.0",
|
|
46
|
+
"zod": "^4.3.6"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@tailwindcss/postcss": "^4",
|
|
50
|
+
"@types/node": "^20",
|
|
51
|
+
"@types/react": "^19",
|
|
52
|
+
"@types/react-dom": "^19",
|
|
53
|
+
"@types/react-syntax-highlighter": "^15.5.13",
|
|
54
|
+
"eslint": "^9",
|
|
55
|
+
"eslint-config-next": "16.1.6",
|
|
56
|
+
"gfport": "^1.0.1",
|
|
57
|
+
"tailwindcss": "^4",
|
|
58
|
+
"typescript": "^5"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
Binary file
|
package/public/file.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
package/public/globe.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
package/public/next.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import {
|
|
4
|
+
getAllCredentials,
|
|
5
|
+
createGitHubCredential,
|
|
6
|
+
createGitLabCredential,
|
|
7
|
+
updateCredential,
|
|
8
|
+
deleteCredential,
|
|
9
|
+
} from '@/lib/credentials';
|
|
10
|
+
|
|
11
|
+
// GET - List all credentials
|
|
12
|
+
export async function GET() {
|
|
13
|
+
try {
|
|
14
|
+
const credentials = await getAllCredentials();
|
|
15
|
+
return NextResponse.json(credentials);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.error('Failed to get credentials:', error);
|
|
18
|
+
return NextResponse.json({ error: 'Failed to get credentials' }, { status: 500 });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// POST - Create a new credential
|
|
23
|
+
const createGitHubSchema = z.object({
|
|
24
|
+
type: z.literal('github'),
|
|
25
|
+
token: z.string().min(1, 'Token is required'),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const createGitLabSchema = z.object({
|
|
29
|
+
type: z.literal('gitlab'),
|
|
30
|
+
serverUrl: z.string().url('Invalid server URL'),
|
|
31
|
+
token: z.string().min(1, 'Token is required'),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const createSchema = z.discriminatedUnion('type', [createGitHubSchema, createGitLabSchema]);
|
|
35
|
+
|
|
36
|
+
export async function POST(request: Request) {
|
|
37
|
+
try {
|
|
38
|
+
const body = await request.json();
|
|
39
|
+
const data = createSchema.parse(body);
|
|
40
|
+
|
|
41
|
+
let result;
|
|
42
|
+
if (data.type === 'github') {
|
|
43
|
+
result = await createGitHubCredential(data.token);
|
|
44
|
+
} else {
|
|
45
|
+
result = await createGitLabCredential(data.serverUrl, data.token);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!result.success) {
|
|
49
|
+
return NextResponse.json({ error: result.error }, { status: 400 });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return NextResponse.json(result.credential);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error instanceof z.ZodError) {
|
|
55
|
+
return NextResponse.json({ error: error.issues[0].message }, { status: 400 });
|
|
56
|
+
}
|
|
57
|
+
console.error('Failed to create credential:', error);
|
|
58
|
+
return NextResponse.json({ error: 'Failed to create credential' }, { status: 500 });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// PUT - Update a credential
|
|
63
|
+
const updateSchema = z.object({
|
|
64
|
+
id: z.string().min(1, 'Credential ID is required'),
|
|
65
|
+
token: z.string().min(1, 'Token is required'),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export async function PUT(request: Request) {
|
|
69
|
+
try {
|
|
70
|
+
const body = await request.json();
|
|
71
|
+
const { id, token } = updateSchema.parse(body);
|
|
72
|
+
|
|
73
|
+
const result = await updateCredential(id, token);
|
|
74
|
+
|
|
75
|
+
if (!result.success) {
|
|
76
|
+
return NextResponse.json({ error: result.error }, { status: 400 });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return NextResponse.json(result.credential);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (error instanceof z.ZodError) {
|
|
82
|
+
return NextResponse.json({ error: error.issues[0].message }, { status: 400 });
|
|
83
|
+
}
|
|
84
|
+
console.error('Failed to update credential:', error);
|
|
85
|
+
return NextResponse.json({ error: 'Failed to update credential' }, { status: 500 });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// DELETE - Delete a credential
|
|
90
|
+
const deleteSchema = z.object({
|
|
91
|
+
id: z.string().min(1, 'Credential ID is required'),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export async function DELETE(request: Request) {
|
|
95
|
+
try {
|
|
96
|
+
const body = await request.json();
|
|
97
|
+
const { id } = deleteSchema.parse(body);
|
|
98
|
+
|
|
99
|
+
const result = await deleteCredential(id);
|
|
100
|
+
|
|
101
|
+
if (!result.success) {
|
|
102
|
+
return NextResponse.json({ error: result.error }, { status: 400 });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return NextResponse.json({ success: true });
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (error instanceof z.ZodError) {
|
|
108
|
+
return NextResponse.json({ error: error.issues[0].message }, { status: 400 });
|
|
109
|
+
}
|
|
110
|
+
console.error('Failed to delete credential:', error);
|
|
111
|
+
return NextResponse.json({ error: 'Failed to delete credential' }, { status: 500 });
|
|
112
|
+
}
|
|
113
|
+
}
|