@owdproject/cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/bin/bridge.js +4 -2
- package/bin/cli.js +63 -5
- package/bin/cp.js +42 -23
- package/bin/lib/catalog.js +3 -0
- package/bin/lib/config.js +51 -8
- package/bin/lib/cpFormat.js +2 -2
- package/bin/lib/create.js +246 -0
- package/bin/lib/desktopConfig.js +48 -8
- package/bin/lib/generateTemplate.js +2 -1
- package/bin/lib/install.js +17 -21
- package/bin/lib/installWizardFlow.js +90 -24
- package/bin/lib/packageSources.js +10 -0
- package/bin/lib/status.js +2 -0
- package/bin/lib/validateModule.js +112 -2
- package/bin/lib/workspace.js +68 -1
- package/go/bridge/bridge.go +20 -0
- package/go/desktop-go +0 -0
- package/go/go.mod +17 -10
- package/go/go.sum +34 -19
- package/go/main.go +7 -3
- package/go/owd-cli +0 -0
- package/go/tui/engine.go +472 -0
- package/go/tui/engine_test.go +40 -0
- package/go/tui/handlers.go +604 -0
- package/go/tui/helpers.go +916 -0
- package/go/tui/helpers_test.go +97 -0
- package/go/tui/modals.go +423 -0
- package/go/tui/resolver.go +377 -0
- package/go/tui/resolver_test.go +86 -0
- package/go/tui/styles.go +153 -0
- package/go/tui/tasks.go +850 -0
- package/go/tui/tui.go +451 -2596
- package/go/tui/tui_test.go +69 -0
- package/go/tui/types.go +230 -0
- package/go/tui/view.go +812 -0
- package/go/tui/workqueue.go +179 -0
- package/go/tui/workqueue_test.go +90 -0
- package/package.json +2 -2
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
package tui
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
|
|
9
|
+
"owd-cli/bridge"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// readDesktopPackageDeps reads @owdproject/* deps with versions from desktop/package.json.
|
|
13
|
+
func readDesktopPackageDeps(packageJsonPath string) (map[string]string, error) {
|
|
14
|
+
data, err := os.ReadFile(packageJsonPath)
|
|
15
|
+
if err != nil {
|
|
16
|
+
return nil, err
|
|
17
|
+
}
|
|
18
|
+
var pkg struct {
|
|
19
|
+
Dependencies map[string]string `json:"dependencies"`
|
|
20
|
+
DevDependencies map[string]string `json:"devDependencies"`
|
|
21
|
+
}
|
|
22
|
+
if err := json.Unmarshal(data, &pkg); err != nil {
|
|
23
|
+
return nil, err
|
|
24
|
+
}
|
|
25
|
+
deps := make(map[string]string)
|
|
26
|
+
for k, v := range pkg.Dependencies {
|
|
27
|
+
if strings.HasPrefix(k, "@owdproject/") {
|
|
28
|
+
deps[k] = v
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
for k, v := range pkg.DevDependencies {
|
|
32
|
+
if strings.HasPrefix(k, "@owdproject/") {
|
|
33
|
+
deps[k] = v
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return deps, nil
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func isNpmVersion(version string) bool {
|
|
40
|
+
if version == "" {
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
if version == "workspace:*" || version == "workspace:^" {
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func shortNameFromPkg(name string) string {
|
|
50
|
+
if idx := strings.LastIndex(name, "/"); idx >= 0 {
|
|
51
|
+
return name[idx+1:]
|
|
52
|
+
}
|
|
53
|
+
return name
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func kindDirForKind(kind string) string {
|
|
57
|
+
switch kind {
|
|
58
|
+
case "app":
|
|
59
|
+
return "apps"
|
|
60
|
+
case "module":
|
|
61
|
+
return "packages"
|
|
62
|
+
case "theme":
|
|
63
|
+
return "themes"
|
|
64
|
+
default:
|
|
65
|
+
return "packages"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func catalogEntryForName(catalog *bridge.CatalogResponse, name string) bridge.CatalogEntry {
|
|
70
|
+
if catalog != nil {
|
|
71
|
+
for _, e := range catalog.Entries {
|
|
72
|
+
if e.Name == name {
|
|
73
|
+
return e
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
short := shortNameFromPkg(name)
|
|
78
|
+
kind := "module"
|
|
79
|
+
if strings.HasPrefix(short, "app-") {
|
|
80
|
+
kind = "app"
|
|
81
|
+
} else if strings.HasPrefix(short, "theme-") {
|
|
82
|
+
kind = "theme"
|
|
83
|
+
}
|
|
84
|
+
return bridge.CatalogEntry{
|
|
85
|
+
Name: name,
|
|
86
|
+
ShortName: short,
|
|
87
|
+
Kind: kind,
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// packagesFromConfig returns all configured package names from desktop.config.ts.
|
|
92
|
+
func packagesFromConfig(config *bridge.Config) []struct {
|
|
93
|
+
Name string
|
|
94
|
+
Kind string
|
|
95
|
+
} {
|
|
96
|
+
var out []struct {
|
|
97
|
+
Name string
|
|
98
|
+
Kind string
|
|
99
|
+
}
|
|
100
|
+
if config == nil {
|
|
101
|
+
return out
|
|
102
|
+
}
|
|
103
|
+
for _, name := range config.Apps {
|
|
104
|
+
out = append(out, struct {
|
|
105
|
+
Name string
|
|
106
|
+
Kind string
|
|
107
|
+
}{Name: name, Kind: "app"})
|
|
108
|
+
}
|
|
109
|
+
for _, name := range config.Modules {
|
|
110
|
+
out = append(out, struct {
|
|
111
|
+
Name string
|
|
112
|
+
Kind string
|
|
113
|
+
}{Name: name, Kind: "module"})
|
|
114
|
+
}
|
|
115
|
+
if config.Theme != nil && *config.Theme != "" {
|
|
116
|
+
out = append(out, struct {
|
|
117
|
+
Name string
|
|
118
|
+
Kind string
|
|
119
|
+
}{Name: *config.Theme, Kind: "theme"})
|
|
120
|
+
}
|
|
121
|
+
return out
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// BuildQueueFromConfig resolves packages in config against desktop/package.json.
|
|
125
|
+
func BuildQueueFromConfig(
|
|
126
|
+
workspaceRoot string,
|
|
127
|
+
ctx *bridge.WorkspaceContext,
|
|
128
|
+
catalog *bridge.CatalogResponse,
|
|
129
|
+
logFn func(string),
|
|
130
|
+
) *WorkQueue {
|
|
131
|
+
q := NewWorkQueue()
|
|
132
|
+
if ctx == nil {
|
|
133
|
+
return q
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
deps, err := readDesktopPackageDeps(ctx.Paths.PackageJson)
|
|
137
|
+
if err != nil {
|
|
138
|
+
logFn("✖ Failed to read desktop/package.json: " + err.Error())
|
|
139
|
+
return q
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
seen := map[string]bool{}
|
|
143
|
+
for _, pkg := range packagesFromConfig(&ctx.Config) {
|
|
144
|
+
if seen[pkg.Name] {
|
|
145
|
+
continue
|
|
146
|
+
}
|
|
147
|
+
seen[pkg.Name] = true
|
|
148
|
+
addPackageToQueue(q, workspaceRoot, pkg.Name, pkg.Kind, deps, catalog, false, logFn)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Recursively scan dependencies of all local packages in the queue
|
|
152
|
+
scanned := map[string]bool{}
|
|
153
|
+
for {
|
|
154
|
+
hasNewScan := false
|
|
155
|
+
var names []string
|
|
156
|
+
for _, item := range q.Items {
|
|
157
|
+
if !scanned[item.Name] {
|
|
158
|
+
names = append(names, item.Name)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for _, name := range names {
|
|
163
|
+
scanned[name] = true
|
|
164
|
+
item := q.FindByName(name)
|
|
165
|
+
if item != nil && isLocallyAvailable(workspaceRoot, item.ShortName) {
|
|
166
|
+
DiscoverNestedDeps(workspaceRoot, *item, catalog, q, logFn)
|
|
167
|
+
hasNewScan = true
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if !hasNewScan {
|
|
171
|
+
break
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return q
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// BuildStartupCheckQueue builds queue for installed-but-not-local packages.
|
|
179
|
+
func BuildStartupCheckQueue(
|
|
180
|
+
workspaceRoot string,
|
|
181
|
+
ctx *bridge.WorkspaceContext,
|
|
182
|
+
catalog *bridge.CatalogResponse,
|
|
183
|
+
logFn func(string),
|
|
184
|
+
) *WorkQueue {
|
|
185
|
+
q := NewWorkQueue()
|
|
186
|
+
if catalog == nil || ctx == nil {
|
|
187
|
+
return q
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
deps, _ := readDesktopPackageDeps(ctx.Paths.PackageJson)
|
|
191
|
+
seen := map[string]bool{}
|
|
192
|
+
|
|
193
|
+
for _, entry := range catalog.Entries {
|
|
194
|
+
if entry.Installed && !entry.LocalSource {
|
|
195
|
+
if !seen[entry.Name] {
|
|
196
|
+
seen[entry.Name] = true
|
|
197
|
+
addPackageToQueue(q, workspaceRoot, entry.Name, entry.Kind, deps, catalog, false, logFn)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Theme kit dependencies
|
|
203
|
+
var themeShort string
|
|
204
|
+
if ctx.Config.Theme != nil && *ctx.Config.Theme != "" {
|
|
205
|
+
themeShort = shortNameFromPkg(*ctx.Config.Theme)
|
|
206
|
+
}
|
|
207
|
+
if themeShort != "" {
|
|
208
|
+
themeDeps, err := getThemeDependencies(workspaceRoot, themeShort)
|
|
209
|
+
if err == nil {
|
|
210
|
+
for _, dep := range themeDeps {
|
|
211
|
+
if seen[dep] {
|
|
212
|
+
continue
|
|
213
|
+
}
|
|
214
|
+
short := shortNameFromPkg(dep)
|
|
215
|
+
if !strings.HasPrefix(short, "kit-") && !strings.HasPrefix(short, "module-") {
|
|
216
|
+
continue
|
|
217
|
+
}
|
|
218
|
+
if isLocallyAvailable(workspaceRoot, short) {
|
|
219
|
+
logFn("ℹ " + short + " already available")
|
|
220
|
+
continue
|
|
221
|
+
}
|
|
222
|
+
seen[dep] = true
|
|
223
|
+
addPackageToQueue(q, workspaceRoot, dep, "module", deps, catalog, true, logFn)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Recursively scan dependencies of all local packages in the queue
|
|
229
|
+
scanned := map[string]bool{}
|
|
230
|
+
for {
|
|
231
|
+
hasNewScan := false
|
|
232
|
+
var names []string
|
|
233
|
+
for _, item := range q.Items {
|
|
234
|
+
if !scanned[item.Name] {
|
|
235
|
+
names = append(names, item.Name)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
for _, name := range names {
|
|
240
|
+
scanned[name] = true
|
|
241
|
+
item := q.FindByName(name)
|
|
242
|
+
if item != nil && isLocallyAvailable(workspaceRoot, item.ShortName) {
|
|
243
|
+
DiscoverNestedDeps(workspaceRoot, *item, catalog, q, logFn)
|
|
244
|
+
hasNewScan = true
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if !hasNewScan {
|
|
248
|
+
break
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return q
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
func addPackageToQueue(
|
|
256
|
+
q *WorkQueue,
|
|
257
|
+
workspaceRoot, name, kind string,
|
|
258
|
+
deps map[string]string,
|
|
259
|
+
catalog *bridge.CatalogResponse,
|
|
260
|
+
discovered bool,
|
|
261
|
+
logFn func(string),
|
|
262
|
+
) {
|
|
263
|
+
entry := catalogEntryForName(catalog, name)
|
|
264
|
+
if kind != "" {
|
|
265
|
+
entry.Kind = kind
|
|
266
|
+
}
|
|
267
|
+
short := entry.ShortName
|
|
268
|
+
if short == "" {
|
|
269
|
+
short = shortNameFromPkg(name)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
version, inPkg := deps[name]
|
|
273
|
+
|
|
274
|
+
if (entry.LocalSource || isLocallyAvailable(workspaceRoot, short)) && inPkg {
|
|
275
|
+
logFn("ℹ " + short + " already available and configured in package.json")
|
|
276
|
+
q.AppendUnique(WorkItem{
|
|
277
|
+
Name: name,
|
|
278
|
+
ShortName: short,
|
|
279
|
+
Type: entry.Kind,
|
|
280
|
+
Source: "local",
|
|
281
|
+
Status: StatusSkipped,
|
|
282
|
+
Kind: KindLocal,
|
|
283
|
+
TargetDir: filepath.Join(workspaceRoot, kindDirForKind(entry.Kind), short),
|
|
284
|
+
Discovered: discovered,
|
|
285
|
+
Entry: entry,
|
|
286
|
+
})
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if !inPkg {
|
|
291
|
+
logFn("⚠ " + short + " missing from package.json")
|
|
292
|
+
q.AppendUnique(WorkItem{
|
|
293
|
+
Name: name,
|
|
294
|
+
ShortName: short,
|
|
295
|
+
Type: entry.Kind,
|
|
296
|
+
Source: SourcePending,
|
|
297
|
+
Status: StatusPending,
|
|
298
|
+
Kind: KindPrompt,
|
|
299
|
+
Discovered: discovered,
|
|
300
|
+
Entry: entry,
|
|
301
|
+
})
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if version == "workspace:*" || version == "workspace:^" {
|
|
306
|
+
logFn("⚠ " + short + " configured as workspace dependency")
|
|
307
|
+
q.AppendUnique(WorkItem{
|
|
308
|
+
Name: name,
|
|
309
|
+
ShortName: short,
|
|
310
|
+
Type: entry.Kind,
|
|
311
|
+
Source: SourcePending,
|
|
312
|
+
Status: StatusPending,
|
|
313
|
+
Kind: KindClone,
|
|
314
|
+
TargetDir: filepath.Join(workspaceRoot, kindDirForKind(entry.Kind), short),
|
|
315
|
+
Discovered: discovered,
|
|
316
|
+
Entry: entry,
|
|
317
|
+
})
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if isNpmVersion(version) {
|
|
322
|
+
logFn("ℹ " + short + " resolved as npm package (" + version + ")")
|
|
323
|
+
q.AppendUnique(WorkItem{
|
|
324
|
+
Name: name,
|
|
325
|
+
ShortName: short,
|
|
326
|
+
Type: entry.Kind,
|
|
327
|
+
Source: "npm:" + version,
|
|
328
|
+
Status: StatusSkipped,
|
|
329
|
+
Kind: KindNpm,
|
|
330
|
+
Discovered: discovered,
|
|
331
|
+
Entry: entry,
|
|
332
|
+
})
|
|
333
|
+
return
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
logFn("⚠ " + short + " has unknown source (" + version + ") — needs resolution")
|
|
337
|
+
q.AppendUnique(WorkItem{
|
|
338
|
+
Name: name,
|
|
339
|
+
ShortName: short,
|
|
340
|
+
Type: entry.Kind,
|
|
341
|
+
Source: SourcePending,
|
|
342
|
+
Status: StatusPending,
|
|
343
|
+
Kind: KindPrompt,
|
|
344
|
+
Discovered: discovered,
|
|
345
|
+
Entry: entry,
|
|
346
|
+
})
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// DiscoverNestedDeps scans a cloned/local package for deps.
|
|
350
|
+
func DiscoverNestedDeps(
|
|
351
|
+
workspaceRoot string,
|
|
352
|
+
item WorkItem,
|
|
353
|
+
catalog *bridge.CatalogResponse,
|
|
354
|
+
existing *WorkQueue,
|
|
355
|
+
logFn func(string),
|
|
356
|
+
) {
|
|
357
|
+
deps, found := getOwdDepsLocal(workspaceRoot, item.ShortName, item.Type)
|
|
358
|
+
if !found {
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
for _, dep := range deps {
|
|
363
|
+
if existing.FindByName(dep) != nil {
|
|
364
|
+
continue
|
|
365
|
+
}
|
|
366
|
+
depShort := shortNameFromPkg(dep)
|
|
367
|
+
depKind := "module"
|
|
368
|
+
if strings.HasPrefix(depShort, "app-") {
|
|
369
|
+
depKind = "app"
|
|
370
|
+
} else if strings.HasPrefix(depShort, "theme-") {
|
|
371
|
+
depKind = "theme"
|
|
372
|
+
}
|
|
373
|
+
logFn("⚠ Dependency discovered: " + depShort)
|
|
374
|
+
pkgDeps, _ := readDesktopPackageDeps(filepath.Join(workspaceRoot, "desktop", "package.json"))
|
|
375
|
+
addPackageToQueue(existing, workspaceRoot, dep, depKind, pkgDeps, catalog, true, logFn)
|
|
376
|
+
}
|
|
377
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
package tui
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"testing"
|
|
7
|
+
|
|
8
|
+
"owd-cli/bridge"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
func TestIsNpmVersion(t *testing.T) {
|
|
12
|
+
if !isNpmVersion("^1.2.0") {
|
|
13
|
+
t.Fatal("semver should be npm")
|
|
14
|
+
}
|
|
15
|
+
if !isNpmVersion("latest") {
|
|
16
|
+
t.Fatal("latest should be npm")
|
|
17
|
+
}
|
|
18
|
+
if isNpmVersion("workspace:*") {
|
|
19
|
+
t.Fatal("workspace should not be npm")
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func TestReadDesktopPackageDeps(t *testing.T) {
|
|
24
|
+
dir := t.TempDir()
|
|
25
|
+
pkgPath := filepath.Join(dir, "package.json")
|
|
26
|
+
content := `{
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@owdproject/app-about": "workspace:*",
|
|
29
|
+
"@owdproject/theme-nova": "^1.0.0"
|
|
30
|
+
}
|
|
31
|
+
}`
|
|
32
|
+
if err := os.WriteFile(pkgPath, []byte(content), 0644); err != nil {
|
|
33
|
+
t.Fatal(err)
|
|
34
|
+
}
|
|
35
|
+
deps, err := readDesktopPackageDeps(pkgPath)
|
|
36
|
+
if err != nil {
|
|
37
|
+
t.Fatal(err)
|
|
38
|
+
}
|
|
39
|
+
if deps["@owdproject/app-about"] != "workspace:*" {
|
|
40
|
+
t.Fatalf("unexpected workspace dep: %v", deps)
|
|
41
|
+
}
|
|
42
|
+
if deps["@owdproject/theme-nova"] != "^1.0.0" {
|
|
43
|
+
t.Fatalf("unexpected npm dep: %v", deps)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func TestBuildQueueFromConfigWorkspaceAndNpm(t *testing.T) {
|
|
48
|
+
dir := t.TempDir()
|
|
49
|
+
desktopDir := filepath.Join(dir, "desktop")
|
|
50
|
+
if err := os.MkdirAll(desktopDir, 0755); err != nil {
|
|
51
|
+
t.Fatal(err)
|
|
52
|
+
}
|
|
53
|
+
pkgPath := filepath.Join(desktopDir, "package.json")
|
|
54
|
+
content := `{
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@owdproject/app-about": "workspace:*",
|
|
57
|
+
"@owdproject/theme-nova": "^1.2.0"
|
|
58
|
+
}
|
|
59
|
+
}`
|
|
60
|
+
if err := os.WriteFile(pkgPath, []byte(content), 0644); err != nil {
|
|
61
|
+
t.Fatal(err)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
theme := "@owdproject/theme-nova"
|
|
65
|
+
ctx := &bridge.WorkspaceContext{
|
|
66
|
+
Config: bridge.Config{
|
|
67
|
+
Theme: &theme,
|
|
68
|
+
Apps: []string{"@owdproject/app-about"},
|
|
69
|
+
},
|
|
70
|
+
Paths: bridge.WorkspacePaths{PackageJson: pkgPath},
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
var logs []string
|
|
74
|
+
q := BuildQueueFromConfig(dir, ctx, nil, func(s string) { logs = append(logs, s) })
|
|
75
|
+
if q.TotalCount() != 2 {
|
|
76
|
+
t.Fatalf("expected 2 queue items, got %d", q.TotalCount())
|
|
77
|
+
}
|
|
78
|
+
about := q.FindByName("@owdproject/app-about")
|
|
79
|
+
if about == nil || about.Source != SourcePending {
|
|
80
|
+
t.Fatalf("expected pending workspace item, got %+v", about)
|
|
81
|
+
}
|
|
82
|
+
nova := q.FindByName("@owdproject/theme-nova")
|
|
83
|
+
if nova == nil || nova.Kind != KindNpm {
|
|
84
|
+
t.Fatalf("expected npm item, got %+v", nova)
|
|
85
|
+
}
|
|
86
|
+
}
|
package/go/tui/styles.go
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
package tui
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"github.com/charmbracelet/lipgloss"
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
// ─────────────────────────────────────────────
|
|
8
|
+
// Colors & Styles
|
|
9
|
+
// ─────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
var (
|
|
12
|
+
colorAccent = lipgloss.Color("#7ee787")
|
|
13
|
+
colorCyan = lipgloss.Color("#24ebff")
|
|
14
|
+
colorDim = lipgloss.Color("#3a3a4a")
|
|
15
|
+
colorMuted = lipgloss.Color("#6c7086")
|
|
16
|
+
colorWarn = lipgloss.Color("#f9e2af")
|
|
17
|
+
colorErr = lipgloss.Color("#f38ba8")
|
|
18
|
+
colorNpm = lipgloss.Color("#7ee787")
|
|
19
|
+
colorGit = lipgloss.Color("#89dceb")
|
|
20
|
+
colorLocal = lipgloss.Color("#ff79c6")
|
|
21
|
+
colorBorder = lipgloss.Color("#4a4a6a")
|
|
22
|
+
colorBorderAct = lipgloss.Color("#7ee787")
|
|
23
|
+
colorWhite = lipgloss.Color("#cdd6f4")
|
|
24
|
+
colorSubtle = lipgloss.Color("#585b70")
|
|
25
|
+
colorBarBg = lipgloss.Color("#11111b")
|
|
26
|
+
colorBarFg = lipgloss.Color("#cdd6f4")
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
var (
|
|
30
|
+
panelBorderStyle = lipgloss.NewStyle().
|
|
31
|
+
Border(lipgloss.RoundedBorder()).
|
|
32
|
+
BorderForeground(colorBorder)
|
|
33
|
+
|
|
34
|
+
panelBorderActive = lipgloss.NewStyle().
|
|
35
|
+
Border(lipgloss.RoundedBorder()).
|
|
36
|
+
BorderForeground(colorBorderAct)
|
|
37
|
+
|
|
38
|
+
accentStyle = lipgloss.NewStyle().Foreground(colorAccent).Bold(true)
|
|
39
|
+
cyanStyle = lipgloss.NewStyle().Foreground(colorCyan).Bold(true)
|
|
40
|
+
mutedStyle = lipgloss.NewStyle().Foreground(colorMuted)
|
|
41
|
+
warnStyle = lipgloss.NewStyle().Foreground(colorWarn)
|
|
42
|
+
errStyle = lipgloss.NewStyle().Foreground(colorErr)
|
|
43
|
+
boldStyle = lipgloss.NewStyle().Bold(true).Foreground(colorWhite)
|
|
44
|
+
dimStyle = lipgloss.NewStyle().Foreground(colorDim)
|
|
45
|
+
subtleStyle = lipgloss.NewStyle().Foreground(colorSubtle)
|
|
46
|
+
|
|
47
|
+
tabActiveStyle = lipgloss.NewStyle().
|
|
48
|
+
Bold(true).
|
|
49
|
+
Background(colorAccent).
|
|
50
|
+
Foreground(lipgloss.Color("#1e1e2e")).
|
|
51
|
+
Padding(0, 2)
|
|
52
|
+
|
|
53
|
+
tabInactiveStyle = lipgloss.NewStyle().
|
|
54
|
+
Foreground(colorMuted).
|
|
55
|
+
Padding(0, 2)
|
|
56
|
+
|
|
57
|
+
selectedRowStyle = lipgloss.NewStyle().
|
|
58
|
+
Bold(true).
|
|
59
|
+
Foreground(colorAccent)
|
|
60
|
+
|
|
61
|
+
installedBadge = lipgloss.NewStyle().Foreground(colorAccent).Render("●")
|
|
62
|
+
uninstalledBadge = lipgloss.NewStyle().Foreground(colorSubtle).Render("○")
|
|
63
|
+
|
|
64
|
+
// bottom bar — no border, gemini-style
|
|
65
|
+
barStyle = lipgloss.NewStyle().
|
|
66
|
+
Foreground(colorBarFg).
|
|
67
|
+
PaddingLeft(2)
|
|
68
|
+
|
|
69
|
+
barTextStyle = lipgloss.NewStyle().
|
|
70
|
+
Foreground(colorBarFg)
|
|
71
|
+
|
|
72
|
+
barKeyStyle = lipgloss.NewStyle().
|
|
73
|
+
Foreground(colorAccent).
|
|
74
|
+
Bold(true)
|
|
75
|
+
|
|
76
|
+
barSepStyle = lipgloss.NewStyle().
|
|
77
|
+
Foreground(colorSubtle)
|
|
78
|
+
|
|
79
|
+
modalStyle = lipgloss.NewStyle().
|
|
80
|
+
Padding(1, 3)
|
|
81
|
+
|
|
82
|
+
modalOptionActive = lipgloss.NewStyle().
|
|
83
|
+
Background(colorCyan).
|
|
84
|
+
Foreground(lipgloss.Color("#000000")).
|
|
85
|
+
Bold(true).
|
|
86
|
+
Padding(0, 2)
|
|
87
|
+
|
|
88
|
+
modalOptionInactive = lipgloss.NewStyle().
|
|
89
|
+
Background(colorDim).
|
|
90
|
+
Foreground(colorMuted).
|
|
91
|
+
Padding(0, 2)
|
|
92
|
+
|
|
93
|
+
modalConfirmActiveErr = lipgloss.NewStyle().
|
|
94
|
+
Background(colorErr).
|
|
95
|
+
Foreground(lipgloss.Color("#000000")).
|
|
96
|
+
Bold(true).
|
|
97
|
+
Padding(0, 2)
|
|
98
|
+
|
|
99
|
+
modalConfirmActiveAccent = lipgloss.NewStyle().
|
|
100
|
+
Background(colorAccent).
|
|
101
|
+
Foreground(lipgloss.Color("#000000")).
|
|
102
|
+
Bold(true).
|
|
103
|
+
Padding(0, 2)
|
|
104
|
+
|
|
105
|
+
settingsSaveActive = lipgloss.NewStyle().
|
|
106
|
+
Background(colorAccent).
|
|
107
|
+
Foreground(lipgloss.Color("#000000")).
|
|
108
|
+
Bold(true).
|
|
109
|
+
Padding(0, 3)
|
|
110
|
+
|
|
111
|
+
settingsSaveInactive = lipgloss.NewStyle().
|
|
112
|
+
Background(colorDim).
|
|
113
|
+
Foreground(colorWhite).
|
|
114
|
+
Padding(0, 3)
|
|
115
|
+
|
|
116
|
+
settingsCancelActive = lipgloss.NewStyle().
|
|
117
|
+
Background(colorErr).
|
|
118
|
+
Foreground(lipgloss.Color("#000000")).
|
|
119
|
+
Bold(true).
|
|
120
|
+
Padding(0, 3)
|
|
121
|
+
|
|
122
|
+
settingsCancelInactive = lipgloss.NewStyle().
|
|
123
|
+
Background(colorDim).
|
|
124
|
+
Foreground(colorWhite).
|
|
125
|
+
Padding(0, 3)
|
|
126
|
+
|
|
127
|
+
localSourceTag = lipgloss.NewStyle().
|
|
128
|
+
Foreground(colorLocal)
|
|
129
|
+
|
|
130
|
+
managePackageRightBorder = lipgloss.NewStyle().
|
|
131
|
+
Border(lipgloss.NormalBorder(), false, false, false, true).
|
|
132
|
+
BorderForeground(colorDim).
|
|
133
|
+
PaddingLeft(3)
|
|
134
|
+
|
|
135
|
+
progressBarEmptyStyle = lipgloss.NewStyle().Foreground(colorDim)
|
|
136
|
+
progressBarFilledStyle = lipgloss.NewStyle().Foreground(colorCyan)
|
|
137
|
+
spinnerStyle = lipgloss.NewStyle().Foreground(colorCyan).Bold(true)
|
|
138
|
+
managePackageLeftPanelStyle = lipgloss.NewStyle().Width(65)
|
|
139
|
+
desktopSubTitleStyle = lipgloss.NewStyle().Foreground(colorMuted)
|
|
140
|
+
modalWrapperStyle = lipgloss.NewStyle().
|
|
141
|
+
Border(lipgloss.RoundedBorder()).
|
|
142
|
+
BorderForeground(colorCyan).
|
|
143
|
+
Padding(1, 3)
|
|
144
|
+
tableHeaderStyle = lipgloss.NewStyle().Foreground(colorSubtle).Bold(true)
|
|
145
|
+
dirtyIndicatorStyle = lipgloss.NewStyle().Foreground(colorNpm)
|
|
146
|
+
selectedRowBgStyle = lipgloss.NewStyle().Background(colorDim)
|
|
147
|
+
statusGreenStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#2ea043"))
|
|
148
|
+
accentRegularStyle = lipgloss.NewStyle().Foreground(colorAccent)
|
|
149
|
+
cyanRegularStyle = lipgloss.NewStyle().Foreground(colorCyan)
|
|
150
|
+
warnBoldStyle = lipgloss.NewStyle().Foreground(colorWarn).Bold(true)
|
|
151
|
+
cyanBoldStyle = lipgloss.NewStyle().Foreground(colorCyan).Bold(true)
|
|
152
|
+
blinkGreenStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#2ea043")).Bold(true)
|
|
153
|
+
)
|