@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,112 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
2
|
+
import { join } from "node:path"
|
|
3
|
+
|
|
4
|
+
export const PACKAGE_MANAGERS = ["pnpm", "yarn", "npm", "bun"] as const
|
|
5
|
+
export type PackageManager = (typeof PACKAGE_MANAGERS)[number]
|
|
6
|
+
|
|
7
|
+
export interface PackageManagerDetection {
|
|
8
|
+
manager: PackageManager
|
|
9
|
+
/** How the decision was made - surfaced in the Settings view. */
|
|
10
|
+
source: "packageManager-field" | "lockfile" | "user-agent" | "default"
|
|
11
|
+
/** The lockfile that decided it, when applicable. */
|
|
12
|
+
evidence?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const LOCKFILES: ReadonlyArray<{ file: string; manager: PackageManager }> = [
|
|
16
|
+
{ file: "pnpm-lock.yaml", manager: "pnpm" },
|
|
17
|
+
{ file: "bun.lock", manager: "bun" },
|
|
18
|
+
{ file: "bun.lockb", manager: "bun" },
|
|
19
|
+
{ file: "yarn.lock", manager: "yarn" },
|
|
20
|
+
{ file: "package-lock.json", manager: "npm" },
|
|
21
|
+
{ file: "npm-shrinkwrap.json", manager: "npm" },
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
function isPackageManager(value: string): value is PackageManager {
|
|
25
|
+
return (PACKAGE_MANAGERS as readonly string[]).includes(value)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parse the `packageManager` field of a package.json (corepack format,
|
|
30
|
+
* e.g. `pnpm@10.26.2+sha512...`).
|
|
31
|
+
*/
|
|
32
|
+
export function parsePackageManagerField(value: unknown): PackageManager | null {
|
|
33
|
+
if (typeof value !== "string") return null
|
|
34
|
+
const name = value.split("@")[0]?.trim().toLowerCase()
|
|
35
|
+
return name && isPackageManager(name) ? name : null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Parse the `npm_config_user_agent` env var, e.g. `pnpm/10.26.2 npm/? node/v24`. */
|
|
39
|
+
export function parseUserAgent(userAgent: string | undefined): PackageManager | null {
|
|
40
|
+
if (!userAgent) return null
|
|
41
|
+
const name = userAgent.split("/")[0]?.trim().toLowerCase()
|
|
42
|
+
return name && isPackageManager(name) ? name : null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface DetectPackageManagerInput {
|
|
46
|
+
rootDir: string
|
|
47
|
+
packageJson?: Record<string, unknown> | null
|
|
48
|
+
userAgent?: string | undefined
|
|
49
|
+
/** Injected for tests. */
|
|
50
|
+
fileExists?: (path: string) => boolean
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Detect the package manager, preferring explicit declarations over guesses:
|
|
55
|
+
* `packageManager` field -> lockfile -> the manager that launched us -> npm.
|
|
56
|
+
*/
|
|
57
|
+
export function detectPackageManager(input: DetectPackageManagerInput): PackageManagerDetection {
|
|
58
|
+
const fileExists = input.fileExists ?? existsSync
|
|
59
|
+
|
|
60
|
+
const declared = parsePackageManagerField(input.packageJson?.packageManager)
|
|
61
|
+
if (declared) return { manager: declared, source: "packageManager-field" }
|
|
62
|
+
|
|
63
|
+
for (const { file, manager } of LOCKFILES) {
|
|
64
|
+
if (fileExists(join(input.rootDir, file))) {
|
|
65
|
+
return { manager, source: "lockfile", evidence: file }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const fromAgent = parseUserAgent(input.userAgent)
|
|
70
|
+
if (fromAgent) return { manager: fromAgent, source: "user-agent" }
|
|
71
|
+
|
|
72
|
+
return { manager: "npm", source: "default" }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The argv needed to run a package.json script with the given manager. */
|
|
76
|
+
export function runScriptArgs(manager: PackageManager, script: string): string[] {
|
|
77
|
+
switch (manager) {
|
|
78
|
+
case "npm":
|
|
79
|
+
return ["run", script]
|
|
80
|
+
case "yarn":
|
|
81
|
+
return [script]
|
|
82
|
+
case "pnpm":
|
|
83
|
+
case "bun":
|
|
84
|
+
return ["run", script]
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Read and parse package.json, returning `null` when absent or invalid. */
|
|
89
|
+
export function readPackageJson(rootDir: string): Record<string, unknown> | null {
|
|
90
|
+
const path = join(rootDir, "package.json")
|
|
91
|
+
try {
|
|
92
|
+
if (!existsSync(path)) return null
|
|
93
|
+
const parsed: unknown = JSON.parse(readFileSync(path, "utf8"))
|
|
94
|
+
return typeof parsed === "object" && parsed !== null ? (parsed as Record<string, unknown>) : null
|
|
95
|
+
} catch {
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface PackageScript {
|
|
101
|
+
name: string
|
|
102
|
+
command: string
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Extract `scripts` from a parsed package.json as a stable, sorted-by-declaration list. */
|
|
106
|
+
export function readScripts(packageJson: Record<string, unknown> | null): PackageScript[] {
|
|
107
|
+
const scripts = packageJson?.scripts
|
|
108
|
+
if (typeof scripts !== "object" || scripts === null) return []
|
|
109
|
+
return Object.entries(scripts as Record<string, unknown>)
|
|
110
|
+
.filter((entry): entry is [string, string] => typeof entry[1] === "string")
|
|
111
|
+
.map(([name, command]) => ({ name, command }))
|
|
112
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { existsSync } from "node:fs"
|
|
2
|
+
import { dirname, join, resolve } from "node:path"
|
|
3
|
+
|
|
4
|
+
const PROJECT_MARKERS = ["package.json", ".git", "pnpm-workspace.yaml", "deno.json", "Cargo.toml"] as const
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Walk upwards from `startDir` looking for a project root marker.
|
|
8
|
+
* Falls back to `startDir` so the app always has somewhere to run.
|
|
9
|
+
*/
|
|
10
|
+
export function findProjectRoot(startDir: string = process.cwd()): string {
|
|
11
|
+
let current = resolve(startDir)
|
|
12
|
+
|
|
13
|
+
for (;;) {
|
|
14
|
+
for (const marker of PROJECT_MARKERS) {
|
|
15
|
+
if (existsSync(join(current, marker))) return current
|
|
16
|
+
}
|
|
17
|
+
const parent = dirname(current)
|
|
18
|
+
if (parent === current) return resolve(startDir)
|
|
19
|
+
current = parent
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Directory holding devcc's own runtime artefacts (logs, future state). */
|
|
24
|
+
export function devccDir(rootDir: string): string {
|
|
25
|
+
return join(rootDir, ".devcc")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Replace the home directory with `~` for compact display. */
|
|
29
|
+
export function tildify(path: string, home = process.env.HOME ?? ""): string {
|
|
30
|
+
if (home && path.startsWith(home)) return `~${path.slice(home.length)}`
|
|
31
|
+
return path
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Truncate a path from the left so the most specific segments stay visible. */
|
|
35
|
+
export function truncatePath(path: string, maxLength: number): string {
|
|
36
|
+
if (path.length <= maxLength) return path
|
|
37
|
+
if (maxLength <= 1) return path.slice(-maxLength)
|
|
38
|
+
return `…${path.slice(-(maxLength - 1))}`
|
|
39
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import type { KeyEvent } from "@opentui/core"
|
|
2
|
+
import { theme } from "../app/theme.js"
|
|
3
|
+
import type { KeyHint } from "../components/Footer.js"
|
|
4
|
+
import { RowList, type ListRow } from "../components/primitives.js"
|
|
5
|
+
import { chunk, dim, styled } from "../components/styled.js"
|
|
6
|
+
import { setSelection, type AppState, type ViewId } from "../core/state.js"
|
|
7
|
+
import type { OutdatedPackage, UpdateSeverity } from "../deps/parse.js"
|
|
8
|
+
import { summariseOutdated } from "../deps/parse.js"
|
|
9
|
+
import { fit, formatClock } from "../utils/format.js"
|
|
10
|
+
import { View, type ViewDeps } from "./View.js"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Rows rendered above the package list (summary + spacer).
|
|
14
|
+
*
|
|
15
|
+
* Fixed so the view and the palette command agree on what
|
|
16
|
+
* `ui.selection.dependencies` points at.
|
|
17
|
+
*/
|
|
18
|
+
export const DEPENDENCY_HEADER_ROWS = 2
|
|
19
|
+
|
|
20
|
+
/** Map a selection index to an index into the package list. */
|
|
21
|
+
export function selectedPackageIndex(selection: number): number {
|
|
22
|
+
return Math.max(0, selection - DEPENDENCY_HEADER_ROWS)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Outdated dependencies, grouped by how big the jump is.
|
|
27
|
+
*
|
|
28
|
+
* This is the only panel that never refreshes on a timer: it queries the
|
|
29
|
+
* registry over the network, so it runs when you ask it to (`r`) and not before.
|
|
30
|
+
*/
|
|
31
|
+
export class DependenciesView extends View {
|
|
32
|
+
readonly id: ViewId = "dependencies"
|
|
33
|
+
readonly title = "Dependencies"
|
|
34
|
+
|
|
35
|
+
private readonly list: RowList
|
|
36
|
+
private packages: OutdatedPackage[] = []
|
|
37
|
+
|
|
38
|
+
constructor(deps: ViewDeps) {
|
|
39
|
+
super(deps)
|
|
40
|
+
this.list = new RowList(deps.ctx, { flexGrow: 1 })
|
|
41
|
+
this.box.add(this.list.box)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
update(state: AppState): void {
|
|
45
|
+
const snapshot = state.dependencies
|
|
46
|
+
this.packages = snapshot.packages
|
|
47
|
+
|
|
48
|
+
const width = Math.max(40, state.ui.width - (state.ui.sidebarVisible ? 26 : 4))
|
|
49
|
+
const nameWidth = Math.min(34, Math.max(14, Math.floor(width * 0.3)))
|
|
50
|
+
const rows: ListRow[] = []
|
|
51
|
+
|
|
52
|
+
rows.push({ key: "__summary", disabled: true, content: this.summaryRow(state) })
|
|
53
|
+
rows.push({ key: "__blank", disabled: true, content: "" })
|
|
54
|
+
|
|
55
|
+
// A status row only appears when there is no package list, which keeps the
|
|
56
|
+
// header height constant at DEPENDENCY_HEADER_ROWS whenever selection matters.
|
|
57
|
+
if (this.packages.length > 0 && snapshot.error) {
|
|
58
|
+
rows[0] = { key: "__summary", disabled: true, content: this.summaryRow(state) }
|
|
59
|
+
} else if (!snapshot.supported) {
|
|
60
|
+
rows.push({
|
|
61
|
+
key: "__unsupported",
|
|
62
|
+
disabled: true,
|
|
63
|
+
content: styled(
|
|
64
|
+
chunk(`⚠ ${snapshot.error ?? "not supported by this package manager"}`, theme.warning),
|
|
65
|
+
),
|
|
66
|
+
})
|
|
67
|
+
} else if (snapshot.error) {
|
|
68
|
+
rows.push({
|
|
69
|
+
key: "__error",
|
|
70
|
+
disabled: true,
|
|
71
|
+
content: styled(chunk(`⚠ ${snapshot.error}`, theme.danger)),
|
|
72
|
+
})
|
|
73
|
+
} else if (snapshot.checkedAt === null) {
|
|
74
|
+
rows.push({
|
|
75
|
+
key: "__prompt",
|
|
76
|
+
disabled: true,
|
|
77
|
+
content: styled(dim("Press r to check the registry for updates.")),
|
|
78
|
+
})
|
|
79
|
+
} else if (this.packages.length === 0) {
|
|
80
|
+
rows.push({
|
|
81
|
+
key: "__uptodate",
|
|
82
|
+
disabled: true,
|
|
83
|
+
content: styled(chunk("✓ everything is up to date", theme.success)),
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const entry of this.packages) {
|
|
88
|
+
rows.push({
|
|
89
|
+
key: entry.name,
|
|
90
|
+
content: styled(
|
|
91
|
+
chunk(fit(severityLabel(entry.severity), 7), severityColor(entry.severity)),
|
|
92
|
+
chunk(fit(entry.name, nameWidth), theme.textBright),
|
|
93
|
+
dim(fit(entry.current, 12)),
|
|
94
|
+
dim(fit("→", 2)),
|
|
95
|
+
chunk(fit(entry.latest, 12), severityColor(entry.severity)),
|
|
96
|
+
dim(fit(kindLabel(entry.kind), 6)),
|
|
97
|
+
entry.deprecated ? chunk("deprecated", theme.danger) : dim(entry.onlyLatest ? "outside range" : ""),
|
|
98
|
+
),
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
this.list.setRows(rows, state.ui.selection.dependencies)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
override handleKey(key: KeyEvent, state: AppState): boolean {
|
|
106
|
+
const selected = state.ui.selection.dependencies
|
|
107
|
+
const first = this.firstPackageRow()
|
|
108
|
+
const last = first + this.packages.length - 1
|
|
109
|
+
|
|
110
|
+
switch (key.name) {
|
|
111
|
+
case "up":
|
|
112
|
+
case "k":
|
|
113
|
+
if (this.packages.length > 0) {
|
|
114
|
+
setSelection(this.deps.app.store, "dependencies", Math.max(first, selected - 1))
|
|
115
|
+
}
|
|
116
|
+
return true
|
|
117
|
+
case "down":
|
|
118
|
+
case "j":
|
|
119
|
+
if (this.packages.length > 0) {
|
|
120
|
+
setSelection(this.deps.app.store, "dependencies", Math.min(last, Math.max(first, selected + 1)))
|
|
121
|
+
}
|
|
122
|
+
return true
|
|
123
|
+
case "r":
|
|
124
|
+
void this.deps.commands.run("deps.check", this.deps.app)
|
|
125
|
+
return true
|
|
126
|
+
case "u":
|
|
127
|
+
void this.updateSelected(selected)
|
|
128
|
+
return true
|
|
129
|
+
default:
|
|
130
|
+
return false
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
override hints(): KeyHint[] {
|
|
135
|
+
return [
|
|
136
|
+
{ keys: "↑↓/jk", label: "select" },
|
|
137
|
+
{ keys: "r", label: "check registry" },
|
|
138
|
+
{ keys: "u", label: "update package" },
|
|
139
|
+
{ keys: "/", label: "commands" },
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
override onActivate(state: AppState): void {
|
|
144
|
+
// Land on the first real package rather than the summary header.
|
|
145
|
+
if (state.ui.selection.dependencies < this.firstPackageRow()) {
|
|
146
|
+
setSelection(this.deps.app.store, "dependencies", this.firstPackageRow())
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private firstPackageRow(): number {
|
|
151
|
+
return DEPENDENCY_HEADER_ROWS
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private summaryRow(state: AppState) {
|
|
155
|
+
const snapshot = state.dependencies
|
|
156
|
+
if (snapshot.checking) return styled(chunk("checking the registry…", theme.warning))
|
|
157
|
+
|
|
158
|
+
const counts = summariseOutdated(snapshot.packages)
|
|
159
|
+
const checked = snapshot.checkedAt ? formatClock(snapshot.checkedAt) : "never"
|
|
160
|
+
return styled(
|
|
161
|
+
chunk(state.project.packageManager.manager, theme.text),
|
|
162
|
+
dim(" · "),
|
|
163
|
+
chunk(`${counts.total} outdated`, counts.total > 0 ? theme.warning : theme.success),
|
|
164
|
+
dim(" · "),
|
|
165
|
+
chunk(`${counts.major} major`, counts.major > 0 ? theme.danger : theme.textDim),
|
|
166
|
+
dim(` · ${counts.minor} minor · ${counts.patch} patch · checked ${checked}`),
|
|
167
|
+
snapshot.error ? chunk(` · ⚠ ${snapshot.error}`, theme.danger) : dim(""),
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private async updateSelected(index: number): Promise<void> {
|
|
172
|
+
const entry = this.packages[selectedPackageIndex(index)]
|
|
173
|
+
if (!entry) return
|
|
174
|
+
await this.deps.app.updateDependency(entry.name, entry.latest)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function severityLabel(severity: UpdateSeverity): string {
|
|
179
|
+
return severity === "unknown" ? "?" : severity
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function severityColor(severity: UpdateSeverity): string {
|
|
183
|
+
switch (severity) {
|
|
184
|
+
case "major":
|
|
185
|
+
return theme.danger
|
|
186
|
+
case "minor":
|
|
187
|
+
return theme.warning
|
|
188
|
+
case "patch":
|
|
189
|
+
return theme.success
|
|
190
|
+
case "unknown":
|
|
191
|
+
return theme.textDim
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function kindLabel(kind: OutdatedPackage["kind"]): string {
|
|
196
|
+
switch (kind) {
|
|
197
|
+
case "devDependencies":
|
|
198
|
+
return "dev"
|
|
199
|
+
case "peerDependencies":
|
|
200
|
+
return "peer"
|
|
201
|
+
case "optionalDependencies":
|
|
202
|
+
return "opt"
|
|
203
|
+
case "dependencies":
|
|
204
|
+
return "prod"
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import type { KeyEvent } from "@opentui/core"
|
|
2
|
+
import { theme } from "../app/theme.js"
|
|
3
|
+
import type { KeyHint } from "../components/Footer.js"
|
|
4
|
+
import { RowList, type ListRow } from "../components/primitives.js"
|
|
5
|
+
import { chunk, dim, styled } from "../components/styled.js"
|
|
6
|
+
import { setSelection, type AppState, type ViewId } from "../core/state.js"
|
|
7
|
+
import { summariseStatus, summariseTracking, type GitFile } from "../git/parse.js"
|
|
8
|
+
import { errorMessage } from "../utils/errors.js"
|
|
9
|
+
import { fit, truncate } from "../utils/format.js"
|
|
10
|
+
import { View, type ViewDeps } from "./View.js"
|
|
11
|
+
|
|
12
|
+
interface GitRow {
|
|
13
|
+
file?: GitFile
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Git dashboard.
|
|
18
|
+
*
|
|
19
|
+
* Read operations refresh on a timer; every write is explicit, and the three
|
|
20
|
+
* that cannot be undone locally - discard, push, and hard operations from the
|
|
21
|
+
* palette - require a confirmation.
|
|
22
|
+
*/
|
|
23
|
+
export class GitView extends View {
|
|
24
|
+
readonly id: ViewId = "git"
|
|
25
|
+
readonly title = "Git"
|
|
26
|
+
|
|
27
|
+
private readonly list: RowList
|
|
28
|
+
private rowMeta: GitRow[] = []
|
|
29
|
+
|
|
30
|
+
constructor(deps: ViewDeps) {
|
|
31
|
+
super(deps)
|
|
32
|
+
this.list = new RowList(deps.ctx, { flexGrow: 1 })
|
|
33
|
+
this.box.add(this.list.box)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
update(state: AppState): void {
|
|
37
|
+
const rows: ListRow[] = []
|
|
38
|
+
const meta: GitRow[] = []
|
|
39
|
+
const width = Math.max(40, state.ui.width - (state.ui.sidebarVisible ? 26 : 4))
|
|
40
|
+
const git = state.git
|
|
41
|
+
|
|
42
|
+
if (git.lastRefreshedAt === null) {
|
|
43
|
+
rows.push({ key: "__checking", disabled: true, content: styled(dim("checking git status…")) })
|
|
44
|
+
} else if (!git.available) {
|
|
45
|
+
rows.push({
|
|
46
|
+
key: "__nogit",
|
|
47
|
+
disabled: true,
|
|
48
|
+
content: styled(chunk("git is not installed", theme.danger)),
|
|
49
|
+
})
|
|
50
|
+
} else if (!git.isRepository) {
|
|
51
|
+
rows.push({
|
|
52
|
+
key: "__norepo",
|
|
53
|
+
disabled: true,
|
|
54
|
+
content: styled(dim("This directory is not a git repository")),
|
|
55
|
+
})
|
|
56
|
+
} else {
|
|
57
|
+
const branch = git.status.branch ?? `detached @ ${git.status.head ?? "?"}`
|
|
58
|
+
rows.push({
|
|
59
|
+
key: "__branch",
|
|
60
|
+
disabled: true,
|
|
61
|
+
content: styled(
|
|
62
|
+
dim(fit("branch", 12)),
|
|
63
|
+
chunk(branch, theme.textBright),
|
|
64
|
+
dim(` ${summariseTracking(git.status)}`),
|
|
65
|
+
),
|
|
66
|
+
})
|
|
67
|
+
rows.push({
|
|
68
|
+
key: "__status",
|
|
69
|
+
disabled: true,
|
|
70
|
+
content: styled(
|
|
71
|
+
dim(fit("status", 12)),
|
|
72
|
+
chunk(summariseStatus(git.status), git.status.clean ? theme.success : theme.warning),
|
|
73
|
+
),
|
|
74
|
+
})
|
|
75
|
+
meta.push({}, {})
|
|
76
|
+
|
|
77
|
+
rows.push({ key: "__blank1", disabled: true, content: "" })
|
|
78
|
+
meta.push({})
|
|
79
|
+
rows.push({ key: "__files", disabled: true, content: styled(chunk("CHANGES", theme.accent)) })
|
|
80
|
+
meta.push({})
|
|
81
|
+
|
|
82
|
+
if (git.status.files.length === 0) {
|
|
83
|
+
rows.push({ key: "__clean", disabled: true, content: styled(dim(" working tree clean")) })
|
|
84
|
+
meta.push({})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const file of git.status.files) {
|
|
88
|
+
rows.push({
|
|
89
|
+
key: `file-${file.path}`,
|
|
90
|
+
content: styled(
|
|
91
|
+
chunk(fit(file.code, 4), stateColor(file.state)),
|
|
92
|
+
chunk(fit(file.state, 11), stateColor(file.state)),
|
|
93
|
+
chunk(truncate(file.path, Math.max(10, width - 20)), theme.text),
|
|
94
|
+
),
|
|
95
|
+
})
|
|
96
|
+
meta.push({ file })
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (git.commits.length > 0 && state.ui.height > 22) {
|
|
100
|
+
rows.push({ key: "__blank2", disabled: true, content: "" })
|
|
101
|
+
meta.push({})
|
|
102
|
+
rows.push({
|
|
103
|
+
key: "__commits",
|
|
104
|
+
disabled: true,
|
|
105
|
+
content: styled(chunk("RECENT COMMITS", theme.accent)),
|
|
106
|
+
})
|
|
107
|
+
meta.push({})
|
|
108
|
+
for (const commit of git.commits.slice(0, 6)) {
|
|
109
|
+
rows.push({
|
|
110
|
+
key: `commit-${commit.hash}`,
|
|
111
|
+
disabled: true,
|
|
112
|
+
content: styled(
|
|
113
|
+
dim(fit(commit.shortHash, 9)),
|
|
114
|
+
chunk(truncate(commit.subject, Math.max(10, width - 34)), theme.text),
|
|
115
|
+
dim(` ${commit.relativeDate}`),
|
|
116
|
+
),
|
|
117
|
+
})
|
|
118
|
+
meta.push({})
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (git.error) {
|
|
124
|
+
rows.push({ key: "__error", disabled: true, content: styled(chunk(`⚠ ${git.error}`, theme.danger)) })
|
|
125
|
+
meta.push({})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.rowMeta = meta
|
|
129
|
+
this.list.setRows(rows, state.ui.selection.git)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
override handleKey(key: KeyEvent, state: AppState): boolean {
|
|
133
|
+
const selected = state.ui.selection.git
|
|
134
|
+
|
|
135
|
+
switch (key.name) {
|
|
136
|
+
case "up":
|
|
137
|
+
case "k":
|
|
138
|
+
this.move(selected, -1)
|
|
139
|
+
return true
|
|
140
|
+
case "down":
|
|
141
|
+
case "j":
|
|
142
|
+
this.move(selected, 1)
|
|
143
|
+
return true
|
|
144
|
+
case "r":
|
|
145
|
+
void this.refresh()
|
|
146
|
+
return true
|
|
147
|
+
case "s":
|
|
148
|
+
void this.stage(selected)
|
|
149
|
+
return true
|
|
150
|
+
case "u":
|
|
151
|
+
void this.unstage(selected)
|
|
152
|
+
return true
|
|
153
|
+
case "d":
|
|
154
|
+
void this.discard(selected)
|
|
155
|
+
return true
|
|
156
|
+
case "c":
|
|
157
|
+
void this.deps.commands.run("git.commit", this.deps.app)
|
|
158
|
+
return true
|
|
159
|
+
case "p":
|
|
160
|
+
// Shift+P arrives as name "p" with `shift` set.
|
|
161
|
+
void this.deps.commands.run(key.shift ? "git.push" : "git.pull", this.deps.app)
|
|
162
|
+
return true
|
|
163
|
+
case "f":
|
|
164
|
+
void this.deps.commands.run("git.fetch", this.deps.app)
|
|
165
|
+
return true
|
|
166
|
+
default:
|
|
167
|
+
return false
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
override hints(): KeyHint[] {
|
|
172
|
+
return [
|
|
173
|
+
{ keys: "↑↓/jk", label: "select" },
|
|
174
|
+
{ keys: "s/u", label: "stage/unstage" },
|
|
175
|
+
{ keys: "d", label: "discard" },
|
|
176
|
+
{ keys: "c", label: "commit" },
|
|
177
|
+
{ keys: "p/P", label: "pull/push" },
|
|
178
|
+
{ keys: "f", label: "fetch" },
|
|
179
|
+
{ keys: "r", label: "refresh" },
|
|
180
|
+
]
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private move(from: number, direction: 1 | -1): void {
|
|
184
|
+
let index = from
|
|
185
|
+
for (let steps = 0; steps < this.rowMeta.length; steps += 1) {
|
|
186
|
+
index += direction
|
|
187
|
+
if (index < 0 || index >= this.rowMeta.length) return
|
|
188
|
+
if (this.rowMeta[index]?.file) {
|
|
189
|
+
setSelection(this.deps.app.store, "git", index)
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private selectedFile(index: number): GitFile | undefined {
|
|
196
|
+
return this.rowMeta[index]?.file
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private async refresh(): Promise<void> {
|
|
200
|
+
await this.deps.app.git.refresh()
|
|
201
|
+
this.deps.app.notify("info", "Git status refreshed")
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private async stage(index: number): Promise<void> {
|
|
205
|
+
const file = this.selectedFile(index)
|
|
206
|
+
if (!file) return
|
|
207
|
+
try {
|
|
208
|
+
await this.deps.app.git.stage([file.path])
|
|
209
|
+
this.deps.app.notify("success", `Staged ${file.path}`)
|
|
210
|
+
} catch (error) {
|
|
211
|
+
this.deps.app.notify("error", errorMessage(error))
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private async unstage(index: number): Promise<void> {
|
|
216
|
+
const file = this.selectedFile(index)
|
|
217
|
+
if (!file) return
|
|
218
|
+
try {
|
|
219
|
+
await this.deps.app.git.unstage([file.path])
|
|
220
|
+
this.deps.app.notify("success", `Unstaged ${file.path}`)
|
|
221
|
+
} catch (error) {
|
|
222
|
+
this.deps.app.notify("error", errorMessage(error))
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Destructive: always confirmed, never applied to untracked files. */
|
|
227
|
+
private async discard(index: number): Promise<void> {
|
|
228
|
+
const file = this.selectedFile(index)
|
|
229
|
+
if (!file) return
|
|
230
|
+
if (file.state === "untracked") {
|
|
231
|
+
this.deps.app.notify("warn", "Refusing to delete untracked files; remove them manually")
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
const confirmed = await this.deps.app.confirm(
|
|
235
|
+
`Discard changes in ${file.path}?`,
|
|
236
|
+
"This runs `git restore` and permanently throws away your local edits to this file.",
|
|
237
|
+
)
|
|
238
|
+
if (!confirmed) return
|
|
239
|
+
try {
|
|
240
|
+
await this.deps.app.git.discard([file.path])
|
|
241
|
+
this.deps.app.notify("success", `Discarded changes in ${file.path}`)
|
|
242
|
+
} catch (error) {
|
|
243
|
+
this.deps.app.notify("error", errorMessage(error))
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function stateColor(state: GitFile["state"]): string {
|
|
249
|
+
switch (state) {
|
|
250
|
+
case "staged":
|
|
251
|
+
return theme.success
|
|
252
|
+
case "modified":
|
|
253
|
+
return theme.warning
|
|
254
|
+
case "untracked":
|
|
255
|
+
return theme.textDim
|
|
256
|
+
case "conflicted":
|
|
257
|
+
return theme.danger
|
|
258
|
+
}
|
|
259
|
+
}
|