@doriandev/devcc 0.1.2
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/LICENSE +15 -0
- package/README.md +659 -0
- package/bin/devcc.mjs +66 -0
- package/dist/devcc/app/App.d.ts +178 -0
- package/dist/devcc/app/App.d.ts.map +1 -0
- package/dist/devcc/app/App.js +1255 -0
- package/dist/devcc/app/App.js.map +1 -0
- package/dist/devcc/app/builtinCommands.d.ts +17 -0
- package/dist/devcc/app/builtinCommands.d.ts.map +1 -0
- package/dist/devcc/app/builtinCommands.js +389 -0
- package/dist/devcc/app/builtinCommands.js.map +1 -0
- package/dist/devcc/app/keymap.d.ts +21 -0
- package/dist/devcc/app/keymap.d.ts.map +1 -0
- package/dist/devcc/app/keymap.js +162 -0
- package/dist/devcc/app/keymap.js.map +1 -0
- package/dist/devcc/app/theme.d.ts +49 -0
- package/dist/devcc/app/theme.d.ts.map +1 -0
- package/dist/devcc/app/theme.js +68 -0
- package/dist/devcc/app/theme.js.map +1 -0
- package/dist/devcc/cli.d.ts +3 -0
- package/dist/devcc/cli.d.ts.map +1 -0
- package/dist/devcc/cli.js +113 -0
- package/dist/devcc/cli.js.map +1 -0
- package/dist/devcc/components/CommandPalette.d.ts +26 -0
- package/dist/devcc/components/CommandPalette.d.ts.map +1 -0
- package/dist/devcc/components/CommandPalette.js +113 -0
- package/dist/devcc/components/CommandPalette.js.map +1 -0
- package/dist/devcc/components/ConfirmDialog.d.ts +31 -0
- package/dist/devcc/components/ConfirmDialog.d.ts.map +1 -0
- package/dist/devcc/components/ConfirmDialog.js +147 -0
- package/dist/devcc/components/ConfirmDialog.js.map +1 -0
- package/dist/devcc/components/Footer.d.ts +22 -0
- package/dist/devcc/components/Footer.d.ts.map +1 -0
- package/dist/devcc/components/Footer.js +72 -0
- package/dist/devcc/components/Footer.js.map +1 -0
- package/dist/devcc/components/Header.d.ts +12 -0
- package/dist/devcc/components/Header.d.ts.map +1 -0
- package/dist/devcc/components/Header.js +84 -0
- package/dist/devcc/components/Header.js.map +1 -0
- package/dist/devcc/components/HelpOverlay.d.ts +18 -0
- package/dist/devcc/components/HelpOverlay.d.ts.map +1 -0
- package/dist/devcc/components/HelpOverlay.js +48 -0
- package/dist/devcc/components/HelpOverlay.js.map +1 -0
- package/dist/devcc/components/Overlay.d.ts +31 -0
- package/dist/devcc/components/Overlay.d.ts.map +1 -0
- package/dist/devcc/components/Overlay.js +60 -0
- package/dist/devcc/components/Overlay.js.map +1 -0
- package/dist/devcc/components/ProjectPicker.d.ts +26 -0
- package/dist/devcc/components/ProjectPicker.d.ts.map +1 -0
- package/dist/devcc/components/ProjectPicker.js +114 -0
- package/dist/devcc/components/ProjectPicker.js.map +1 -0
- package/dist/devcc/components/Sidebar.d.ts +42 -0
- package/dist/devcc/components/Sidebar.d.ts.map +1 -0
- package/dist/devcc/components/Sidebar.js +127 -0
- package/dist/devcc/components/Sidebar.js.map +1 -0
- package/dist/devcc/components/StatusBadge.d.ts +16 -0
- package/dist/devcc/components/StatusBadge.d.ts.map +1 -0
- package/dist/devcc/components/StatusBadge.js +30 -0
- package/dist/devcc/components/StatusBadge.js.map +1 -0
- package/dist/devcc/components/primitives.d.ts +62 -0
- package/dist/devcc/components/primitives.d.ts.map +1 -0
- package/dist/devcc/components/primitives.js +169 -0
- package/dist/devcc/components/primitives.js.map +1 -0
- package/dist/devcc/components/styled.d.ts +16 -0
- package/dist/devcc/components/styled.d.ts.map +1 -0
- package/dist/devcc/components/styled.js +35 -0
- package/dist/devcc/components/styled.js.map +1 -0
- package/dist/devcc/core/commands.d.ts +89 -0
- package/dist/devcc/core/commands.d.ts.map +1 -0
- package/dist/devcc/core/commands.js +68 -0
- package/dist/devcc/core/commands.js.map +1 -0
- package/dist/devcc/core/config.d.ts +81 -0
- package/dist/devcc/core/config.d.ts.map +1 -0
- package/dist/devcc/core/config.js +313 -0
- package/dist/devcc/core/config.js.map +1 -0
- package/dist/devcc/core/events.d.ts +21 -0
- package/dist/devcc/core/events.d.ts.map +1 -0
- package/dist/devcc/core/events.js +27 -0
- package/dist/devcc/core/events.js.map +1 -0
- package/dist/devcc/core/state.d.ts +104 -0
- package/dist/devcc/core/state.d.ts.map +1 -0
- package/dist/devcc/core/state.js +139 -0
- package/dist/devcc/core/state.js.map +1 -0
- package/dist/devcc/deps/DependencyService.d.ts +50 -0
- package/dist/devcc/deps/DependencyService.d.ts.map +1 -0
- package/dist/devcc/deps/DependencyService.js +109 -0
- package/dist/devcc/deps/DependencyService.js.map +1 -0
- package/dist/devcc/deps/parse.d.ts +49 -0
- package/dist/devcc/deps/parse.d.ts.map +1 -0
- package/dist/devcc/deps/parse.js +158 -0
- package/dist/devcc/deps/parse.js.map +1 -0
- package/dist/devcc/git/GitService.d.ts +60 -0
- package/dist/devcc/git/GitService.d.ts.map +1 -0
- package/dist/devcc/git/GitService.js +160 -0
- package/dist/devcc/git/GitService.js.map +1 -0
- package/dist/devcc/git/parse.d.ts +57 -0
- package/dist/devcc/git/parse.d.ts.map +1 -0
- package/dist/devcc/git/parse.js +174 -0
- package/dist/devcc/git/parse.js.map +1 -0
- package/dist/devcc/index.d.ts +37 -0
- package/dist/devcc/index.d.ts.map +1 -0
- package/dist/devcc/index.js +36 -0
- package/dist/devcc/index.js.map +1 -0
- package/dist/devcc/logs/LogBuffer.d.ts +86 -0
- package/dist/devcc/logs/LogBuffer.d.ts.map +1 -0
- package/dist/devcc/logs/LogBuffer.js +200 -0
- package/dist/devcc/logs/LogBuffer.js.map +1 -0
- package/dist/devcc/logs/merge.d.ts +36 -0
- package/dist/devcc/logs/merge.d.ts.map +1 -0
- package/dist/devcc/logs/merge.js +71 -0
- package/dist/devcc/logs/merge.js.map +1 -0
- package/dist/devcc/processes/ManagedProcess.d.ts +129 -0
- package/dist/devcc/processes/ManagedProcess.d.ts.map +1 -0
- package/dist/devcc/processes/ManagedProcess.js +80 -0
- package/dist/devcc/processes/ManagedProcess.js.map +1 -0
- package/dist/devcc/processes/ProcessManager.d.ts +112 -0
- package/dist/devcc/processes/ProcessManager.d.ts.map +1 -0
- package/dist/devcc/processes/ProcessManager.js +525 -0
- package/dist/devcc/processes/ProcessManager.js.map +1 -0
- package/dist/devcc/processes/graph.d.ts +35 -0
- package/dist/devcc/processes/graph.d.ts.map +1 -0
- package/dist/devcc/processes/graph.js +68 -0
- package/dist/devcc/processes/graph.js.map +1 -0
- package/dist/devcc/projects/ProjectRegistry.d.ts +52 -0
- package/dist/devcc/projects/ProjectRegistry.d.ts.map +1 -0
- package/dist/devcc/projects/ProjectRegistry.js +177 -0
- package/dist/devcc/projects/ProjectRegistry.js.map +1 -0
- package/dist/devcc/services/ServiceManager.d.ts +48 -0
- package/dist/devcc/services/ServiceManager.d.ts.map +1 -0
- package/dist/devcc/services/ServiceManager.js +146 -0
- package/dist/devcc/services/ServiceManager.js.map +1 -0
- package/dist/devcc/services/docker.d.ts +37 -0
- package/dist/devcc/services/docker.d.ts.map +1 -0
- package/dist/devcc/services/docker.js +179 -0
- package/dist/devcc/services/docker.js.map +1 -0
- package/dist/devcc/services/node.d.ts +40 -0
- package/dist/devcc/services/node.d.ts.map +1 -0
- package/dist/devcc/services/node.js +107 -0
- package/dist/devcc/services/node.js.map +1 -0
- package/dist/devcc/services/ollama.d.ts +27 -0
- package/dist/devcc/services/ollama.d.ts.map +1 -0
- package/dist/devcc/services/ollama.js +66 -0
- package/dist/devcc/services/ollama.js.map +1 -0
- package/dist/devcc/services/postgres.d.ts +30 -0
- package/dist/devcc/services/postgres.d.ts.map +1 -0
- package/dist/devcc/services/postgres.js +47 -0
- package/dist/devcc/services/postgres.js.map +1 -0
- package/dist/devcc/services/redis.d.ts +20 -0
- package/dist/devcc/services/redis.d.ts.map +1 -0
- package/dist/devcc/services/redis.js +39 -0
- package/dist/devcc/services/redis.js.map +1 -0
- package/dist/devcc/services/types.d.ts +51 -0
- package/dist/devcc/services/types.d.ts.map +1 -0
- package/dist/devcc/services/types.js +10 -0
- package/dist/devcc/services/types.js.map +1 -0
- package/dist/devcc/system/SystemMonitor.d.ts +87 -0
- package/dist/devcc/system/SystemMonitor.d.ts.map +1 -0
- package/dist/devcc/system/SystemMonitor.js +142 -0
- package/dist/devcc/system/SystemMonitor.js.map +1 -0
- package/dist/devcc/utils/errors.d.ts +31 -0
- package/dist/devcc/utils/errors.d.ts.map +1 -0
- package/dist/devcc/utils/errors.js +52 -0
- package/dist/devcc/utils/errors.js.map +1 -0
- package/dist/devcc/utils/exec.d.ts +41 -0
- package/dist/devcc/utils/exec.d.ts.map +1 -0
- package/dist/devcc/utils/exec.js +112 -0
- package/dist/devcc/utils/exec.js.map +1 -0
- package/dist/devcc/utils/format.d.ts +18 -0
- package/dist/devcc/utils/format.d.ts.map +1 -0
- package/dist/devcc/utils/format.js +85 -0
- package/dist/devcc/utils/format.js.map +1 -0
- package/dist/devcc/utils/fuzzy.d.ts +12 -0
- package/dist/devcc/utils/fuzzy.d.ts.map +1 -0
- package/dist/devcc/utils/fuzzy.js +20 -0
- package/dist/devcc/utils/fuzzy.js.map +1 -0
- package/dist/devcc/utils/logger.d.ts +41 -0
- package/dist/devcc/utils/logger.d.ts.map +1 -0
- package/dist/devcc/utils/logger.js +117 -0
- package/dist/devcc/utils/logger.js.map +1 -0
- package/dist/devcc/utils/packageManager.d.ts +39 -0
- package/dist/devcc/utils/packageManager.d.ts.map +1 -0
- package/dist/devcc/utils/packageManager.js +85 -0
- package/dist/devcc/utils/packageManager.js.map +1 -0
- package/dist/devcc/utils/paths.d.ts +12 -0
- package/dist/devcc/utils/paths.d.ts.map +1 -0
- package/dist/devcc/utils/paths.js +39 -0
- package/dist/devcc/utils/paths.js.map +1 -0
- package/dist/devcc/views/DependenciesView.d.ts +34 -0
- package/dist/devcc/views/DependenciesView.d.ts.map +1 -0
- package/dist/devcc/views/DependenciesView.js +170 -0
- package/dist/devcc/views/DependenciesView.js.map +1 -0
- package/dist/devcc/views/GitView.d.ts +29 -0
- package/dist/devcc/views/GitView.d.ts.map +1 -0
- package/dist/devcc/views/GitView.js +226 -0
- package/dist/devcc/views/GitView.js.map +1 -0
- package/dist/devcc/views/LogsView.d.ts +39 -0
- package/dist/devcc/views/LogsView.d.ts.map +1 -0
- package/dist/devcc/views/LogsView.js +249 -0
- package/dist/devcc/views/LogsView.js.map +1 -0
- package/dist/devcc/views/OverviewView.d.ts +16 -0
- package/dist/devcc/views/OverviewView.d.ts.map +1 -0
- package/dist/devcc/views/OverviewView.js +135 -0
- package/dist/devcc/views/OverviewView.js.map +1 -0
- package/dist/devcc/views/ProcessesView.d.ts +26 -0
- package/dist/devcc/views/ProcessesView.d.ts.map +1 -0
- package/dist/devcc/views/ProcessesView.js +161 -0
- package/dist/devcc/views/ProcessesView.js.map +1 -0
- package/dist/devcc/views/ScriptsView.d.ts +30 -0
- package/dist/devcc/views/ScriptsView.d.ts.map +1 -0
- package/dist/devcc/views/ScriptsView.js +168 -0
- package/dist/devcc/views/ScriptsView.js.map +1 -0
- package/dist/devcc/views/ServicesView.d.ts +28 -0
- package/dist/devcc/views/ServicesView.d.ts.map +1 -0
- package/dist/devcc/views/ServicesView.js +153 -0
- package/dist/devcc/views/ServicesView.js.map +1 -0
- package/dist/devcc/views/SettingsView.d.ts +20 -0
- package/dist/devcc/views/SettingsView.d.ts.map +1 -0
- package/dist/devcc/views/SettingsView.js +114 -0
- package/dist/devcc/views/SettingsView.js.map +1 -0
- package/dist/devcc/views/SystemView.d.ts +13 -0
- package/dist/devcc/views/SystemView.d.ts.map +1 -0
- package/dist/devcc/views/SystemView.js +82 -0
- package/dist/devcc/views/SystemView.js.map +1 -0
- package/dist/devcc/views/View.d.ts +35 -0
- package/dist/devcc/views/View.d.ts.map +1 -0
- package/dist/devcc/views/View.js +42 -0
- package/dist/devcc/views/View.js.map +1 -0
- package/package.json +79 -0
- package/src/devcc/app/App.ts +1463 -0
- package/src/devcc/app/builtinCommands.ts +429 -0
- package/src/devcc/app/keymap.ts +167 -0
- package/src/devcc/app/theme.ts +83 -0
- package/src/devcc/cli.ts +122 -0
- package/src/devcc/components/CommandPalette.ts +136 -0
- package/src/devcc/components/ConfirmDialog.ts +174 -0
- package/src/devcc/components/Footer.ts +85 -0
- package/src/devcc/components/Header.ts +93 -0
- package/src/devcc/components/HelpOverlay.ts +62 -0
- package/src/devcc/components/Overlay.ts +91 -0
- package/src/devcc/components/ProjectPicker.ts +138 -0
- package/src/devcc/components/Sidebar.ts +163 -0
- package/src/devcc/components/StatusBadge.ts +36 -0
- package/src/devcc/components/primitives.ts +213 -0
- package/src/devcc/components/styled.ts +44 -0
- package/src/devcc/core/commands.ts +140 -0
- package/src/devcc/core/config.ts +405 -0
- package/src/devcc/core/events.ts +41 -0
- package/src/devcc/core/state.ts +236 -0
- package/src/devcc/deps/DependencyService.ts +152 -0
- package/src/devcc/deps/parse.ts +210 -0
- package/src/devcc/git/GitService.ts +205 -0
- package/src/devcc/git/parse.ts +203 -0
- package/src/devcc/index.ts +115 -0
- package/src/devcc/logs/LogBuffer.ts +237 -0
- package/src/devcc/logs/merge.ts +87 -0
- package/src/devcc/processes/ManagedProcess.ts +195 -0
- package/src/devcc/processes/ProcessManager.ts +625 -0
- package/src/devcc/processes/graph.ts +98 -0
- package/src/devcc/projects/ProjectRegistry.ts +219 -0
- package/src/devcc/services/ServiceManager.ts +180 -0
- package/src/devcc/services/docker.ts +220 -0
- package/src/devcc/services/node.ts +137 -0
- package/src/devcc/services/ollama.ts +80 -0
- package/src/devcc/services/postgres.ts +57 -0
- package/src/devcc/services/redis.ts +47 -0
- package/src/devcc/services/types.ts +59 -0
- package/src/devcc/system/SystemMonitor.ts +207 -0
- package/src/devcc/utils/errors.ts +64 -0
- package/src/devcc/utils/exec.ts +165 -0
- package/src/devcc/utils/format.ts +83 -0
- package/src/devcc/utils/fuzzy.ts +44 -0
- package/src/devcc/utils/logger.ts +140 -0
- package/src/devcc/utils/packageManager.ts +112 -0
- package/src/devcc/utils/paths.ts +39 -0
- package/src/devcc/views/DependenciesView.ts +206 -0
- package/src/devcc/views/GitView.ts +259 -0
- package/src/devcc/views/LogsView.ts +300 -0
- package/src/devcc/views/OverviewView.ts +187 -0
- package/src/devcc/views/ProcessesView.ts +177 -0
- package/src/devcc/views/ScriptsView.ts +187 -0
- package/src/devcc/views/ServicesView.ts +188 -0
- package/src/devcc/views/SettingsView.ts +146 -0
- package/src/devcc/views/SystemView.ts +110 -0
- package/src/devcc/views/View.ts +63 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { TypedEmitter } from "../core/events.js"
|
|
2
|
+
import { DevccError } from "../utils/errors.js"
|
|
3
|
+
import { exec, type ExecResult } from "../utils/exec.js"
|
|
4
|
+
import type { ScopedLogger } from "../utils/logger.js"
|
|
5
|
+
import {
|
|
6
|
+
EMPTY_GIT_STATUS,
|
|
7
|
+
LOG_FORMAT,
|
|
8
|
+
parseCommits,
|
|
9
|
+
parsePorcelainV2,
|
|
10
|
+
type GitCommit,
|
|
11
|
+
type GitStatus,
|
|
12
|
+
} from "./parse.js"
|
|
13
|
+
|
|
14
|
+
export interface GitSnapshot {
|
|
15
|
+
available: boolean
|
|
16
|
+
isRepository: boolean
|
|
17
|
+
status: GitStatus
|
|
18
|
+
commits: GitCommit[]
|
|
19
|
+
version: string | null
|
|
20
|
+
lastRefreshedAt: Date | null
|
|
21
|
+
/** Set when the last refresh failed; the previous snapshot is kept. */
|
|
22
|
+
error: string | null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const EMPTY_GIT_SNAPSHOT: GitSnapshot = {
|
|
26
|
+
available: false,
|
|
27
|
+
isRepository: false,
|
|
28
|
+
status: EMPTY_GIT_STATUS,
|
|
29
|
+
commits: [],
|
|
30
|
+
version: null,
|
|
31
|
+
lastRefreshedAt: null,
|
|
32
|
+
error: null,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type GitServiceEvents = {
|
|
36
|
+
"git:changed": [GitSnapshot]
|
|
37
|
+
"git:error": [DevccError]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface GitServiceOptions {
|
|
41
|
+
rootDir: string
|
|
42
|
+
logger: ScopedLogger
|
|
43
|
+
commitLimit?: number
|
|
44
|
+
timeoutMs?: number
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Thin wrapper around the `git` executable.
|
|
49
|
+
*
|
|
50
|
+
* We call git directly rather than adding a git abstraction library: the six
|
|
51
|
+
* commands the dashboard needs are stable plumbing, and porcelain v2 output is
|
|
52
|
+
* far easier to keep honest than a dependency's changing object model.
|
|
53
|
+
*/
|
|
54
|
+
export class GitService extends TypedEmitter<GitServiceEvents> {
|
|
55
|
+
private snapshot: GitSnapshot = { ...EMPTY_GIT_SNAPSHOT }
|
|
56
|
+
private refreshing: Promise<GitSnapshot> | null = null
|
|
57
|
+
private readonly commitLimit: number
|
|
58
|
+
private readonly timeoutMs: number
|
|
59
|
+
|
|
60
|
+
constructor(private readonly options: GitServiceOptions) {
|
|
61
|
+
super()
|
|
62
|
+
this.commitLimit = options.commitLimit ?? 8
|
|
63
|
+
this.timeoutMs = options.timeoutMs ?? 5_000
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getSnapshot(): GitSnapshot {
|
|
67
|
+
return this.snapshot
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Refresh branch/status/commits. Concurrent calls share one run. */
|
|
71
|
+
async refresh(): Promise<GitSnapshot> {
|
|
72
|
+
if (this.refreshing) return this.refreshing
|
|
73
|
+
this.refreshing = this.doRefresh().finally(() => {
|
|
74
|
+
this.refreshing = null
|
|
75
|
+
})
|
|
76
|
+
return this.refreshing
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private async doRefresh(): Promise<GitSnapshot> {
|
|
80
|
+
const [versionResult, statusResult, logResult] = await Promise.all([
|
|
81
|
+
this.git(["--version"]),
|
|
82
|
+
this.git(["status", "--porcelain=v2", "--branch", "--untracked-files=all"]),
|
|
83
|
+
this.git(["log", `--max-count=${this.commitLimit}`, `--pretty=format:${LOG_FORMAT}`]),
|
|
84
|
+
])
|
|
85
|
+
|
|
86
|
+
const available = versionResult.spawnError === undefined
|
|
87
|
+
if (!available) {
|
|
88
|
+
return this.publish({
|
|
89
|
+
...EMPTY_GIT_SNAPSHOT,
|
|
90
|
+
available: false,
|
|
91
|
+
lastRefreshedAt: new Date(),
|
|
92
|
+
error: "git executable not found",
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const version = versionResult.ok ? versionResult.stdout.trim().replace(/^git version /u, "") : null
|
|
97
|
+
|
|
98
|
+
if (!statusResult.ok) {
|
|
99
|
+
const notARepo = /not a git repository/iu.test(statusResult.stderr)
|
|
100
|
+
return this.publish({
|
|
101
|
+
...this.snapshot,
|
|
102
|
+
available: true,
|
|
103
|
+
isRepository: false,
|
|
104
|
+
status: EMPTY_GIT_STATUS,
|
|
105
|
+
commits: [],
|
|
106
|
+
version,
|
|
107
|
+
lastRefreshedAt: new Date(),
|
|
108
|
+
error: notARepo ? null : statusResult.stderr.trim() || "git status failed",
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return this.publish({
|
|
113
|
+
available: true,
|
|
114
|
+
isRepository: true,
|
|
115
|
+
status: parsePorcelainV2(statusResult.stdout),
|
|
116
|
+
// A fresh repository with no commits fails `git log`; an empty list is correct there.
|
|
117
|
+
commits: logResult.ok ? parseCommits(logResult.stdout) : [],
|
|
118
|
+
version,
|
|
119
|
+
lastRefreshedAt: new Date(),
|
|
120
|
+
error: null,
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** `git fetch` is deliberately manual: it touches the network. */
|
|
125
|
+
async fetch(): Promise<void> {
|
|
126
|
+
await this.mutate(["fetch", "--prune"], "git fetch", 30_000)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async pull(): Promise<void> {
|
|
130
|
+
await this.mutate(["pull", "--ff-only"], "git pull", 60_000)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async push(): Promise<void> {
|
|
134
|
+
await this.mutate(["push"], "git push", 60_000)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async stage(paths: readonly string[]): Promise<void> {
|
|
138
|
+
if (paths.length === 0) return
|
|
139
|
+
await this.mutate(["add", "--", ...paths], "git add")
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async stageAll(): Promise<void> {
|
|
143
|
+
await this.mutate(["add", "--all"], "git add --all")
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async unstage(paths: readonly string[]): Promise<void> {
|
|
147
|
+
if (paths.length === 0) return
|
|
148
|
+
await this.mutate(["restore", "--staged", "--", ...paths], "git restore --staged")
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Destructive: throws away worktree changes. Callers must confirm first. */
|
|
152
|
+
async discard(paths: readonly string[]): Promise<void> {
|
|
153
|
+
if (paths.length === 0) return
|
|
154
|
+
await this.mutate(["restore", "--worktree", "--", ...paths], "git restore")
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async commit(message: string): Promise<void> {
|
|
158
|
+
const trimmed = message.trim()
|
|
159
|
+
if (trimmed === "") {
|
|
160
|
+
throw new DevccError({ scope: "git", message: "Commit message is empty" })
|
|
161
|
+
}
|
|
162
|
+
await this.mutate(["commit", "--message", trimmed], "git commit")
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Read-only diff for the inspect panel. */
|
|
166
|
+
async diff(path: string, staged: boolean): Promise<string> {
|
|
167
|
+
const args = ["--no-pager", "diff", "--no-color"]
|
|
168
|
+
if (staged) args.push("--staged")
|
|
169
|
+
args.push("--", path)
|
|
170
|
+
const result = await this.git(args, 10_000)
|
|
171
|
+
return result.ok ? result.stdout : result.stderr
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private async mutate(args: readonly string[], label: string, timeoutMs?: number): Promise<void> {
|
|
175
|
+
const result = await this.git(args, timeoutMs)
|
|
176
|
+
if (!result.ok) {
|
|
177
|
+
const detail = [result.stdout, result.stderr].filter(Boolean).join("\n").trim()
|
|
178
|
+
this.options.logger.warn(`${label} failed`, { code: result.code, detail })
|
|
179
|
+
const error = new DevccError({
|
|
180
|
+
scope: "git",
|
|
181
|
+
message: `${label} failed`,
|
|
182
|
+
detail: detail || (result.timedOut ? "timed out" : undefined),
|
|
183
|
+
})
|
|
184
|
+
this.emit("git:error", error)
|
|
185
|
+
throw error
|
|
186
|
+
}
|
|
187
|
+
this.options.logger.info(`${label} ok`)
|
|
188
|
+
await this.refresh()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private git(args: readonly string[], timeoutMs?: number): Promise<ExecResult> {
|
|
192
|
+
return exec("git", args, {
|
|
193
|
+
cwd: this.options.rootDir,
|
|
194
|
+
timeoutMs: timeoutMs ?? this.timeoutMs,
|
|
195
|
+
// Never let git open an editor or a credential prompt under the TUI.
|
|
196
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_OPTIONAL_LOCKS: "0", GIT_EDITOR: "true" },
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private publish(snapshot: GitSnapshot): GitSnapshot {
|
|
201
|
+
this.snapshot = snapshot
|
|
202
|
+
this.emit("git:changed", snapshot)
|
|
203
|
+
return snapshot
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure parsers for git plumbing output.
|
|
3
|
+
*
|
|
4
|
+
* Kept separate from `GitService` so they can be unit tested without a repo,
|
|
5
|
+
* and so the shape of the data the UI consumes is defined in one place.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type GitFileState = "staged" | "modified" | "untracked" | "conflicted"
|
|
9
|
+
|
|
10
|
+
export interface GitFile {
|
|
11
|
+
path: string
|
|
12
|
+
state: GitFileState
|
|
13
|
+
/** Two-letter porcelain code, e.g. `M.`, `.M`, `A.`, `??`. */
|
|
14
|
+
code: string
|
|
15
|
+
/** Original path for renames. */
|
|
16
|
+
origPath?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface GitStatus {
|
|
20
|
+
branch: string | null
|
|
21
|
+
upstream: string | null
|
|
22
|
+
ahead: number
|
|
23
|
+
behind: number
|
|
24
|
+
/** Short commit hash of HEAD, when known. */
|
|
25
|
+
head: string | null
|
|
26
|
+
files: GitFile[]
|
|
27
|
+
clean: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface GitCommit {
|
|
31
|
+
hash: string
|
|
32
|
+
shortHash: string
|
|
33
|
+
author: string
|
|
34
|
+
relativeDate: string
|
|
35
|
+
subject: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const EMPTY_GIT_STATUS: GitStatus = {
|
|
39
|
+
branch: null,
|
|
40
|
+
upstream: null,
|
|
41
|
+
ahead: 0,
|
|
42
|
+
behind: 0,
|
|
43
|
+
head: null,
|
|
44
|
+
files: [],
|
|
45
|
+
clean: true,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Parse `git status --porcelain=v2 --branch --untracked-files=all`.
|
|
50
|
+
*
|
|
51
|
+
* Format reference: `man git-status`, "Porcelain Format Version 2".
|
|
52
|
+
*/
|
|
53
|
+
export function parsePorcelainV2(output: string): GitStatus {
|
|
54
|
+
const status: GitStatus = { ...EMPTY_GIT_STATUS, files: [] }
|
|
55
|
+
|
|
56
|
+
for (const rawLine of output.split("\n")) {
|
|
57
|
+
const line = rawLine.replace(/\r$/u, "")
|
|
58
|
+
if (line.length === 0) continue
|
|
59
|
+
|
|
60
|
+
if (line.startsWith("# branch.head ")) {
|
|
61
|
+
const value = line.slice("# branch.head ".length).trim()
|
|
62
|
+
status.branch = value === "(detached)" ? null : value
|
|
63
|
+
continue
|
|
64
|
+
}
|
|
65
|
+
if (line.startsWith("# branch.oid ")) {
|
|
66
|
+
const value = line.slice("# branch.oid ".length).trim()
|
|
67
|
+
status.head = value === "(initial)" ? null : value.slice(0, 7)
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
if (line.startsWith("# branch.upstream ")) {
|
|
71
|
+
status.upstream = line.slice("# branch.upstream ".length).trim()
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
if (line.startsWith("# branch.ab ")) {
|
|
75
|
+
for (const part of line.slice("# branch.ab ".length).trim().split(" ")) {
|
|
76
|
+
const value = Number.parseInt(part.slice(1), 10)
|
|
77
|
+
if (Number.isNaN(value)) continue
|
|
78
|
+
if (part.startsWith("+")) status.ahead = value
|
|
79
|
+
else if (part.startsWith("-")) status.behind = value
|
|
80
|
+
}
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
83
|
+
if (line.startsWith("# ")) continue
|
|
84
|
+
|
|
85
|
+
const kind = line[0]
|
|
86
|
+
if (kind === "1") {
|
|
87
|
+
// 1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>
|
|
88
|
+
const fields = splitFields(line, 8)
|
|
89
|
+
const code = fields[1] ?? ".."
|
|
90
|
+
const path = fields[8] ?? ""
|
|
91
|
+
if (path) status.files.push({ path, code, state: stateFromCode(code) })
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
if (kind === "2") {
|
|
95
|
+
// 2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path><TAB><origPath>
|
|
96
|
+
const fields = splitFields(line, 9)
|
|
97
|
+
const code = fields[1] ?? ".."
|
|
98
|
+
const rest = fields[9] ?? ""
|
|
99
|
+
const [path = "", origPath] = rest.split("\t")
|
|
100
|
+
if (path) status.files.push({ path, code, state: stateFromCode(code), origPath })
|
|
101
|
+
continue
|
|
102
|
+
}
|
|
103
|
+
if (kind === "u") {
|
|
104
|
+
const fields = splitFields(line, 10)
|
|
105
|
+
const path = fields[10] ?? ""
|
|
106
|
+
if (path) status.files.push({ path, code: fields[1] ?? "UU", state: "conflicted" })
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
if (kind === "?") {
|
|
110
|
+
const path = line.slice(2)
|
|
111
|
+
if (path) status.files.push({ path, code: "??", state: "untracked" })
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
status.clean = status.files.length === 0
|
|
117
|
+
return status
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Porcelain v2 codes are `XY` where X is the staged change and Y the worktree
|
|
122
|
+
* change; `.` means "unchanged".
|
|
123
|
+
*/
|
|
124
|
+
function stateFromCode(code: string): GitFileState {
|
|
125
|
+
const staged = code[0] ?? "."
|
|
126
|
+
return staged !== "." && staged !== "?" ? "staged" : "modified"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Split a porcelain line into `count` space-separated fields plus the remainder. */
|
|
130
|
+
function splitFields(line: string, count: number): string[] {
|
|
131
|
+
const fields: string[] = []
|
|
132
|
+
let index = 0
|
|
133
|
+
for (let i = 0; i < count; i += 1) {
|
|
134
|
+
const next = line.indexOf(" ", index)
|
|
135
|
+
if (next === -1) {
|
|
136
|
+
fields.push(line.slice(index))
|
|
137
|
+
return fields
|
|
138
|
+
}
|
|
139
|
+
fields.push(line.slice(index, next))
|
|
140
|
+
index = next + 1
|
|
141
|
+
}
|
|
142
|
+
fields.push(line.slice(index))
|
|
143
|
+
return fields
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** ASCII unit separator (0x1F), used as the field separator in our git log format. */
|
|
147
|
+
export const LOG_SEPARATOR = String.fromCharCode(31)
|
|
148
|
+
|
|
149
|
+
/** Format string matching {@link parseCommits}. */
|
|
150
|
+
export const LOG_FORMAT = ["%H", "%h", "%an", "%ar", "%s"].join(LOG_SEPARATOR)
|
|
151
|
+
|
|
152
|
+
/** Parse `git log --pretty=format:<LOG_FORMAT>`. */
|
|
153
|
+
export function parseCommits(output: string): GitCommit[] {
|
|
154
|
+
const commits: GitCommit[] = []
|
|
155
|
+
for (const line of output.split("\n")) {
|
|
156
|
+
if (line.trim() === "") continue
|
|
157
|
+
const [hash, shortHash, author, relativeDate, ...subject] = line.split(LOG_SEPARATOR)
|
|
158
|
+
if (!hash || !shortHash) continue
|
|
159
|
+
commits.push({
|
|
160
|
+
hash,
|
|
161
|
+
shortHash,
|
|
162
|
+
author: author ?? "",
|
|
163
|
+
relativeDate: relativeDate ?? "",
|
|
164
|
+
subject: subject.join(LOG_SEPARATOR),
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
return commits
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface GitCounts {
|
|
171
|
+
staged: number
|
|
172
|
+
modified: number
|
|
173
|
+
untracked: number
|
|
174
|
+
conflicted: number
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function countByState(files: readonly GitFile[]): GitCounts {
|
|
178
|
+
const counts: GitCounts = { staged: 0, modified: 0, untracked: 0, conflicted: 0 }
|
|
179
|
+
for (const file of files) counts[file.state] += 1
|
|
180
|
+
return counts
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Short summary such as `3 staged / 2 modified / 1 untracked`, or `clean`. */
|
|
184
|
+
export function summariseStatus(status: GitStatus): string {
|
|
185
|
+
if (status.clean) return "clean"
|
|
186
|
+
const counts = countByState(status.files)
|
|
187
|
+
const parts: string[] = []
|
|
188
|
+
if (counts.staged) parts.push(`${counts.staged} staged`)
|
|
189
|
+
if (counts.modified) parts.push(`${counts.modified} modified`)
|
|
190
|
+
if (counts.untracked) parts.push(`${counts.untracked} untracked`)
|
|
191
|
+
if (counts.conflicted) parts.push(`${counts.conflicted} conflicted`)
|
|
192
|
+
return parts.join(" / ")
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** `ahead 2, behind 1`, or `up to date` / `no upstream`. */
|
|
196
|
+
export function summariseTracking(status: GitStatus): string {
|
|
197
|
+
if (!status.upstream) return "no upstream"
|
|
198
|
+
if (status.ahead === 0 && status.behind === 0) return "up to date"
|
|
199
|
+
const parts: string[] = []
|
|
200
|
+
if (status.ahead) parts.push(`ahead ${status.ahead}`)
|
|
201
|
+
if (status.behind) parts.push(`behind ${status.behind}`)
|
|
202
|
+
return parts.join(", ")
|
|
203
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API for Dev Command Center.
|
|
3
|
+
*
|
|
4
|
+
* Project config files import `defineConfig` from here; embedders can drive the
|
|
5
|
+
* `App` directly or reuse individual pieces (process manager, service adapters,
|
|
6
|
+
* git parsing) without the UI.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { App, type AppOptions } from "./app/App.js"
|
|
10
|
+
export { createBuiltinCommands, createConfigCommands, createScriptCommands } from "./app/builtinCommands.js"
|
|
11
|
+
export { HELP_SECTIONS, VIEW_SHORTCUTS } from "./app/keymap.js"
|
|
12
|
+
export { theme, glyphs, processStatusStyle, serviceStateStyle } from "./app/theme.js"
|
|
13
|
+
|
|
14
|
+
export { CommandRegistry, type CommandContext, type CommandDefinition } from "./core/commands.js"
|
|
15
|
+
export {
|
|
16
|
+
defineConfig,
|
|
17
|
+
loadConfig,
|
|
18
|
+
validateConfig,
|
|
19
|
+
findConfigFile,
|
|
20
|
+
CONFIG_FILENAMES,
|
|
21
|
+
DEFAULT_REFRESH,
|
|
22
|
+
type CommandConfigEntry,
|
|
23
|
+
type DevccConfig,
|
|
24
|
+
type ResolvedConfig,
|
|
25
|
+
type ServiceConfigEntry,
|
|
26
|
+
} from "./core/config.js"
|
|
27
|
+
export { TypedEmitter, Subscriptions, type Unsubscribe } from "./core/events.js"
|
|
28
|
+
export {
|
|
29
|
+
Store,
|
|
30
|
+
VIEW_IDS,
|
|
31
|
+
createInitialState,
|
|
32
|
+
type AppState,
|
|
33
|
+
type Notification,
|
|
34
|
+
type ProjectState,
|
|
35
|
+
type UiState,
|
|
36
|
+
type ViewId,
|
|
37
|
+
} from "./core/state.js"
|
|
38
|
+
|
|
39
|
+
export { DependencyService, updateArgs, type DependencySnapshot } from "./deps/DependencyService.js"
|
|
40
|
+
export {
|
|
41
|
+
outdatedArgs,
|
|
42
|
+
parseOutdated,
|
|
43
|
+
severityBetween,
|
|
44
|
+
sortOutdated,
|
|
45
|
+
summariseOutdated,
|
|
46
|
+
supportsOutdatedJson,
|
|
47
|
+
type OutdatedPackage,
|
|
48
|
+
type UpdateSeverity,
|
|
49
|
+
} from "./deps/parse.js"
|
|
50
|
+
|
|
51
|
+
export { GitService, type GitSnapshot } from "./git/GitService.js"
|
|
52
|
+
export {
|
|
53
|
+
parseCommits,
|
|
54
|
+
parsePorcelainV2,
|
|
55
|
+
summariseStatus,
|
|
56
|
+
summariseTracking,
|
|
57
|
+
type GitCommit,
|
|
58
|
+
type GitFile,
|
|
59
|
+
type GitStatus,
|
|
60
|
+
} from "./git/parse.js"
|
|
61
|
+
|
|
62
|
+
export {
|
|
63
|
+
LogBuffer,
|
|
64
|
+
LogStore,
|
|
65
|
+
MAX_LOG_LINES,
|
|
66
|
+
type LogEntry,
|
|
67
|
+
type LogSource,
|
|
68
|
+
type LogStream,
|
|
69
|
+
} from "./logs/LogBuffer.js"
|
|
70
|
+
|
|
71
|
+
export {
|
|
72
|
+
canTransition,
|
|
73
|
+
crashBackoffDelay,
|
|
74
|
+
shouldAutoRestart,
|
|
75
|
+
DEFAULT_CRASH_POLICY,
|
|
76
|
+
createManagedProcess,
|
|
77
|
+
describeCommand,
|
|
78
|
+
isActive,
|
|
79
|
+
type ManagedProcess,
|
|
80
|
+
type ProcessDefinition,
|
|
81
|
+
type ProcessStatus,
|
|
82
|
+
type ReadinessCheck,
|
|
83
|
+
} from "./processes/ManagedProcess.js"
|
|
84
|
+
export { dependentsOf, planStartOrder, type StartPlan } from "./processes/graph.js"
|
|
85
|
+
export { ProcessManager, type ProcessManagerOptions } from "./processes/ProcessManager.js"
|
|
86
|
+
|
|
87
|
+
export {
|
|
88
|
+
buildProjectList,
|
|
89
|
+
discoverProjects,
|
|
90
|
+
isProjectDirectory,
|
|
91
|
+
readRecents,
|
|
92
|
+
recordRecent,
|
|
93
|
+
type ProjectEntry,
|
|
94
|
+
} from "./projects/ProjectRegistry.js"
|
|
95
|
+
|
|
96
|
+
export { ServiceManager } from "./services/ServiceManager.js"
|
|
97
|
+
export { DockerService, findComposeFile, parseComposePs } from "./services/docker.js"
|
|
98
|
+
export { PortService, detectNodeServices } from "./services/node.js"
|
|
99
|
+
export { OllamaService } from "./services/ollama.js"
|
|
100
|
+
export { PostgresService, parsePostgresUrl } from "./services/postgres.js"
|
|
101
|
+
export { RedisService, parseRedisUrl } from "./services/redis.js"
|
|
102
|
+
export type { ServiceAdapter, ServiceSnapshot, ServiceState, ServiceStatus } from "./services/types.js"
|
|
103
|
+
|
|
104
|
+
export { SystemMonitor, cpuUsageBetween, sampleCpu, type SystemMetrics } from "./system/SystemMonitor.js"
|
|
105
|
+
|
|
106
|
+
export { DevccError, errorMessage, toDevccError } from "./utils/errors.js"
|
|
107
|
+
export { Logger, createLogger, type LogLevel } from "./utils/logger.js"
|
|
108
|
+
export {
|
|
109
|
+
detectPackageManager,
|
|
110
|
+
readPackageJson,
|
|
111
|
+
readScripts,
|
|
112
|
+
runScriptArgs,
|
|
113
|
+
type PackageManager,
|
|
114
|
+
} from "./utils/packageManager.js"
|
|
115
|
+
export { findProjectRoot } from "./utils/paths.js"
|