@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,179 @@
|
|
|
1
|
+
package tui
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
|
|
6
|
+
"owd-cli/bridge"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
type WorkItemStatus string
|
|
10
|
+
|
|
11
|
+
const (
|
|
12
|
+
StatusPending WorkItemStatus = "PENDING"
|
|
13
|
+
StatusRunning WorkItemStatus = "RUNNING"
|
|
14
|
+
StatusCompleted WorkItemStatus = "COMPLETED"
|
|
15
|
+
StatusFailed WorkItemStatus = "FAILED"
|
|
16
|
+
StatusSkipped WorkItemStatus = "SKIPPED"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
type WorkItemKind string
|
|
20
|
+
|
|
21
|
+
const (
|
|
22
|
+
KindClone WorkItemKind = "clone"
|
|
23
|
+
KindNpm WorkItemKind = "npm"
|
|
24
|
+
KindLocal WorkItemKind = "local"
|
|
25
|
+
KindPrompt WorkItemKind = "prompt"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
const SourcePending = "pending"
|
|
29
|
+
|
|
30
|
+
type WorkItem struct {
|
|
31
|
+
Name string
|
|
32
|
+
ShortName string
|
|
33
|
+
Type string // app | module | theme
|
|
34
|
+
Source string // npm:version | workspace:* | git-ssh | git-https | pending
|
|
35
|
+
Status WorkItemStatus
|
|
36
|
+
Kind WorkItemKind
|
|
37
|
+
Error string
|
|
38
|
+
TargetDir string
|
|
39
|
+
Discovered bool
|
|
40
|
+
Entry bridge.CatalogEntry
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type WorkQueue struct {
|
|
44
|
+
Items []WorkItem
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func NewWorkQueue() *WorkQueue {
|
|
48
|
+
return &WorkQueue{Items: []WorkItem{}}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func (q *WorkQueue) TotalCount() int {
|
|
52
|
+
return len(q.Items)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func (q *WorkQueue) CompletedCount() int {
|
|
56
|
+
n := 0
|
|
57
|
+
for _, item := range q.Items {
|
|
58
|
+
switch item.Status {
|
|
59
|
+
case StatusCompleted, StatusSkipped, StatusFailed:
|
|
60
|
+
n++
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return n
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func (q *WorkQueue) Progress() (completed, total int) {
|
|
67
|
+
return q.CompletedCount(), q.TotalCount()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func (q *WorkQueue) FindByName(name string) *WorkItem {
|
|
71
|
+
for i := range q.Items {
|
|
72
|
+
if q.Items[i].Name == name {
|
|
73
|
+
return &q.Items[i]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return nil
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
func (q *WorkQueue) NextPending() *WorkItem {
|
|
80
|
+
for i := range q.Items {
|
|
81
|
+
if q.Items[i].Status == StatusPending {
|
|
82
|
+
return &q.Items[i]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return nil
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func (q *WorkQueue) HasPending() bool {
|
|
89
|
+
return q.NextPending() != nil
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
func (q *WorkQueue) HasPendingPrompt() bool {
|
|
93
|
+
for _, item := range q.Items {
|
|
94
|
+
if item.Status == StatusPending && item.Source == SourcePending {
|
|
95
|
+
return true
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return false
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
func (q *WorkQueue) NextPendingPrompt() *WorkItem {
|
|
102
|
+
for i := range q.Items {
|
|
103
|
+
if q.Items[i].Status == StatusPending && q.Items[i].Source == SourcePending {
|
|
104
|
+
return &q.Items[i]
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return nil
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
func (q *WorkQueue) AppendUnique(item WorkItem) bool {
|
|
111
|
+
if q.FindByName(item.Name) != nil {
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
q.Items = append(q.Items, item)
|
|
115
|
+
return true
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
func (q *WorkQueue) MarkRunning(name string) {
|
|
119
|
+
q.setStatus(name, StatusRunning, "")
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
func (q *WorkQueue) MarkCompleted(name string) {
|
|
123
|
+
q.setStatus(name, StatusCompleted, "")
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
func (q *WorkQueue) MarkSkipped(name string) {
|
|
127
|
+
q.setStatus(name, StatusSkipped, "")
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
func (q *WorkQueue) MarkFailed(name string, err string) {
|
|
131
|
+
q.setStatus(name, StatusFailed, err)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
func (q *WorkQueue) setStatus(name string, status WorkItemStatus, err string) {
|
|
135
|
+
for i := range q.Items {
|
|
136
|
+
if q.Items[i].Name == name {
|
|
137
|
+
q.Items[i].Status = status
|
|
138
|
+
if err != "" {
|
|
139
|
+
q.Items[i].Error = err
|
|
140
|
+
}
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
func (q *WorkQueue) SetSource(name, source string, kind WorkItemKind) {
|
|
147
|
+
for i := range q.Items {
|
|
148
|
+
if q.Items[i].Name == name {
|
|
149
|
+
q.Items[i].Source = source
|
|
150
|
+
q.Items[i].Kind = kind
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
func (q *WorkQueue) Clone() WorkQueue {
|
|
157
|
+
dup := WorkQueue{Items: make([]WorkItem, len(q.Items))}
|
|
158
|
+
copy(dup.Items, q.Items)
|
|
159
|
+
return dup
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
func (item WorkItem) StatusIcon() string {
|
|
163
|
+
switch item.Status {
|
|
164
|
+
case StatusCompleted:
|
|
165
|
+
return "✔"
|
|
166
|
+
case StatusRunning:
|
|
167
|
+
return "▶"
|
|
168
|
+
case StatusFailed:
|
|
169
|
+
return "✖"
|
|
170
|
+
case StatusSkipped:
|
|
171
|
+
return "⊘"
|
|
172
|
+
default:
|
|
173
|
+
return "○"
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
func (item WorkItem) LogTransition(from, to WorkItemStatus) string {
|
|
178
|
+
return fmt.Sprintf("ℹ Queue: %s %s → %s", item.ShortName, from, to)
|
|
179
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
package tui
|
|
2
|
+
|
|
3
|
+
import "testing"
|
|
4
|
+
|
|
5
|
+
func TestWorkQueueProgress(t *testing.T) {
|
|
6
|
+
q := NewWorkQueue()
|
|
7
|
+
if q.CompletedCount() != 0 || q.TotalCount() != 0 {
|
|
8
|
+
t.Fatalf("expected empty queue progress 0/0")
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
q.AppendUnique(WorkItem{Name: "a", ShortName: "a", Status: StatusPending})
|
|
12
|
+
q.AppendUnique(WorkItem{Name: "b", ShortName: "b", Status: StatusPending})
|
|
13
|
+
|
|
14
|
+
if c, total := q.Progress(); c != 0 || total != 2 {
|
|
15
|
+
t.Fatalf("expected 0/2, got %d/%d", c, total)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
q.MarkCompleted("a")
|
|
19
|
+
if c, total := q.Progress(); c != 1 || total != 2 {
|
|
20
|
+
t.Fatalf("expected 1/2, got %d/%d", c, total)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
q.AppendUnique(WorkItem{Name: "c", ShortName: "c", Status: StatusPending})
|
|
24
|
+
if c, total := q.Progress(); c != 1 || total != 3 {
|
|
25
|
+
t.Fatalf("expected 1/3 after append, got %d/%d", c, total)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func TestWorkQueueAppendUnique(t *testing.T) {
|
|
30
|
+
q := NewWorkQueue()
|
|
31
|
+
if !q.AppendUnique(WorkItem{Name: "@owdproject/app-about", ShortName: "app-about"}) {
|
|
32
|
+
t.Fatal("first append should succeed")
|
|
33
|
+
}
|
|
34
|
+
if q.AppendUnique(WorkItem{Name: "@owdproject/app-about", ShortName: "app-about"}) {
|
|
35
|
+
t.Fatal("duplicate append should fail")
|
|
36
|
+
}
|
|
37
|
+
if len(q.Items) != 1 {
|
|
38
|
+
t.Fatalf("expected 1 item, got %d", len(q.Items))
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func TestWorkQueueNextPending(t *testing.T) {
|
|
43
|
+
q := NewWorkQueue()
|
|
44
|
+
q.Items = []WorkItem{
|
|
45
|
+
{Name: "a", Status: StatusCompleted},
|
|
46
|
+
{Name: "b", Status: StatusPending, Source: SourcePending},
|
|
47
|
+
{Name: "c", Status: StatusPending, Source: "git-ssh"},
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if next := q.NextPending(); next == nil || next.Name != "b" {
|
|
51
|
+
t.Fatalf("expected b, got %v", next)
|
|
52
|
+
}
|
|
53
|
+
if prompt := q.NextPendingPrompt(); prompt == nil || prompt.Name != "b" {
|
|
54
|
+
t.Fatalf("expected pending prompt for b, got %v", prompt)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
func TestWorkQueueStatusTransitions(t *testing.T) {
|
|
59
|
+
q := NewWorkQueue()
|
|
60
|
+
q.AppendUnique(WorkItem{Name: "x", ShortName: "x", Status: StatusPending})
|
|
61
|
+
|
|
62
|
+
q.MarkRunning("x")
|
|
63
|
+
if item := q.FindByName("x"); item.Status != StatusRunning {
|
|
64
|
+
t.Fatalf("expected RUNNING, got %s", item.Status)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
q.MarkFailed("x", "clone failed")
|
|
68
|
+
if item := q.FindByName("x"); item.Status != StatusFailed || item.Error != "clone failed" {
|
|
69
|
+
t.Fatalf("expected FAILED with error, got %+v", item)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func TestWorkItemStatusIcon(t *testing.T) {
|
|
74
|
+
tests := []struct {
|
|
75
|
+
status WorkItemStatus
|
|
76
|
+
want string
|
|
77
|
+
}{
|
|
78
|
+
{StatusCompleted, "✔"},
|
|
79
|
+
{StatusRunning, "▶"},
|
|
80
|
+
{StatusFailed, "✖"},
|
|
81
|
+
{StatusSkipped, "⊘"},
|
|
82
|
+
{StatusPending, "○"},
|
|
83
|
+
}
|
|
84
|
+
for _, tc := range tests {
|
|
85
|
+
item := WorkItem{Status: tc.status}
|
|
86
|
+
if got := item.StatusIcon(); got != tc.want {
|
|
87
|
+
t.Fatalf("status %s: want %s, got %s", tc.status, tc.want, got)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owdproject/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Control Panel and Command Line Interface for Open Web Desktop",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"owd",
|
|
@@ -41,6 +41,6 @@
|
|
|
41
41
|
"nanoid": "^5.1.11",
|
|
42
42
|
"shellwords": "^1.1.1",
|
|
43
43
|
"ts-morph": "^25.0.1",
|
|
44
|
-
"@owdproject/core": "3.
|
|
44
|
+
"@owdproject/core": "3.5.0"
|
|
45
45
|
}
|
|
46
46
|
}
|