@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.
- package/LICENSE +20 -0
- package/README.md +50 -16
- package/bin/bridge.js +127 -0
- package/bin/cli.js +433 -0
- package/bin/cp.js +3523 -0
- package/bin/desktop.js +5 -0
- package/bin/lib/catalog.js +501 -0
- package/bin/lib/config.js +130 -0
- package/bin/lib/cpAscii.js +39 -0
- package/bin/lib/cpFormat.js +343 -0
- package/bin/lib/desktopConfig.js +8 -0
- package/bin/lib/generateTemplate.js +426 -0
- package/bin/lib/install.js +525 -0
- package/bin/lib/installWizardFlow.js +726 -0
- package/bin/lib/migrateModulePlayground.config.mjs +187 -0
- package/bin/lib/migrateModulePlayground.mjs +318 -0
- package/bin/lib/npmVersions.js +124 -0
- package/bin/lib/packageSources.js +365 -0
- package/bin/lib/playgroundContext.js +188 -0
- package/bin/lib/scaffold.js +205 -0
- package/bin/lib/status.js +317 -0
- package/bin/lib/validateModule.js +868 -0
- package/bin/lib/workspace.js +222 -0
- package/bin/owd.js +2 -118
- package/go/bridge/bridge.go +200 -0
- package/go/desktop-go +0 -0
- package/go/go.mod +26 -0
- package/go/go.sum +37 -0
- package/go/main.go +30 -0
- package/go/tui/tui.go +3014 -0
- package/package.json +33 -23
- package/src/commands/create.js +0 -11
- package/src/commands/generate.js +0 -12
- package/src/commands/installApp.js +0 -9
- package/src/commands/installTheme.js +0 -9
- package/src/commands/start.js +0 -13
- package/src/utils/cloneRepo.js +0 -25
- package/src/utils/installPackage.js +0 -15
- package/src/utils/logging.js +0 -15
|
@@ -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 }
|