@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,236 @@
|
|
|
1
|
+
import type { GitSnapshot } from "../git/GitService.js"
|
|
2
|
+
import { EMPTY_GIT_SNAPSHOT } from "../git/GitService.js"
|
|
3
|
+
import type { LogSource } from "../logs/LogBuffer.js"
|
|
4
|
+
import type { ManagedProcess } from "../processes/ManagedProcess.js"
|
|
5
|
+
import type { DependencySnapshot } from "../deps/DependencyService.js"
|
|
6
|
+
import { EMPTY_DEPENDENCY_SNAPSHOT } from "../deps/DependencyService.js"
|
|
7
|
+
import type { ProjectEntry } from "../projects/ProjectRegistry.js"
|
|
8
|
+
import type { ServiceSnapshot } from "../services/types.js"
|
|
9
|
+
import type { HostInfo, SystemMetrics, ToolVersions } from "../system/SystemMonitor.js"
|
|
10
|
+
import type { PackageManagerDetection } from "../utils/packageManager.js"
|
|
11
|
+
|
|
12
|
+
export const VIEW_IDS = [
|
|
13
|
+
"overview",
|
|
14
|
+
"processes",
|
|
15
|
+
"services",
|
|
16
|
+
"scripts",
|
|
17
|
+
"git",
|
|
18
|
+
"logs",
|
|
19
|
+
"dependencies",
|
|
20
|
+
"system",
|
|
21
|
+
"settings",
|
|
22
|
+
] as const
|
|
23
|
+
|
|
24
|
+
export type ViewId = (typeof VIEW_IDS)[number]
|
|
25
|
+
|
|
26
|
+
export type OverlayKind = "none" | "palette" | "help" | "confirm" | "prompt" | "projects" | "inspect"
|
|
27
|
+
|
|
28
|
+
export type NotificationLevel = "info" | "success" | "warn" | "error"
|
|
29
|
+
|
|
30
|
+
export interface Notification {
|
|
31
|
+
id: number
|
|
32
|
+
level: NotificationLevel
|
|
33
|
+
message: string
|
|
34
|
+
detail?: string
|
|
35
|
+
createdAt: Date
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ProjectState {
|
|
39
|
+
name: string
|
|
40
|
+
rootDir: string
|
|
41
|
+
packageManager: PackageManagerDetection
|
|
42
|
+
configPath: string | null
|
|
43
|
+
configIssues: string[]
|
|
44
|
+
host: HostInfo
|
|
45
|
+
tools: ToolVersions
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ScriptRun {
|
|
49
|
+
scriptName: string
|
|
50
|
+
/** Process id in the ProcessManager, also the log source id. */
|
|
51
|
+
processId: string
|
|
52
|
+
startedAt: Date
|
|
53
|
+
finishedAt?: Date
|
|
54
|
+
exitCode?: number
|
|
55
|
+
status: "running" | "succeeded" | "failed"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ScriptsState {
|
|
59
|
+
available: { name: string; command: string }[]
|
|
60
|
+
runs: Record<string, ScriptRun>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface UiState {
|
|
64
|
+
view: ViewId
|
|
65
|
+
overlay: OverlayKind
|
|
66
|
+
/** Selected row index per view; keeps position when switching back. */
|
|
67
|
+
selection: Record<ViewId, number>
|
|
68
|
+
logSource: string | null
|
|
69
|
+
logFollow: boolean
|
|
70
|
+
/** Terminal size; views degrade gracefully below the thresholds. */
|
|
71
|
+
width: number
|
|
72
|
+
height: number
|
|
73
|
+
sidebarVisible: boolean
|
|
74
|
+
statusMessage: string | null
|
|
75
|
+
clock: Date
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface AppState {
|
|
79
|
+
ui: UiState
|
|
80
|
+
project: ProjectState
|
|
81
|
+
processes: ManagedProcess[]
|
|
82
|
+
services: ServiceSnapshot[]
|
|
83
|
+
scripts: ScriptsState
|
|
84
|
+
git: GitSnapshot
|
|
85
|
+
dependencies: DependencySnapshot
|
|
86
|
+
system: SystemMetrics | null
|
|
87
|
+
logSources: LogSource[]
|
|
88
|
+
notifications: Notification[]
|
|
89
|
+
/** Projects devcc can switch to, including the current one. */
|
|
90
|
+
projects: ProjectEntry[]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Terminal narrower than this hides the sidebar. */
|
|
94
|
+
export const NARROW_WIDTH = 76
|
|
95
|
+
/** Terminal shorter than this drops decorative rows. */
|
|
96
|
+
export const SHORT_HEIGHT = 20
|
|
97
|
+
|
|
98
|
+
export function createInitialSelection(): Record<ViewId, number> {
|
|
99
|
+
return Object.fromEntries(VIEW_IDS.map((id) => [id, 0])) as Record<ViewId, number>
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function createInitialState(project: ProjectState, width: number, height: number): AppState {
|
|
103
|
+
return {
|
|
104
|
+
ui: {
|
|
105
|
+
view: "overview",
|
|
106
|
+
overlay: "none",
|
|
107
|
+
selection: createInitialSelection(),
|
|
108
|
+
logSource: null,
|
|
109
|
+
logFollow: true,
|
|
110
|
+
width,
|
|
111
|
+
height,
|
|
112
|
+
sidebarVisible: width >= NARROW_WIDTH,
|
|
113
|
+
statusMessage: null,
|
|
114
|
+
clock: new Date(),
|
|
115
|
+
},
|
|
116
|
+
project,
|
|
117
|
+
processes: [],
|
|
118
|
+
services: [],
|
|
119
|
+
scripts: { available: [], runs: {} },
|
|
120
|
+
git: EMPTY_GIT_SNAPSHOT,
|
|
121
|
+
dependencies: EMPTY_DEPENDENCY_SNAPSHOT,
|
|
122
|
+
system: null,
|
|
123
|
+
logSources: [],
|
|
124
|
+
notifications: [],
|
|
125
|
+
projects: [],
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export type Listener<T> = (value: T, previous: T) => void
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Minimal observable store.
|
|
133
|
+
*
|
|
134
|
+
* `watch()` subscribes to a slice: the listener only fires when that slice
|
|
135
|
+
* actually changes, so a 1 Hz CPU sample does not repaint the git panel.
|
|
136
|
+
*/
|
|
137
|
+
export class Store<S extends object> {
|
|
138
|
+
private state: S
|
|
139
|
+
private readonly watchers = new Set<{
|
|
140
|
+
select: (state: S) => unknown
|
|
141
|
+
equals: (a: unknown, b: unknown) => boolean
|
|
142
|
+
listener: Listener<never>
|
|
143
|
+
last: unknown
|
|
144
|
+
}>()
|
|
145
|
+
|
|
146
|
+
constructor(initialState: S) {
|
|
147
|
+
this.state = initialState
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
getState(): S {
|
|
151
|
+
return this.state
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Replace state via an updater; watchers are notified with the new slices. */
|
|
155
|
+
setState(updater: (state: S) => S): void {
|
|
156
|
+
const previous = this.state
|
|
157
|
+
const next = updater(previous)
|
|
158
|
+
if (next === previous) return
|
|
159
|
+
this.state = next
|
|
160
|
+
this.notify()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Patch top-level keys. Reference-equal patches are ignored. */
|
|
164
|
+
patch(patch: Partial<S>): void {
|
|
165
|
+
this.setState((state) => {
|
|
166
|
+
let changed = false
|
|
167
|
+
for (const key of Object.keys(patch) as (keyof S)[]) {
|
|
168
|
+
if (patch[key] !== state[key]) {
|
|
169
|
+
changed = true
|
|
170
|
+
break
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return changed ? { ...state, ...patch } : state
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
watch<T>(
|
|
178
|
+
select: (state: S) => T,
|
|
179
|
+
listener: Listener<T>,
|
|
180
|
+
equals: (a: T, b: T) => boolean = Object.is,
|
|
181
|
+
): () => void {
|
|
182
|
+
const entry = {
|
|
183
|
+
select: select as (state: S) => unknown,
|
|
184
|
+
equals: equals as (a: unknown, b: unknown) => boolean,
|
|
185
|
+
listener: listener as unknown as Listener<never>,
|
|
186
|
+
last: select(this.state) as unknown,
|
|
187
|
+
}
|
|
188
|
+
this.watchers.add(entry)
|
|
189
|
+
return () => {
|
|
190
|
+
this.watchers.delete(entry)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private notify(): void {
|
|
195
|
+
for (const entry of [...this.watchers]) {
|
|
196
|
+
const value = entry.select(this.state)
|
|
197
|
+
if (entry.equals(value, entry.last)) continue
|
|
198
|
+
const last = entry.last
|
|
199
|
+
entry.last = value
|
|
200
|
+
;(entry.listener as unknown as Listener<unknown>)(value, last)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export type AppStore = Store<AppState>
|
|
206
|
+
|
|
207
|
+
/** Shallow array equality, for watching lists of snapshots. */
|
|
208
|
+
export function shallowArrayEqual<T>(a: readonly T[], b: readonly T[]): boolean {
|
|
209
|
+
if (a === b) return true
|
|
210
|
+
if (a.length !== b.length) return false
|
|
211
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
212
|
+
if (a[i] !== b[i]) return false
|
|
213
|
+
}
|
|
214
|
+
return true
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Convenience updaters used by the app layer. */
|
|
218
|
+
export function updateUi(store: AppStore, patch: Partial<UiState>): void {
|
|
219
|
+
store.setState((state) => {
|
|
220
|
+
let changed = false
|
|
221
|
+
for (const key of Object.keys(patch) as (keyof UiState)[]) {
|
|
222
|
+
if (patch[key] !== state.ui[key]) {
|
|
223
|
+
changed = true
|
|
224
|
+
break
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return changed ? { ...state, ui: { ...state.ui, ...patch } } : state
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function setSelection(store: AppStore, view: ViewId, index: number): void {
|
|
232
|
+
store.setState((state) => {
|
|
233
|
+
if (state.ui.selection[view] === index) return state
|
|
234
|
+
return { ...state, ui: { ...state.ui, selection: { ...state.ui.selection, [view]: index } } }
|
|
235
|
+
})
|
|
236
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { TypedEmitter } from "../core/events.js"
|
|
2
|
+
import { DevccError } from "../utils/errors.js"
|
|
3
|
+
import { exec } from "../utils/exec.js"
|
|
4
|
+
import type { ScopedLogger } from "../utils/logger.js"
|
|
5
|
+
import type { PackageManager } from "../utils/packageManager.js"
|
|
6
|
+
import {
|
|
7
|
+
outdatedArgs,
|
|
8
|
+
parseOutdated,
|
|
9
|
+
sortOutdated,
|
|
10
|
+
supportsOutdatedJson,
|
|
11
|
+
type OutdatedPackage,
|
|
12
|
+
} from "./parse.js"
|
|
13
|
+
|
|
14
|
+
export interface DependencySnapshot {
|
|
15
|
+
packages: OutdatedPackage[]
|
|
16
|
+
/** Null until the first check has run. */
|
|
17
|
+
checkedAt: Date | null
|
|
18
|
+
checking: boolean
|
|
19
|
+
/** Set when the check could not run at all (unsupported manager, no network). */
|
|
20
|
+
error: string | null
|
|
21
|
+
supported: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const EMPTY_DEPENDENCY_SNAPSHOT: DependencySnapshot = {
|
|
25
|
+
packages: [],
|
|
26
|
+
checkedAt: null,
|
|
27
|
+
checking: false,
|
|
28
|
+
error: null,
|
|
29
|
+
supported: true,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type DependencyServiceEvents = {
|
|
33
|
+
"deps:changed": [DependencySnapshot]
|
|
34
|
+
"deps:error": [DevccError]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface DependencyServiceOptions {
|
|
38
|
+
rootDir: string
|
|
39
|
+
manager: PackageManager
|
|
40
|
+
logger: ScopedLogger
|
|
41
|
+
/** Registry lookups are slow; the default budget is generous. */
|
|
42
|
+
timeoutMs?: number
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Checks for outdated dependencies via the project's own package manager.
|
|
47
|
+
*
|
|
48
|
+
* This is the one panel that never polls: it talks to the registry, so it runs
|
|
49
|
+
* only when asked. `outdated` exits non-zero when it finds updates, which is a
|
|
50
|
+
* result and not a failure - only an empty parse plus a non-zero exit is an
|
|
51
|
+
* error worth reporting.
|
|
52
|
+
*/
|
|
53
|
+
export class DependencyService extends TypedEmitter<DependencyServiceEvents> {
|
|
54
|
+
private snapshot: DependencySnapshot = { ...EMPTY_DEPENDENCY_SNAPSHOT }
|
|
55
|
+
private inFlight: Promise<DependencySnapshot> | null = null
|
|
56
|
+
private readonly timeoutMs: number
|
|
57
|
+
|
|
58
|
+
constructor(private readonly options: DependencyServiceOptions) {
|
|
59
|
+
super()
|
|
60
|
+
this.timeoutMs = options.timeoutMs ?? 90_000
|
|
61
|
+
this.snapshot = { ...EMPTY_DEPENDENCY_SNAPSHOT, supported: supportsOutdatedJson(options.manager) }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getSnapshot(): DependencySnapshot {
|
|
65
|
+
return this.snapshot
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Run the check. Concurrent calls share one run. */
|
|
69
|
+
async check(): Promise<DependencySnapshot> {
|
|
70
|
+
if (this.inFlight) return this.inFlight
|
|
71
|
+
this.inFlight = this.run().finally(() => {
|
|
72
|
+
this.inFlight = null
|
|
73
|
+
})
|
|
74
|
+
return this.inFlight
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private async run(): Promise<DependencySnapshot> {
|
|
78
|
+
const { manager, rootDir } = this.options
|
|
79
|
+
|
|
80
|
+
if (!supportsOutdatedJson(manager)) {
|
|
81
|
+
return this.publish({
|
|
82
|
+
...this.snapshot,
|
|
83
|
+
supported: false,
|
|
84
|
+
checking: false,
|
|
85
|
+
checkedAt: new Date(),
|
|
86
|
+
error: `${manager} does not provide machine-readable "outdated" output`,
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.publish({ ...this.snapshot, checking: true, error: null })
|
|
91
|
+
|
|
92
|
+
const result = await exec(manager, outdatedArgs(manager), {
|
|
93
|
+
cwd: rootDir,
|
|
94
|
+
timeoutMs: this.timeoutMs,
|
|
95
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
if (result.spawnError) {
|
|
99
|
+
return this.fail(`${manager} not found on PATH`)
|
|
100
|
+
}
|
|
101
|
+
if (result.timedOut) {
|
|
102
|
+
return this.fail(`${manager} outdated timed out`)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const packages = sortOutdated(parseOutdated(manager, result.stdout))
|
|
106
|
+
|
|
107
|
+
// `outdated` exits 1 when updates exist, so only treat a non-zero exit with
|
|
108
|
+
// nothing parsed as a real failure.
|
|
109
|
+
if (packages.length === 0 && !result.ok && result.stdout.trim() === "") {
|
|
110
|
+
const detail = result.stderr.trim()
|
|
111
|
+
if (detail !== "") {
|
|
112
|
+
this.options.logger.warn("outdated check failed", { code: result.code, detail })
|
|
113
|
+
return this.fail(detail.split("\n")[0] ?? "outdated check failed")
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.options.logger.info("outdated check complete", { count: packages.length })
|
|
118
|
+
return this.publish({
|
|
119
|
+
packages,
|
|
120
|
+
checkedAt: new Date(),
|
|
121
|
+
checking: false,
|
|
122
|
+
error: null,
|
|
123
|
+
supported: true,
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private fail(message: string): DependencySnapshot {
|
|
128
|
+
const error = new DevccError({ scope: "command", message: `Dependency check failed: ${message}` })
|
|
129
|
+
this.emit("deps:error", error)
|
|
130
|
+
return this.publish({ ...this.snapshot, checking: false, checkedAt: new Date(), error: message })
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private publish(snapshot: DependencySnapshot): DependencySnapshot {
|
|
134
|
+
this.snapshot = snapshot
|
|
135
|
+
this.emit("deps:changed", snapshot)
|
|
136
|
+
return snapshot
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The argv that updates a single package to its latest version. */
|
|
141
|
+
export function updateArgs(manager: PackageManager, name: string): string[] {
|
|
142
|
+
switch (manager) {
|
|
143
|
+
case "pnpm":
|
|
144
|
+
return ["update", "--latest", name]
|
|
145
|
+
case "npm":
|
|
146
|
+
return ["install", `${name}@latest`]
|
|
147
|
+
case "yarn":
|
|
148
|
+
return ["upgrade", `${name}@latest`]
|
|
149
|
+
case "bun":
|
|
150
|
+
return ["update", "--latest", name]
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import type { PackageManager } from "../utils/packageManager.js"
|
|
2
|
+
|
|
3
|
+
export type DependencyKind = "dependencies" | "devDependencies" | "peerDependencies" | "optionalDependencies"
|
|
4
|
+
|
|
5
|
+
/** How far behind a package is, derived from the version strings. */
|
|
6
|
+
export type UpdateSeverity = "major" | "minor" | "patch" | "unknown"
|
|
7
|
+
|
|
8
|
+
export interface OutdatedPackage {
|
|
9
|
+
name: string
|
|
10
|
+
/** Installed version. */
|
|
11
|
+
current: string
|
|
12
|
+
/** Highest version allowed by the range in package.json. */
|
|
13
|
+
wanted: string
|
|
14
|
+
/** Highest version published. */
|
|
15
|
+
latest: string
|
|
16
|
+
kind: DependencyKind
|
|
17
|
+
severity: UpdateSeverity
|
|
18
|
+
/** True when `current` already satisfies the range but `latest` is newer. */
|
|
19
|
+
onlyLatest: boolean
|
|
20
|
+
deprecated?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Managers whose `outdated` command can emit machine-readable output. */
|
|
24
|
+
export const JSON_CAPABLE_MANAGERS: readonly PackageManager[] = ["pnpm", "npm", "yarn"]
|
|
25
|
+
|
|
26
|
+
export function supportsOutdatedJson(manager: PackageManager): boolean {
|
|
27
|
+
return JSON_CAPABLE_MANAGERS.includes(manager)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The argv that produces JSON `outdated` output for each manager. */
|
|
31
|
+
export function outdatedArgs(manager: PackageManager): string[] {
|
|
32
|
+
switch (manager) {
|
|
33
|
+
case "pnpm":
|
|
34
|
+
return ["outdated", "--format", "json"]
|
|
35
|
+
case "npm":
|
|
36
|
+
return ["outdated", "--json", "--long"]
|
|
37
|
+
case "yarn":
|
|
38
|
+
return ["outdated", "--json"]
|
|
39
|
+
case "bun":
|
|
40
|
+
return ["outdated"]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Strip range operators and build metadata: `^1.2.3-beta.1` -> `[1, 2, 3]`. */
|
|
45
|
+
export function parseVersion(version: string): [number, number, number] | null {
|
|
46
|
+
const match = /(\d+)\.(\d+)\.(\d+)/u.exec(version.trim())
|
|
47
|
+
if (!match) return null
|
|
48
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Classify the gap between two versions. `unknown` when either side is not a
|
|
53
|
+
* plain semver triple (git refs, `latest`, workspace protocols).
|
|
54
|
+
*/
|
|
55
|
+
export function severityBetween(current: string, latest: string): UpdateSeverity {
|
|
56
|
+
const from = parseVersion(current)
|
|
57
|
+
const to = parseVersion(latest)
|
|
58
|
+
if (!from || !to) return "unknown"
|
|
59
|
+
if (to[0] !== from[0]) return "major"
|
|
60
|
+
if (to[1] !== from[1]) return "minor"
|
|
61
|
+
if (to[2] !== from[2]) return "patch"
|
|
62
|
+
return "unknown"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normaliseKind(value: unknown): DependencyKind {
|
|
66
|
+
switch (typeof value === "string" ? value.toLowerCase() : "") {
|
|
67
|
+
case "devdependencies":
|
|
68
|
+
case "dev":
|
|
69
|
+
return "devDependencies"
|
|
70
|
+
case "peerdependencies":
|
|
71
|
+
case "peer":
|
|
72
|
+
return "peerDependencies"
|
|
73
|
+
case "optionaldependencies":
|
|
74
|
+
case "optional":
|
|
75
|
+
return "optionalDependencies"
|
|
76
|
+
default:
|
|
77
|
+
return "dependencies"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
82
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
83
|
+
? (value as Record<string, unknown>)
|
|
84
|
+
: undefined
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function asString(value: unknown): string | undefined {
|
|
88
|
+
return typeof value === "string" && value.length > 0 ? value : undefined
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildEntry(
|
|
92
|
+
name: string,
|
|
93
|
+
current: string,
|
|
94
|
+
wanted: string,
|
|
95
|
+
latest: string,
|
|
96
|
+
kind: DependencyKind,
|
|
97
|
+
deprecated?: boolean,
|
|
98
|
+
): OutdatedPackage {
|
|
99
|
+
return {
|
|
100
|
+
name,
|
|
101
|
+
current,
|
|
102
|
+
wanted,
|
|
103
|
+
latest,
|
|
104
|
+
kind,
|
|
105
|
+
severity: severityBetween(current, latest),
|
|
106
|
+
onlyLatest: current === wanted && wanted !== latest,
|
|
107
|
+
deprecated,
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* pnpm (`--format json`) and npm (`--json`) both emit an object keyed by package
|
|
113
|
+
* name; only the field names around it differ slightly.
|
|
114
|
+
*/
|
|
115
|
+
export function parseNpmStyleOutdated(json: unknown): OutdatedPackage[] {
|
|
116
|
+
const root = asRecord(json)
|
|
117
|
+
if (!root) return []
|
|
118
|
+
|
|
119
|
+
const packages: OutdatedPackage[] = []
|
|
120
|
+
for (const [name, raw] of Object.entries(root)) {
|
|
121
|
+
// npm --json can produce an array when a package is present at several paths.
|
|
122
|
+
const entry = asRecord(Array.isArray(raw) ? raw[0] : raw)
|
|
123
|
+
if (!entry) continue
|
|
124
|
+
|
|
125
|
+
const current = asString(entry.current)
|
|
126
|
+
const latest = asString(entry.latest)
|
|
127
|
+
if (!latest) continue
|
|
128
|
+
|
|
129
|
+
packages.push(
|
|
130
|
+
buildEntry(
|
|
131
|
+
name,
|
|
132
|
+
current ?? "-",
|
|
133
|
+
asString(entry.wanted) ?? current ?? "-",
|
|
134
|
+
latest,
|
|
135
|
+
normaliseKind(entry.dependencyType ?? entry.type),
|
|
136
|
+
entry.isDeprecated === true,
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
return packages
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** yarn classic emits newline-delimited JSON with a `table` payload. */
|
|
144
|
+
export function parseYarnOutdated(output: string): OutdatedPackage[] {
|
|
145
|
+
for (const line of output.split("\n")) {
|
|
146
|
+
if (line.trim() === "") continue
|
|
147
|
+
let parsed: unknown
|
|
148
|
+
try {
|
|
149
|
+
parsed = JSON.parse(line)
|
|
150
|
+
} catch {
|
|
151
|
+
continue
|
|
152
|
+
}
|
|
153
|
+
const record = asRecord(parsed)
|
|
154
|
+
if (record?.type !== "table") continue
|
|
155
|
+
|
|
156
|
+
const body = asRecord(record.data)?.body
|
|
157
|
+
if (!Array.isArray(body)) continue
|
|
158
|
+
|
|
159
|
+
return body
|
|
160
|
+
.filter((row): row is unknown[] => Array.isArray(row))
|
|
161
|
+
.map((row) => {
|
|
162
|
+
const [name, current, wanted, latest, kind] = row.map((cell) =>
|
|
163
|
+
typeof cell === "string" ? cell : "",
|
|
164
|
+
)
|
|
165
|
+
return buildEntry(name ?? "", current ?? "-", wanted ?? "-", latest ?? "-", normaliseKind(kind))
|
|
166
|
+
})
|
|
167
|
+
.filter((entry) => entry.name !== "")
|
|
168
|
+
}
|
|
169
|
+
return []
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Dispatch to the right parser for the manager that produced `output`. */
|
|
173
|
+
export function parseOutdated(manager: PackageManager, output: string): OutdatedPackage[] {
|
|
174
|
+
const trimmed = output.trim()
|
|
175
|
+
if (trimmed === "") return []
|
|
176
|
+
|
|
177
|
+
if (manager === "yarn") return parseYarnOutdated(trimmed)
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
return parseNpmStyleOutdated(JSON.parse(trimmed))
|
|
181
|
+
} catch {
|
|
182
|
+
return []
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const SEVERITY_ORDER: Record<UpdateSeverity, number> = { major: 0, minor: 1, patch: 2, unknown: 3 }
|
|
187
|
+
|
|
188
|
+
/** Most-significant updates first, then alphabetically. */
|
|
189
|
+
export function sortOutdated(packages: readonly OutdatedPackage[]): OutdatedPackage[] {
|
|
190
|
+
return [...packages].sort((a, b) => {
|
|
191
|
+
const bySeverity = SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity]
|
|
192
|
+
return bySeverity !== 0 ? bySeverity : a.name.localeCompare(b.name)
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface OutdatedSummary {
|
|
197
|
+
total: number
|
|
198
|
+
major: number
|
|
199
|
+
minor: number
|
|
200
|
+
patch: number
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function summariseOutdated(packages: readonly OutdatedPackage[]): OutdatedSummary {
|
|
204
|
+
return {
|
|
205
|
+
total: packages.length,
|
|
206
|
+
major: packages.filter((entry) => entry.severity === "major").length,
|
|
207
|
+
minor: packages.filter((entry) => entry.severity === "minor").length,
|
|
208
|
+
patch: packages.filter((entry) => entry.severity === "patch").length,
|
|
209
|
+
}
|
|
210
|
+
}
|