@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.
@@ -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.1.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.4.0"
44
+ "@owdproject/core": "3.5.0"
45
45
  }
46
46
  }