devvami 1.4.2 → 1.5.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 -0
- package/oclif.manifest.json +129 -89
- package/package.json +2 -1
- package/src/commands/auth/login.js +20 -16
- package/src/commands/changelog.js +12 -12
- package/src/commands/costs/get.js +14 -24
- package/src/commands/costs/trend.js +13 -24
- package/src/commands/create/repo.js +72 -54
- package/src/commands/docs/list.js +29 -25
- package/src/commands/docs/projects.js +58 -24
- package/src/commands/docs/read.js +56 -39
- package/src/commands/docs/search.js +37 -25
- package/src/commands/doctor.js +37 -35
- package/src/commands/dotfiles/add.js +51 -39
- package/src/commands/dotfiles/setup.js +62 -33
- package/src/commands/dotfiles/status.js +18 -18
- package/src/commands/dotfiles/sync.js +62 -46
- package/src/commands/init.js +143 -132
- package/src/commands/logs/index.js +10 -16
- package/src/commands/open.js +12 -12
- package/src/commands/pipeline/logs.js +8 -11
- package/src/commands/pipeline/rerun.js +21 -16
- package/src/commands/pipeline/status.js +28 -24
- package/src/commands/pr/create.js +40 -27
- package/src/commands/pr/detail.js +9 -7
- package/src/commands/pr/review.js +18 -19
- package/src/commands/pr/status.js +27 -21
- package/src/commands/prompts/browse.js +15 -15
- package/src/commands/prompts/download.js +15 -16
- package/src/commands/prompts/install-speckit.js +11 -12
- package/src/commands/prompts/list.js +12 -12
- package/src/commands/prompts/run.js +16 -19
- package/src/commands/repo/list.js +57 -41
- package/src/commands/search.js +20 -18
- package/src/commands/security/setup.js +38 -34
- package/src/commands/sync-config-ai/index.js +143 -0
- package/src/commands/tasks/assigned.js +43 -33
- package/src/commands/tasks/list.js +43 -33
- package/src/commands/tasks/today.js +32 -30
- package/src/commands/upgrade.js +18 -17
- package/src/commands/vuln/detail.js +8 -8
- package/src/commands/vuln/scan.js +39 -20
- package/src/commands/vuln/search.js +23 -18
- package/src/commands/welcome.js +2 -2
- package/src/commands/whoami.js +19 -23
- package/src/formatters/ai-config.js +127 -0
- package/src/formatters/charts.js +6 -23
- package/src/formatters/cost.js +1 -7
- package/src/formatters/dotfiles.js +48 -19
- package/src/formatters/markdown.js +11 -6
- package/src/formatters/openapi.js +7 -9
- package/src/formatters/prompts.js +69 -78
- package/src/formatters/security.js +2 -2
- package/src/formatters/status.js +1 -1
- package/src/formatters/table.js +1 -3
- package/src/formatters/vuln.js +33 -20
- package/src/help.js +162 -164
- package/src/hooks/init.js +1 -3
- package/src/hooks/postrun.js +5 -7
- package/src/index.js +1 -1
- package/src/services/ai-config-store.js +318 -0
- package/src/services/ai-env-deployer.js +444 -0
- package/src/services/ai-env-scanner.js +242 -0
- package/src/services/audit-detector.js +2 -2
- package/src/services/audit-runner.js +40 -31
- package/src/services/auth.js +9 -9
- package/src/services/awesome-copilot.js +7 -4
- package/src/services/aws-costs.js +22 -22
- package/src/services/clickup.js +26 -26
- package/src/services/cloudwatch-logs.js +5 -9
- package/src/services/config.js +13 -13
- package/src/services/docs.js +19 -20
- package/src/services/dotfiles.js +149 -51
- package/src/services/github.js +22 -24
- package/src/services/nvd.js +21 -31
- package/src/services/platform.js +2 -2
- package/src/services/prompts.js +23 -35
- package/src/services/security.js +135 -61
- package/src/services/shell.js +4 -4
- package/src/services/skills-sh.js +3 -9
- package/src/services/speckit.js +4 -7
- package/src/services/version-check.js +10 -10
- package/src/types.js +85 -0
- package/src/utils/aws-vault.js +18 -41
- package/src/utils/banner.js +5 -7
- package/src/utils/errors.js +42 -46
- package/src/utils/frontmatter.js +4 -4
- package/src/utils/gradient.js +18 -16
- package/src/utils/open-browser.js +3 -3
- package/src/utils/tui/form.js +1006 -0
- package/src/utils/tui/modal.js +15 -14
- package/src/utils/tui/navigable-table.js +16 -16
- package/src/utils/tui/tab-tui.js +800 -0
- package/src/utils/typewriter.js +3 -3
- package/src/utils/welcome.js +18 -21
- package/src/validators/repo-name.js +2 -2
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command, Args, Flags} from '@oclif/core'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import ora from 'ora'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
4
|
+
import {input, confirm} from '@inquirer/prompts'
|
|
5
|
+
import {listTemplates, createFromTemplate, setBranchProtection, enableDependabot} from '../../services/github.js'
|
|
6
|
+
import {loadConfig} from '../../services/config.js'
|
|
7
|
+
import {validateRepoName} from '../../validators/repo-name.js'
|
|
8
|
+
import {renderTable} from '../../formatters/table.js'
|
|
9
|
+
import {exec} from '../../services/shell.js'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* @param {string} lang
|
|
@@ -16,15 +16,15 @@ function langColor(lang) {
|
|
|
16
16
|
const map = {
|
|
17
17
|
javascript: chalk.yellow,
|
|
18
18
|
typescript: chalk.blue,
|
|
19
|
-
python:
|
|
20
|
-
java:
|
|
21
|
-
go:
|
|
22
|
-
ruby:
|
|
23
|
-
rust:
|
|
24
|
-
kotlin:
|
|
25
|
-
swift:
|
|
26
|
-
php:
|
|
27
|
-
shell:
|
|
19
|
+
python: chalk.green,
|
|
20
|
+
java: chalk.red,
|
|
21
|
+
go: chalk.cyan,
|
|
22
|
+
ruby: chalk.magenta,
|
|
23
|
+
rust: chalk.hex('#CE422B'),
|
|
24
|
+
kotlin: chalk.hex('#7F52FF'),
|
|
25
|
+
swift: chalk.hex('#F05138'),
|
|
26
|
+
php: chalk.hex('#777BB4'),
|
|
27
|
+
shell: chalk.greenBright,
|
|
28
28
|
}
|
|
29
29
|
const fn = map[lang.toLowerCase()]
|
|
30
30
|
return fn ? fn(lang) : chalk.dim(lang)
|
|
@@ -43,21 +43,21 @@ export default class CreateRepo extends Command {
|
|
|
43
43
|
static enableJsonFlag = true
|
|
44
44
|
|
|
45
45
|
static args = {
|
|
46
|
-
template: Args.string({
|
|
46
|
+
template: Args.string({description: 'Nome del template', required: false}),
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
static flags = {
|
|
50
|
-
list:
|
|
51
|
-
search:
|
|
52
|
-
name:
|
|
53
|
-
description: Flags.string({
|
|
54
|
-
private:
|
|
55
|
-
public:
|
|
56
|
-
'dry-run':
|
|
50
|
+
list: Flags.boolean({description: 'Lista template disponibili', default: false}),
|
|
51
|
+
search: Flags.string({char: 's', description: 'Cerca in nome e descrizione dei template (case-insensitive)'}),
|
|
52
|
+
name: Flags.string({description: 'Nome del nuovo repository'}),
|
|
53
|
+
description: Flags.string({description: 'Descrizione del repository', default: ''}),
|
|
54
|
+
private: Flags.boolean({description: 'Repository privato (default)', default: true}),
|
|
55
|
+
public: Flags.boolean({description: 'Repository pubblico', default: false}),
|
|
56
|
+
'dry-run': Flags.boolean({description: 'Preview senza eseguire', default: false}),
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
async run() {
|
|
60
|
-
const {
|
|
60
|
+
const {args, flags} = await this.parse(CreateRepo)
|
|
61
61
|
const isJson = flags.json
|
|
62
62
|
const isDryRun = flags['dry-run']
|
|
63
63
|
const config = await loadConfig()
|
|
@@ -68,51 +68,58 @@ export default class CreateRepo extends Command {
|
|
|
68
68
|
|
|
69
69
|
// --list mode
|
|
70
70
|
if (flags.list || !args.template) {
|
|
71
|
-
const spinner = isJson
|
|
71
|
+
const spinner = isJson
|
|
72
|
+
? null
|
|
73
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Fetching templates...')}).start()
|
|
72
74
|
const templates = await listTemplates(config.org)
|
|
73
75
|
spinner?.stop()
|
|
74
76
|
|
|
75
77
|
// Search filter
|
|
76
78
|
const searchQuery = flags.search?.toLowerCase()
|
|
77
79
|
const filtered = searchQuery
|
|
78
|
-
? templates.filter(
|
|
79
|
-
t.name.toLowerCase().includes(searchQuery) ||
|
|
80
|
-
t.description.toLowerCase().includes(searchQuery),
|
|
80
|
+
? templates.filter(
|
|
81
|
+
(t) => t.name.toLowerCase().includes(searchQuery) || t.description.toLowerCase().includes(searchQuery),
|
|
81
82
|
)
|
|
82
83
|
: templates
|
|
83
84
|
|
|
84
|
-
if (isJson) return {
|
|
85
|
+
if (isJson) return {templates: filtered}
|
|
85
86
|
|
|
86
87
|
if (templates.length === 0) {
|
|
87
88
|
this.log(chalk.yellow('No templates found in the organization.'))
|
|
88
89
|
this.log(chalk.dim('Templates are GitHub repos marked as "Template repository".'))
|
|
89
|
-
return {
|
|
90
|
+
return {templates: []}
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
if (filtered.length === 0) {
|
|
93
94
|
this.log(chalk.dim(`No templates matching "${flags.search}".`))
|
|
94
|
-
return {
|
|
95
|
+
return {templates: []}
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
const filterInfo = flags.search
|
|
98
|
-
? chalk.dim(' — search: ') + chalk.white(`"${flags.search}"`)
|
|
99
|
-
: ''
|
|
98
|
+
const filterInfo = flags.search ? chalk.dim(' — search: ') + chalk.white(`"${flags.search}"`) : ''
|
|
100
99
|
|
|
101
100
|
this.log(
|
|
102
101
|
chalk.bold('\nAvailable templates') +
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
filterInfo +
|
|
103
|
+
chalk.dim(` (${filtered.length}${filtered.length < templates.length ? `/${templates.length}` : ''})`) +
|
|
104
|
+
'\n',
|
|
106
105
|
)
|
|
107
106
|
|
|
108
|
-
this.log(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
107
|
+
this.log(
|
|
108
|
+
renderTable(filtered, [
|
|
109
|
+
{header: 'Name', key: 'name', width: 35},
|
|
110
|
+
{
|
|
111
|
+
header: 'Language',
|
|
112
|
+
key: 'language',
|
|
113
|
+
width: 14,
|
|
114
|
+
format: (v) => v || '—',
|
|
115
|
+
colorize: (v) => (v === '—' ? chalk.dim(v) : langColor(v)),
|
|
116
|
+
},
|
|
117
|
+
{header: 'Description', key: 'description', width: 60, format: (v) => String(v || '—')},
|
|
118
|
+
]),
|
|
119
|
+
)
|
|
113
120
|
|
|
114
121
|
this.log('')
|
|
115
|
-
return {
|
|
122
|
+
return {templates: filtered}
|
|
116
123
|
}
|
|
117
124
|
|
|
118
125
|
// Create mode
|
|
@@ -126,7 +133,7 @@ export default class CreateRepo extends Command {
|
|
|
126
133
|
// Get repo name
|
|
127
134
|
let repoName = flags.name
|
|
128
135
|
if (!repoName && !isJson) {
|
|
129
|
-
repoName = await input({
|
|
136
|
+
repoName = await input({message: 'Repository name:'})
|
|
130
137
|
} else if (!repoName) {
|
|
131
138
|
this.error('--name is required in non-interactive mode')
|
|
132
139
|
}
|
|
@@ -142,13 +149,16 @@ export default class CreateRepo extends Command {
|
|
|
142
149
|
const ok = await confirm({
|
|
143
150
|
message: `Create ${isPrivate ? 'private' : 'public'} repo "${config.org}/${repoName}" from "${args.template}"?`,
|
|
144
151
|
})
|
|
145
|
-
if (!ok) {
|
|
152
|
+
if (!ok) {
|
|
153
|
+
this.log('Aborted.')
|
|
154
|
+
return
|
|
155
|
+
}
|
|
146
156
|
}
|
|
147
157
|
|
|
148
158
|
if (isDryRun) {
|
|
149
159
|
const preview = {
|
|
150
|
-
repository: {
|
|
151
|
-
postScaffolding: {
|
|
160
|
+
repository: {name: repoName, org: config.org, template: args.template, private: isPrivate},
|
|
161
|
+
postScaffolding: {branchProtection: 'would configure', dependabot: 'would enable', codeowners: 'would create'},
|
|
152
162
|
}
|
|
153
163
|
if (isJson) return preview
|
|
154
164
|
this.log(chalk.bold('\nDry run preview:'))
|
|
@@ -157,7 +167,9 @@ export default class CreateRepo extends Command {
|
|
|
157
167
|
}
|
|
158
168
|
|
|
159
169
|
// Create repo
|
|
160
|
-
const spinner = isJson
|
|
170
|
+
const spinner = isJson
|
|
171
|
+
? null
|
|
172
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Creating repository...')}).start()
|
|
161
173
|
const repo = await createFromTemplate({
|
|
162
174
|
templateOwner: config.org,
|
|
163
175
|
templateRepo: args.template,
|
|
@@ -169,22 +181,28 @@ export default class CreateRepo extends Command {
|
|
|
169
181
|
spinner?.succeed(`Repository created: ${repo.htmlUrl}`)
|
|
170
182
|
|
|
171
183
|
// Post-scaffolding
|
|
172
|
-
const bpSpinner = isJson
|
|
184
|
+
const bpSpinner = isJson
|
|
185
|
+
? null
|
|
186
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Configuring branch protection...')}).start()
|
|
173
187
|
await setBranchProtection(config.org, repoName).catch(() => null)
|
|
174
188
|
bpSpinner?.succeed('Branch protection configured')
|
|
175
189
|
|
|
176
|
-
const depSpinner = isJson
|
|
190
|
+
const depSpinner = isJson
|
|
191
|
+
? null
|
|
192
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Enabling Dependabot...')}).start()
|
|
177
193
|
await enableDependabot(config.org, repoName).catch(() => null)
|
|
178
194
|
depSpinner?.succeed('Dependabot enabled')
|
|
179
195
|
|
|
180
196
|
// Clone
|
|
181
|
-
const cloneSpinner = isJson
|
|
197
|
+
const cloneSpinner = isJson
|
|
198
|
+
? null
|
|
199
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Cloning repository...')}).start()
|
|
182
200
|
await exec('gh', ['repo', 'clone', `${config.org}/${repoName}`])
|
|
183
201
|
cloneSpinner?.succeed(`Cloned to ./${repoName}`)
|
|
184
202
|
|
|
185
203
|
const result = {
|
|
186
|
-
repository: {
|
|
187
|
-
postScaffolding: {
|
|
204
|
+
repository: {name: repoName, url: repo.htmlUrl, localPath: `./${repoName}`},
|
|
205
|
+
postScaffolding: {branchProtection: 'ok', dependabot: 'ok', codeowners: 'ok'},
|
|
188
206
|
}
|
|
189
207
|
|
|
190
208
|
if (!isJson) {
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command, Flags} from '@oclif/core'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import ora from 'ora'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import {loadConfig} from '../../services/config.js'
|
|
5
|
+
import {listDocs, detectCurrentRepo} from '../../services/docs.js'
|
|
6
|
+
import {renderTable} from '../../formatters/table.js'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @param {string} type
|
|
10
10
|
* @returns {string}
|
|
11
11
|
*/
|
|
12
12
|
function typeColor(type) {
|
|
13
|
-
if (type === 'readme')
|
|
14
|
-
if (type === 'swagger')
|
|
13
|
+
if (type === 'readme') return chalk.cyan(type)
|
|
14
|
+
if (type === 'swagger') return chalk.yellow(type)
|
|
15
15
|
if (type === 'asyncapi') return chalk.green(type)
|
|
16
16
|
return chalk.dim(type)
|
|
17
17
|
}
|
|
@@ -38,12 +38,12 @@ export default class DocsList extends Command {
|
|
|
38
38
|
static enableJsonFlag = true
|
|
39
39
|
|
|
40
40
|
static flags = {
|
|
41
|
-
repo:
|
|
42
|
-
search: Flags.string({
|
|
41
|
+
repo: Flags.string({char: 'r', description: 'Nome del repository (default: repo nella directory corrente)'}),
|
|
42
|
+
search: Flags.string({char: 's', description: 'Filtra per nome o percorso (case-insensitive)'}),
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
async run() {
|
|
46
|
-
const {
|
|
46
|
+
const {flags} = await this.parse(DocsList)
|
|
47
47
|
const isJson = flags.json
|
|
48
48
|
const config = await loadConfig()
|
|
49
49
|
|
|
@@ -55,13 +55,15 @@ export default class DocsList extends Command {
|
|
|
55
55
|
repo = flags.repo
|
|
56
56
|
} else {
|
|
57
57
|
try {
|
|
58
|
-
;({
|
|
58
|
+
;({owner, repo} = await detectCurrentRepo())
|
|
59
59
|
} catch (err) {
|
|
60
60
|
this.error(/** @type {Error} */ (err).message)
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
const spinner = isJson
|
|
64
|
+
const spinner = isJson
|
|
65
|
+
? null
|
|
66
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Fetching documentation...')}).start()
|
|
65
67
|
let entries
|
|
66
68
|
try {
|
|
67
69
|
entries = await listDocs(owner, repo)
|
|
@@ -77,34 +79,36 @@ export default class DocsList extends Command {
|
|
|
77
79
|
? entries.filter((e) => e.name.toLowerCase().includes(q) || e.path.toLowerCase().includes(q))
|
|
78
80
|
: entries
|
|
79
81
|
|
|
80
|
-
if (isJson) return {
|
|
82
|
+
if (isJson) return {repo, owner, entries: filtered, total: filtered.length}
|
|
81
83
|
|
|
82
84
|
if (entries.length === 0) {
|
|
83
85
|
this.log(chalk.dim(`No documentation found in ${owner}/${repo}.`))
|
|
84
|
-
return {
|
|
86
|
+
return {repo, owner, entries: [], total: 0}
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
if (filtered.length === 0) {
|
|
88
90
|
this.log(chalk.dim(`No documentation matching "${flags.search}" in ${owner}/${repo}.`))
|
|
89
|
-
return {
|
|
91
|
+
return {repo, owner, entries: [], total: 0}
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
const filterInfo = q ? chalk.dim(` — search: ${chalk.white(`"${flags.search}"`)}`): ''
|
|
94
|
+
const filterInfo = q ? chalk.dim(` — search: ${chalk.white(`"${flags.search}"`)}`) : ''
|
|
93
95
|
this.log(
|
|
94
96
|
chalk.bold(`\nDocumentation in ${owner}/${repo}`) +
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
filterInfo +
|
|
98
|
+
chalk.dim(` (${filtered.length}${filtered.length < entries.length ? `/${entries.length}` : ''})`) +
|
|
99
|
+
'\n',
|
|
98
100
|
)
|
|
99
101
|
|
|
100
|
-
this.log(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
this.log(
|
|
103
|
+
renderTable(filtered, [
|
|
104
|
+
{header: 'Type', key: 'type', width: 10, colorize: typeColor},
|
|
105
|
+
{header: 'Name', key: 'name', width: 30},
|
|
106
|
+
{header: 'Path', key: 'path', width: 50},
|
|
107
|
+
{header: 'Size', key: 'size', width: 8, format: (v) => formatSize(Number(v))},
|
|
108
|
+
]),
|
|
109
|
+
)
|
|
106
110
|
this.log('')
|
|
107
111
|
|
|
108
|
-
return {
|
|
112
|
+
return {repo, owner, entries: filtered, total: filtered.length}
|
|
109
113
|
}
|
|
110
114
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command, Flags} from '@oclif/core'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import ora from 'ora'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import {loadConfig} from '../../services/config.js'
|
|
5
|
+
import {listRepos} from '../../services/github.js'
|
|
6
|
+
import {listProjectsDocs} from '../../services/docs.js'
|
|
7
|
+
import {renderTable} from '../../formatters/table.js'
|
|
8
8
|
|
|
9
9
|
export default class DocsProjects extends Command {
|
|
10
|
-
static description =
|
|
10
|
+
static description = "Mostra la documentazione disponibile per ogni repository dell'organizzazione"
|
|
11
11
|
|
|
12
12
|
static examples = [
|
|
13
13
|
'<%= config.bin %> docs projects',
|
|
@@ -18,11 +18,11 @@ export default class DocsProjects extends Command {
|
|
|
18
18
|
static enableJsonFlag = true
|
|
19
19
|
|
|
20
20
|
static flags = {
|
|
21
|
-
search: Flags.string({
|
|
21
|
+
search: Flags.string({char: 's', description: 'Filtra per nome repository (case-insensitive)'}),
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
async run() {
|
|
25
|
-
const {
|
|
25
|
+
const {flags} = await this.parse(DocsProjects)
|
|
26
26
|
const isJson = flags.json
|
|
27
27
|
const config = await loadConfig()
|
|
28
28
|
|
|
@@ -31,7 +31,9 @@ export default class DocsProjects extends Command {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// 1. Fetch all repos
|
|
34
|
-
const repoSpinner = isJson
|
|
34
|
+
const repoSpinner = isJson
|
|
35
|
+
? null
|
|
36
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Fetching repositories...')}).start()
|
|
35
37
|
let repos
|
|
36
38
|
try {
|
|
37
39
|
repos = await listRepos(config.org)
|
|
@@ -43,7 +45,7 @@ export default class DocsProjects extends Command {
|
|
|
43
45
|
|
|
44
46
|
if (repos.length === 0) {
|
|
45
47
|
this.log(chalk.dim(`No repositories found in organization "${config.org}".`))
|
|
46
|
-
return {
|
|
48
|
+
return {org: config.org, projects: [], total: 0}
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
// 2. Filter by search
|
|
@@ -52,11 +54,17 @@ export default class DocsProjects extends Command {
|
|
|
52
54
|
|
|
53
55
|
if (filteredRepos.length === 0) {
|
|
54
56
|
this.log(chalk.dim(`No repositories matching "${flags.search}" in ${config.org}.`))
|
|
55
|
-
return {
|
|
57
|
+
return {org: config.org, projects: [], total: 0}
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
// 3. Scan each repo for docs
|
|
59
|
-
const scanSpinner = isJson
|
|
61
|
+
const scanSpinner = isJson
|
|
62
|
+
? null
|
|
63
|
+
: ora({
|
|
64
|
+
spinner: 'arc',
|
|
65
|
+
color: false,
|
|
66
|
+
text: chalk.hex('#FF6B2B')(`Scanning docs in ${filteredRepos.length} repositories...`),
|
|
67
|
+
}).start()
|
|
60
68
|
|
|
61
69
|
const repoNames = filteredRepos.map((r) => r.name)
|
|
62
70
|
let projects
|
|
@@ -68,25 +76,51 @@ export default class DocsProjects extends Command {
|
|
|
68
76
|
}
|
|
69
77
|
scanSpinner?.stop()
|
|
70
78
|
|
|
71
|
-
if (isJson) return {
|
|
79
|
+
if (isJson) return {org: config.org, projects, total: projects.length}
|
|
72
80
|
|
|
73
81
|
const filterInfo = q ? chalk.dim(` — search: ${chalk.white(`"${flags.search}"`)}`) : ''
|
|
74
82
|
this.log(
|
|
75
83
|
chalk.bold(`\nDocumentation overview for ${config.org}`) +
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
filterInfo +
|
|
85
|
+
chalk.dim(` (${projects.length}${projects.length < repos.length ? `/${repos.length}` : ''})`) +
|
|
86
|
+
'\n',
|
|
79
87
|
)
|
|
80
88
|
|
|
81
|
-
this.log(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
this.log(
|
|
90
|
+
renderTable(projects, [
|
|
91
|
+
{header: 'Repository', key: 'repo', width: 40},
|
|
92
|
+
{
|
|
93
|
+
header: 'README',
|
|
94
|
+
key: 'hasReadme',
|
|
95
|
+
width: 8,
|
|
96
|
+
format: (v) => (v ? '✓' : '—'),
|
|
97
|
+
colorize: (v) => (v === '✓' ? chalk.green(v) : chalk.dim(v)),
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
header: 'Docs',
|
|
101
|
+
key: 'docsCount',
|
|
102
|
+
width: 6,
|
|
103
|
+
format: (v) => (Number(v) > 0 ? String(v) : '—'),
|
|
104
|
+
colorize: (v) => (v !== '—' ? chalk.cyan(v) : chalk.dim(v)),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
header: 'Swagger',
|
|
108
|
+
key: 'hasSwagger',
|
|
109
|
+
width: 9,
|
|
110
|
+
format: (v) => (v ? '✓' : '—'),
|
|
111
|
+
colorize: (v) => (v === '✓' ? chalk.yellow(v) : chalk.dim(v)),
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
header: 'AsyncAPI',
|
|
115
|
+
key: 'hasAsyncApi',
|
|
116
|
+
width: 10,
|
|
117
|
+
format: (v) => (v ? '✓' : '—'),
|
|
118
|
+
colorize: (v) => (v === '✓' ? chalk.green(v) : chalk.dim(v)),
|
|
119
|
+
},
|
|
120
|
+
]),
|
|
121
|
+
)
|
|
88
122
|
this.log('')
|
|
89
123
|
|
|
90
|
-
return {
|
|
124
|
+
return {org: config.org, projects, total: projects.length}
|
|
91
125
|
}
|
|
92
126
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command, Args, Flags} from '@oclif/core'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import ora from 'ora'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
4
|
+
import {loadConfig} from '../../services/config.js'
|
|
5
|
+
import {listDocs, readFile, detectCurrentRepo, detectApiSpecType} from '../../services/docs.js'
|
|
6
|
+
import {renderMarkdown, extractMermaidBlocks, toMermaidLiveUrl} from '../../formatters/markdown.js'
|
|
7
|
+
import {parseOpenApi, parseAsyncApi} from '../../formatters/openapi.js'
|
|
8
|
+
import {renderTable} from '../../formatters/table.js'
|
|
9
|
+
import {openBrowser} from '../../utils/open-browser.js'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* @param {string} method
|
|
@@ -52,17 +52,17 @@ export default class DocsRead extends Command {
|
|
|
52
52
|
static enableJsonFlag = true
|
|
53
53
|
|
|
54
54
|
static args = {
|
|
55
|
-
file: Args.string({
|
|
55
|
+
file: Args.string({description: 'Percorso del file da leggere (default: README)', required: false}),
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
static flags = {
|
|
59
|
-
repo:
|
|
60
|
-
raw:
|
|
61
|
-
render: Flags.boolean({
|
|
59
|
+
repo: Flags.string({char: 'r', description: 'Nome del repository (default: repo nella directory corrente)'}),
|
|
60
|
+
raw: Flags.boolean({description: 'Mostra contenuto grezzo senza parsing speciale', default: false}),
|
|
61
|
+
render: Flags.boolean({description: 'Apri i diagrammi Mermaid nel browser via mermaid.live', default: false}),
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
async run() {
|
|
65
|
-
const {
|
|
65
|
+
const {args, flags} = await this.parse(DocsRead)
|
|
66
66
|
const isJson = flags.json
|
|
67
67
|
const config = await loadConfig()
|
|
68
68
|
|
|
@@ -74,7 +74,7 @@ export default class DocsRead extends Command {
|
|
|
74
74
|
repo = flags.repo
|
|
75
75
|
} else {
|
|
76
76
|
try {
|
|
77
|
-
;({
|
|
77
|
+
;({owner, repo} = await detectCurrentRepo())
|
|
78
78
|
} catch (err) {
|
|
79
79
|
this.error(/** @type {Error} */ (err).message)
|
|
80
80
|
}
|
|
@@ -83,7 +83,9 @@ export default class DocsRead extends Command {
|
|
|
83
83
|
// Resolve file path
|
|
84
84
|
let filePath = args.file
|
|
85
85
|
if (!filePath) {
|
|
86
|
-
const spinner = isJson
|
|
86
|
+
const spinner = isJson
|
|
87
|
+
? null
|
|
88
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Looking for README...')}).start()
|
|
87
89
|
let entries
|
|
88
90
|
try {
|
|
89
91
|
entries = await listDocs(owner, repo)
|
|
@@ -95,24 +97,35 @@ export default class DocsRead extends Command {
|
|
|
95
97
|
const readme = entries.find((e) => e.type === 'readme')
|
|
96
98
|
if (!readme) {
|
|
97
99
|
this.log(chalk.dim(`No README found in ${owner}/${repo}.`))
|
|
98
|
-
return {
|
|
100
|
+
return {repo, owner, path: null, type: null, content: null, size: 0}
|
|
99
101
|
}
|
|
100
102
|
filePath = readme.path
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
// Fetch content
|
|
104
|
-
const spinner2 = isJson
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
const spinner2 = isJson
|
|
107
|
+
? null
|
|
108
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')(`Reading ${filePath}...`)}).start()
|
|
109
|
+
let content
|
|
110
|
+
try {
|
|
111
|
+
content = await readFile(owner, repo, filePath)
|
|
112
|
+
} catch {
|
|
113
|
+
spinner2?.stop()
|
|
114
|
+
this.error(
|
|
115
|
+
`File "${filePath}" not found in ${owner}/${repo}. Run \`dvmi docs list\` to see available documentation.`,
|
|
116
|
+
)
|
|
117
|
+
}
|
|
112
118
|
spinner2?.stop()
|
|
113
119
|
|
|
114
120
|
if (isJson) {
|
|
115
|
-
return {
|
|
121
|
+
return {
|
|
122
|
+
repo,
|
|
123
|
+
owner,
|
|
124
|
+
path: filePath,
|
|
125
|
+
type: detectApiSpecType(filePath, content) ?? 'doc',
|
|
126
|
+
content,
|
|
127
|
+
size: content.length,
|
|
128
|
+
}
|
|
116
129
|
}
|
|
117
130
|
|
|
118
131
|
// Handle --render (Mermaid)
|
|
@@ -133,33 +146,37 @@ export default class DocsRead extends Command {
|
|
|
133
146
|
|
|
134
147
|
// Render
|
|
135
148
|
if (!flags.raw && specType === 'swagger') {
|
|
136
|
-
const {
|
|
149
|
+
const {endpoints, error} = parseOpenApi(content)
|
|
137
150
|
if (error || endpoints.length === 0) {
|
|
138
151
|
this.log(chalk.yellow(`⚠ Could not parse "${filePath}" as OpenAPI spec (showing raw content). ${error ?? ''}`))
|
|
139
152
|
this.log(content)
|
|
140
153
|
} else {
|
|
141
154
|
this.log(chalk.bold(`\nAPI Endpoints — ${filePath}\n`))
|
|
142
|
-
this.log(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
155
|
+
this.log(
|
|
156
|
+
renderTable(endpoints, [
|
|
157
|
+
{header: 'Method', key: 'method', width: 8, colorize: methodColor},
|
|
158
|
+
{header: 'Path', key: 'path', width: 45},
|
|
159
|
+
{header: 'Summary', key: 'summary', width: 40},
|
|
160
|
+
{header: 'Parameters', key: 'parameters', width: 30, format: (v) => v || '—'},
|
|
161
|
+
]),
|
|
162
|
+
)
|
|
148
163
|
this.log('')
|
|
149
164
|
}
|
|
150
165
|
} else if (!flags.raw && specType === 'asyncapi') {
|
|
151
|
-
const {
|
|
166
|
+
const {channels, error} = parseAsyncApi(content)
|
|
152
167
|
if (error || channels.length === 0) {
|
|
153
168
|
this.log(chalk.yellow(`⚠ Could not parse "${filePath}" as AsyncAPI spec (showing raw content). ${error ?? ''}`))
|
|
154
169
|
this.log(content)
|
|
155
170
|
} else {
|
|
156
171
|
this.log(chalk.bold(`\nAsyncAPI Channels — ${filePath}\n`))
|
|
157
|
-
this.log(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
172
|
+
this.log(
|
|
173
|
+
renderTable(channels, [
|
|
174
|
+
{header: 'Channel', key: 'channel', width: 35},
|
|
175
|
+
{header: 'Operation', key: 'operation', width: 12, colorize: opColor},
|
|
176
|
+
{header: 'Summary', key: 'summary', width: 40},
|
|
177
|
+
{header: 'Message', key: 'message', width: 25, format: (v) => v || '—'},
|
|
178
|
+
]),
|
|
179
|
+
)
|
|
163
180
|
this.log('')
|
|
164
181
|
}
|
|
165
182
|
} else {
|
|
@@ -167,6 +184,6 @@ export default class DocsRead extends Command {
|
|
|
167
184
|
this.log(renderMarkdown(content))
|
|
168
185
|
}
|
|
169
186
|
|
|
170
|
-
return {
|
|
187
|
+
return {repo, owner, path: filePath, type: specType ?? 'doc', content, size: content.length}
|
|
171
188
|
}
|
|
172
189
|
}
|