@owdproject/cli 0.0.2 → 0.1.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,343 @@
1
+ import { formatCatalogAge } from './catalog.js'
2
+ import { githubHtmlUrl } from './packageSources.js'
3
+
4
+ const ansiRegex = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
5
+ function stripTags(str) {
6
+ const cleanAnsi = String(str).replace(ansiRegex, '')
7
+ return cleanAnsi.replace(/\{[^{}]+\}/g, '')
8
+ }
9
+
10
+ /** @typedef {{ pending?: boolean | undefined, colors?: Record<string, string>, columns?: Record<string, number> }} RowContext */
11
+
12
+ const COL = {
13
+ sel: 3,
14
+ name: 28,
15
+ sources: 7,
16
+ dir: 4,
17
+ publisher: 20,
18
+ meta: 14,
19
+ }
20
+
21
+ export function getColumnWidths(targetWidth) {
22
+ const showChanges = targetWidth >= 110
23
+ const changesWidth = showChanges ? 10 : 0
24
+ const spaces = showChanges ? 6 : 5
25
+ const fixed = 3 + 7 + 4 + 14 + spaces + changesWidth
26
+ const remaining = targetWidth - fixed
27
+
28
+ let nameWidth = 28
29
+ let pubWidth = 20
30
+
31
+ if (remaining < 48) {
32
+ const minName = 15
33
+ const minPub = 10
34
+ const minTotal = minName + minPub // 25
35
+ if (remaining <= minTotal) {
36
+ nameWidth = minName
37
+ pubWidth = minPub
38
+ } else {
39
+ const ratio = (remaining - minTotal) / (48 - minTotal)
40
+ nameWidth = Math.floor(minName + ratio * (28 - minName))
41
+ pubWidth = remaining - nameWidth
42
+ }
43
+ } else {
44
+ const extra = remaining - 48
45
+ const extraName = Math.floor(extra * 0.6)
46
+ const extraPub = extra - extraName
47
+ nameWidth = 28 + extraName
48
+ pubWidth = 20 + extraPub
49
+ }
50
+
51
+ return {
52
+ sel: 3,
53
+ name: nameWidth,
54
+ sources: 7,
55
+ dir: 4,
56
+ changes: changesWidth,
57
+ publisher: pubWidth,
58
+ meta: 14,
59
+ }
60
+ }
61
+
62
+
63
+ const DEFAULT_COLORS = {
64
+ npm: '#7ee787',
65
+ git: 'cyan',
66
+ local: '#ff79c6',
67
+ warn: 'yellow',
68
+ add: '#7ee787',
69
+ remove: 'red',
70
+ accent: '#7ee787',
71
+ muted: '#6c7086',
72
+ }
73
+
74
+ /**
75
+ * @param {string} text
76
+ * @param {number} width
77
+ */
78
+ export function padCell(text, width) {
79
+ const raw = String(text)
80
+ if (raw.length >= width) return raw.slice(0, width)
81
+ return raw + ' '.repeat(width - raw.length)
82
+ }
83
+
84
+ /**
85
+ * @param {object} item
86
+ */
87
+ export function selectionMarker(item) {
88
+ const pending = item.pending
89
+ const installed = item.installed
90
+ if (pending === true) return '[+]'
91
+ if (pending === false) return '[-]'
92
+ if (installed) return '[*]'
93
+ return '[ ]'
94
+ }
95
+
96
+ /**
97
+ * @param {object} item
98
+ */
99
+ export function sourceSlots(item) {
100
+ const hasNpm = Boolean(item.sourcesMeta?.npm?.version)
101
+ const hasGit = Boolean(item.htmlUrl || item.sourcesMeta?.github)
102
+ const hasLoc = Boolean(item.localSource)
103
+ return {
104
+ npm: hasNpm ? 'NPM' : '---',
105
+ git: hasGit ? 'GIT' : '---',
106
+ loc: hasLoc ? 'LOC' : '---',
107
+ }
108
+ }
109
+
110
+ /**
111
+ * @param {object} item
112
+ * @param {RowContext} [ctx]
113
+ */
114
+ export function formatSelectionMarker(item, colors = DEFAULT_COLORS) {
115
+ const pending = item.pending
116
+ const installed = item.installed
117
+ if (pending === true) return `{${colors.muted}-fg}[{/}{${colors.add}-fg}+{/}{${colors.muted}-fg}]{/}`
118
+ if (pending === false) return `{${colors.muted}-fg}[{/}{${colors.remove}-fg}-{/}{${colors.muted}-fg}]{/}`
119
+ if (installed) return `{${colors.muted}-fg}[{/}{${colors.accent}-fg}*{/}{${colors.muted}-fg}]{/}`
120
+ return `{${colors.muted}-fg}[ ]{/}`
121
+ }
122
+
123
+ /**
124
+ * @param {object} item
125
+ * @param {RowContext} [ctx]
126
+ */
127
+ export function formatCatalogRowPlain(item, ctx = {}) {
128
+ const colors = { ...DEFAULT_COLORS, ...ctx.colors }
129
+ const columns = { ...COL, ...ctx.columns }
130
+ const slots = sourceSlots(item)
131
+ const publisher = item.org && item.org !== 'workspace' ? item.org : 'owdproject'
132
+ const stars = item.stars > 0 ? `*${item.stars}` : ''
133
+ const age = formatCatalogAge(item.updatedAt ?? item.pushedAt)
134
+
135
+ // 1. Status / Selection Marker (3 chars)
136
+ const selTag = formatSelectionMarker(item, colors)
137
+
138
+ // 2. Name Column (columns.name chars)
139
+ let badgeText = (item.isNew || item.isRecent) ? ' new' : ''
140
+ const maxNameLen = columns.name - badgeText.length
141
+ let namePart = item.shortName
142
+ if (namePart.length > maxNameLen) {
143
+ namePart = namePart.slice(0, maxNameLen)
144
+ }
145
+ let nameTag = `{bold}{${colors.accent}-fg}${namePart}{/}{/}`
146
+ if (badgeText) {
147
+ nameTag += `{${colors.warn}-fg}${badgeText}{/}`
148
+ }
149
+ const namePadLen = columns.name - namePart.length - badgeText.length
150
+ if (namePadLen > 0) {
151
+ nameTag += ' '.repeat(namePadLen)
152
+ }
153
+
154
+ // 3. Sources Column (7 chars)
155
+ const srcNpm =
156
+ slots.npm === 'NPM' ? `{${colors.npm}-fg}${slots.npm}{/}` : `{${colors.muted}-fg}${slots.npm}{/}`
157
+ const srcGit =
158
+ slots.git === 'GIT' ? `{${colors.git}-fg}${slots.git}{/}` : `{${colors.muted}-fg}${slots.git}{/}`
159
+ const sources = `${srcNpm} ${srcGit}`
160
+
161
+ // 3.5 DIR Column (4 chars)
162
+ let dirTag = ''
163
+ const isMissing = item.installed && !item.localSource
164
+ if (isMissing) {
165
+ dirTag = `{red-fg}MISS{/}`
166
+ } else if (item.localSource) {
167
+ dirTag = `{${colors.npm}-fg}OK{/} `
168
+ } else {
169
+ dirTag = `{${colors.muted}-fg}---{/} `
170
+ }
171
+
172
+ // 4. Publisher Column (columns.publisher chars)
173
+ let pubPart = publisher
174
+ if (pubPart.length > columns.publisher) {
175
+ pubPart = pubPart.slice(0, columns.publisher)
176
+ }
177
+ const pubColor = item.trusted === false ? colors.warn : colors.muted
178
+ let pubTag = `{${pubColor}-fg}${pubPart}{/}`
179
+ const pubPadLen = columns.publisher - pubPart.length
180
+ if (pubPadLen > 0) {
181
+ pubTag += ' '.repeat(pubPadLen)
182
+ }
183
+
184
+ // 5. Meta Column (columns.meta chars)
185
+ let finalStars = stars
186
+ let finalAge = age
187
+ const metaText = [stars, age].filter(Boolean).join(' ')
188
+ if (metaText.length > columns.meta) {
189
+ if (stars && age) {
190
+ const allowedAgeLen = columns.meta - stars.length - 1
191
+ if (allowedAgeLen > 0) {
192
+ finalAge = age.slice(0, allowedAgeLen)
193
+ } else {
194
+ finalAge = ''
195
+ finalStars = stars.slice(0, columns.meta)
196
+ }
197
+ } else if (stars) {
198
+ finalStars = stars.slice(0, columns.meta)
199
+ } else if (age) {
200
+ finalAge = age.slice(0, columns.meta)
201
+ }
202
+ }
203
+ const finalMetaText = [finalStars, finalAge].filter(Boolean).join(' ')
204
+ const metaPadLen = Math.max(0, columns.meta - finalMetaText.length)
205
+ let metaTag = ' '.repeat(metaPadLen)
206
+ if (finalStars && finalAge) {
207
+ metaTag += `{yellow-fg}${finalStars}{/} {${colors.muted}-fg}${finalAge}{/}`
208
+ } else if (finalStars) {
209
+ metaTag += `{yellow-fg}${finalStars}{/}`
210
+ } else if (finalAge) {
211
+ metaTag += `{${colors.muted}-fg}${finalAge}{/}`
212
+ }
213
+
214
+ let changesTag = ''
215
+ if (columns.changes > 0) {
216
+ const parts = []
217
+
218
+ // Local Git changes
219
+ if (item.localSource && ctx.localGitChanges && ctx.localGitChanges.has(item.shortName)) {
220
+ const ch = ctx.localGitChanges.get(item.shortName)
221
+ if (ch.added > 0) parts.push(`{${colors.add}-fg}+${ch.added}{/}`)
222
+ if (ch.modified > 0) parts.push(`{${colors.warn}-fg}~${ch.modified}{/}`)
223
+ if (ch.deleted > 0) parts.push(`{${colors.remove}-fg}-${ch.deleted}{/}`)
224
+ }
225
+
226
+ // Upstream Updates
227
+ if (ctx.packageUpdates && ctx.packageUpdates.has(item.shortName)) {
228
+ const updateInfo = ctx.packageUpdates.get(item.shortName)
229
+ if (updateInfo && updateInfo.hasUpdate) {
230
+ if (updateInfo.localGit) {
231
+ const count = updateInfo.behindCount ? `${updateInfo.behindCount}` : ''
232
+ parts.push(`{cyan-fg}↓${count}{/}`)
233
+ } else if (updateInfo.npm) {
234
+ parts.push(`{${colors.npm}-fg}↑{/}`)
235
+ }
236
+ }
237
+ }
238
+
239
+ const txt = parts.join(' ')
240
+ const rawLen = stripTags(txt).length
241
+ changesTag = txt + ' '.repeat(Math.max(0, columns.changes - rawLen))
242
+ }
243
+
244
+ // Join columns with exactly one space (matching header format)
245
+ const cols = [selTag, nameTag, sources, dirTag]
246
+ if (columns.changes > 0) {
247
+ cols.push(changesTag)
248
+ }
249
+ cols.push(pubTag, metaTag)
250
+ return cols.join(' ')
251
+ }
252
+
253
+ /**
254
+ * @param {Record<string, string>} [colors]
255
+ */
256
+ export function formatLegendLine(colors = DEFAULT_COLORS) {
257
+ const c = { ...DEFAULT_COLORS, ...colors }
258
+ return [
259
+ `{${c.add}-fg}[+]{/} add`,
260
+ `{${c.remove}-fg}[-]{/} remove`,
261
+ `{${c.accent}-fg}[*]{/} on desktop`,
262
+ '|',
263
+ `{${c.npm}-fg}NPM{/} registry`,
264
+ `{${c.git}-fg}GIT{/} repo`,
265
+ `{${c.local}-fg}LOC{/} workspace`,
266
+ '|',
267
+ `{${c.npm}-fg}OK{/} folder exists`,
268
+ `{red-fg}MISS{/} folder missing`,
269
+ '|',
270
+ `{cyan-fg}↓{/} git pull`,
271
+ `{${c.npm}-fg}↑{/} npm update`,
272
+ ].join(' ')
273
+ }
274
+
275
+ /**
276
+ * @param {object} item
277
+ * @param {string} targetDir
278
+ * @param {Record<string, string>} [colors]
279
+ * @param {number} [width]
280
+ */
281
+ export function formatDetailPanel(item, targetDir, colors = DEFAULT_COLORS, width = 76) {
282
+ const c = { ...DEFAULT_COLORS, ...colors }
283
+ const owner = item.org && item.org !== 'workspace' ? item.org : 'owdproject'
284
+ const lines = [`{bold}{${c.accent}-fg}${item.shortName}{/}{/}`]
285
+
286
+ const sourceParts = []
287
+ if (item.sourcesMeta?.npm?.version) {
288
+ sourceParts.push(`{${c.npm}-fg}NPM{/} ${item.sourcesMeta.npm.version}`)
289
+ }
290
+ const gitUrl = item.htmlUrl ?? githubHtmlUrl(owner, item.shortName)
291
+ sourceParts.push(`{${c.git}-fg}GIT{/} ${gitUrl}`)
292
+ if (item.localSource) {
293
+ sourceParts.push(`{${c.local}-fg}LOC{/} ${targetDir}/`)
294
+ }
295
+ lines.push(` Sources ..... ${sourceParts.join(' ')}`)
296
+
297
+ const pub =
298
+ item.trusted === false
299
+ ? `{${c.warn}-fg}${owner} (untrusted){/}`
300
+ : owner
301
+ lines.push(` Publisher ... ${pub}`)
302
+
303
+ const starsStr = item.stars > 0 ? `{yellow-fg}★${item.stars}{/}` : '0'
304
+ const dateStr = item.updatedAt ? new Date(item.updatedAt).toLocaleDateString() : '—'
305
+ const kindStr = item.kind ? item.kind.toUpperCase() : '—'
306
+ lines.push(` Stats ....... Stars: ${starsStr} · Updated: ${dateStr} · Type: ${kindStr}`)
307
+
308
+ if (item.localSource) {
309
+ // Show update/change status in details if available
310
+ }
311
+
312
+ if (!item.localSource) {
313
+ const cloneText = ` Clone ....... ${targetDir}/`
314
+ const maxCloneLen = Math.max(15, width - 2)
315
+ const truncatedCloneText = cloneText.length > maxCloneLen ? cloneText.slice(0, maxCloneLen - 3) + '...' : cloneText
316
+ lines.push(` {${c.muted}-fg}${truncatedCloneText}{/}`)
317
+ }
318
+
319
+ if (item.description) {
320
+ const maxDescLen = Math.max(15, width - 4)
321
+ lines.push(` {${c.muted}-fg}${item.description.slice(0, maxDescLen)}{/}`)
322
+ }
323
+
324
+ const isMissing = item.installed && !item.localSource
325
+ if (isMissing) {
326
+ lines.push(` {red-fg}{bold}⚠ WARNING: Local workspace folder is missing!{/}{/}`)
327
+ }
328
+
329
+ return lines.join('\n')
330
+ }
331
+
332
+ export function formatHeaderLine(colors = DEFAULT_COLORS, columns = COL) {
333
+ const status = ' '.repeat(columns.sel || 3) + ' '
334
+ const name = 'NAME'.padEnd(columns.name || 28, ' ') + ' '
335
+ const sources = 'SOURCES'.padEnd(columns.sources || 7, ' ') + ' '
336
+ const dir = 'DIR'.padEnd(columns.dir || 4, ' ') + ' '
337
+ const changes = columns.changes > 0 ? 'SYNC'.padEnd(columns.changes, ' ') + ' ' : ''
338
+ const publisher = 'PUBLISHER'.padEnd(columns.publisher || 20, ' ') + ' '
339
+ const meta = 'STARS/AGE'.padStart(columns.meta || 14, ' ')
340
+ return `{bold}${status}${name}${sources}${dir}${changes}${publisher}${meta}{/}`
341
+ }
342
+
343
+ export { COL as TUI_COLUMN_WIDTHS }
@@ -0,0 +1,8 @@
1
+ export {
2
+ DESKTOP_CONFIG_FILENAME,
3
+ LEGACY_DESKTOP_CONFIG_FILENAME,
4
+ LEGACY_CONFIG_DEPRECATION,
5
+ resolveDesktopConfigPath,
6
+ warnLegacyDesktopConfig,
7
+ desktopConfigWritePath,
8
+ } from '@owdproject/core/kit/authoring.ts'