devvami 1.4.1 → 1.4.2
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/oclif.manifest.json +89 -89
- package/package.json +1 -1
- package/src/commands/vuln/scan.js +66 -11
- package/src/utils/tui/navigable-table.js +9 -1
package/oclif.manifest.json
CHANGED
|
@@ -520,94 +520,6 @@
|
|
|
520
520
|
"trend.js"
|
|
521
521
|
]
|
|
522
522
|
},
|
|
523
|
-
"create:repo": {
|
|
524
|
-
"aliases": [],
|
|
525
|
-
"args": {
|
|
526
|
-
"template": {
|
|
527
|
-
"description": "Nome del template",
|
|
528
|
-
"name": "template",
|
|
529
|
-
"required": false
|
|
530
|
-
}
|
|
531
|
-
},
|
|
532
|
-
"description": "Crea nuovo progetto da template GitHub o lista i template disponibili",
|
|
533
|
-
"examples": [
|
|
534
|
-
"<%= config.bin %> create repo --list",
|
|
535
|
-
"<%= config.bin %> create repo --list --search \"lambda\"",
|
|
536
|
-
"<%= config.bin %> create repo template-lambda",
|
|
537
|
-
"<%= config.bin %> create repo template-lambda --name my-service --dry-run"
|
|
538
|
-
],
|
|
539
|
-
"flags": {
|
|
540
|
-
"json": {
|
|
541
|
-
"description": "Format output as json.",
|
|
542
|
-
"helpGroup": "GLOBAL",
|
|
543
|
-
"name": "json",
|
|
544
|
-
"allowNo": false,
|
|
545
|
-
"type": "boolean"
|
|
546
|
-
},
|
|
547
|
-
"list": {
|
|
548
|
-
"description": "Lista template disponibili",
|
|
549
|
-
"name": "list",
|
|
550
|
-
"allowNo": false,
|
|
551
|
-
"type": "boolean"
|
|
552
|
-
},
|
|
553
|
-
"search": {
|
|
554
|
-
"char": "s",
|
|
555
|
-
"description": "Cerca in nome e descrizione dei template (case-insensitive)",
|
|
556
|
-
"name": "search",
|
|
557
|
-
"hasDynamicHelp": false,
|
|
558
|
-
"multiple": false,
|
|
559
|
-
"type": "option"
|
|
560
|
-
},
|
|
561
|
-
"name": {
|
|
562
|
-
"description": "Nome del nuovo repository",
|
|
563
|
-
"name": "name",
|
|
564
|
-
"hasDynamicHelp": false,
|
|
565
|
-
"multiple": false,
|
|
566
|
-
"type": "option"
|
|
567
|
-
},
|
|
568
|
-
"description": {
|
|
569
|
-
"description": "Descrizione del repository",
|
|
570
|
-
"name": "description",
|
|
571
|
-
"default": "",
|
|
572
|
-
"hasDynamicHelp": false,
|
|
573
|
-
"multiple": false,
|
|
574
|
-
"type": "option"
|
|
575
|
-
},
|
|
576
|
-
"private": {
|
|
577
|
-
"description": "Repository privato (default)",
|
|
578
|
-
"name": "private",
|
|
579
|
-
"allowNo": false,
|
|
580
|
-
"type": "boolean"
|
|
581
|
-
},
|
|
582
|
-
"public": {
|
|
583
|
-
"description": "Repository pubblico",
|
|
584
|
-
"name": "public",
|
|
585
|
-
"allowNo": false,
|
|
586
|
-
"type": "boolean"
|
|
587
|
-
},
|
|
588
|
-
"dry-run": {
|
|
589
|
-
"description": "Preview senza eseguire",
|
|
590
|
-
"name": "dry-run",
|
|
591
|
-
"allowNo": false,
|
|
592
|
-
"type": "boolean"
|
|
593
|
-
}
|
|
594
|
-
},
|
|
595
|
-
"hasDynamicHelp": false,
|
|
596
|
-
"hiddenAliases": [],
|
|
597
|
-
"id": "create:repo",
|
|
598
|
-
"pluginAlias": "devvami",
|
|
599
|
-
"pluginName": "devvami",
|
|
600
|
-
"pluginType": "core",
|
|
601
|
-
"strict": true,
|
|
602
|
-
"enableJsonFlag": true,
|
|
603
|
-
"isESM": true,
|
|
604
|
-
"relativePath": [
|
|
605
|
-
"src",
|
|
606
|
-
"commands",
|
|
607
|
-
"create",
|
|
608
|
-
"repo.js"
|
|
609
|
-
]
|
|
610
|
-
},
|
|
611
523
|
"docs:list": {
|
|
612
524
|
"aliases": [],
|
|
613
525
|
"args": {},
|
|
@@ -813,6 +725,94 @@
|
|
|
813
725
|
"search.js"
|
|
814
726
|
]
|
|
815
727
|
},
|
|
728
|
+
"create:repo": {
|
|
729
|
+
"aliases": [],
|
|
730
|
+
"args": {
|
|
731
|
+
"template": {
|
|
732
|
+
"description": "Nome del template",
|
|
733
|
+
"name": "template",
|
|
734
|
+
"required": false
|
|
735
|
+
}
|
|
736
|
+
},
|
|
737
|
+
"description": "Crea nuovo progetto da template GitHub o lista i template disponibili",
|
|
738
|
+
"examples": [
|
|
739
|
+
"<%= config.bin %> create repo --list",
|
|
740
|
+
"<%= config.bin %> create repo --list --search \"lambda\"",
|
|
741
|
+
"<%= config.bin %> create repo template-lambda",
|
|
742
|
+
"<%= config.bin %> create repo template-lambda --name my-service --dry-run"
|
|
743
|
+
],
|
|
744
|
+
"flags": {
|
|
745
|
+
"json": {
|
|
746
|
+
"description": "Format output as json.",
|
|
747
|
+
"helpGroup": "GLOBAL",
|
|
748
|
+
"name": "json",
|
|
749
|
+
"allowNo": false,
|
|
750
|
+
"type": "boolean"
|
|
751
|
+
},
|
|
752
|
+
"list": {
|
|
753
|
+
"description": "Lista template disponibili",
|
|
754
|
+
"name": "list",
|
|
755
|
+
"allowNo": false,
|
|
756
|
+
"type": "boolean"
|
|
757
|
+
},
|
|
758
|
+
"search": {
|
|
759
|
+
"char": "s",
|
|
760
|
+
"description": "Cerca in nome e descrizione dei template (case-insensitive)",
|
|
761
|
+
"name": "search",
|
|
762
|
+
"hasDynamicHelp": false,
|
|
763
|
+
"multiple": false,
|
|
764
|
+
"type": "option"
|
|
765
|
+
},
|
|
766
|
+
"name": {
|
|
767
|
+
"description": "Nome del nuovo repository",
|
|
768
|
+
"name": "name",
|
|
769
|
+
"hasDynamicHelp": false,
|
|
770
|
+
"multiple": false,
|
|
771
|
+
"type": "option"
|
|
772
|
+
},
|
|
773
|
+
"description": {
|
|
774
|
+
"description": "Descrizione del repository",
|
|
775
|
+
"name": "description",
|
|
776
|
+
"default": "",
|
|
777
|
+
"hasDynamicHelp": false,
|
|
778
|
+
"multiple": false,
|
|
779
|
+
"type": "option"
|
|
780
|
+
},
|
|
781
|
+
"private": {
|
|
782
|
+
"description": "Repository privato (default)",
|
|
783
|
+
"name": "private",
|
|
784
|
+
"allowNo": false,
|
|
785
|
+
"type": "boolean"
|
|
786
|
+
},
|
|
787
|
+
"public": {
|
|
788
|
+
"description": "Repository pubblico",
|
|
789
|
+
"name": "public",
|
|
790
|
+
"allowNo": false,
|
|
791
|
+
"type": "boolean"
|
|
792
|
+
},
|
|
793
|
+
"dry-run": {
|
|
794
|
+
"description": "Preview senza eseguire",
|
|
795
|
+
"name": "dry-run",
|
|
796
|
+
"allowNo": false,
|
|
797
|
+
"type": "boolean"
|
|
798
|
+
}
|
|
799
|
+
},
|
|
800
|
+
"hasDynamicHelp": false,
|
|
801
|
+
"hiddenAliases": [],
|
|
802
|
+
"id": "create:repo",
|
|
803
|
+
"pluginAlias": "devvami",
|
|
804
|
+
"pluginName": "devvami",
|
|
805
|
+
"pluginType": "core",
|
|
806
|
+
"strict": true,
|
|
807
|
+
"enableJsonFlag": true,
|
|
808
|
+
"isESM": true,
|
|
809
|
+
"relativePath": [
|
|
810
|
+
"src",
|
|
811
|
+
"commands",
|
|
812
|
+
"create",
|
|
813
|
+
"repo.js"
|
|
814
|
+
]
|
|
815
|
+
},
|
|
816
816
|
"dotfiles:add": {
|
|
817
817
|
"aliases": [],
|
|
818
818
|
"args": {
|
|
@@ -2120,5 +2120,5 @@
|
|
|
2120
2120
|
]
|
|
2121
2121
|
}
|
|
2122
2122
|
},
|
|
2123
|
-
"version": "1.4.
|
|
2123
|
+
"version": "1.4.2"
|
|
2124
2124
|
}
|
package/package.json
CHANGED
|
@@ -4,7 +4,20 @@ import ora from 'ora'
|
|
|
4
4
|
import chalk from 'chalk'
|
|
5
5
|
import { detectEcosystems, supportedEcosystemsMessage } from '../../services/audit-detector.js'
|
|
6
6
|
import { runAudit, summarizeFindings, filterBySeverity } from '../../services/audit-runner.js'
|
|
7
|
-
import { formatFindingsTable, formatScanSummary, formatMarkdownReport } from '../../formatters/vuln.js'
|
|
7
|
+
import { formatFindingsTable, formatScanSummary, formatMarkdownReport, truncate, colorSeverity } from '../../formatters/vuln.js'
|
|
8
|
+
import { getCveDetail } from '../../services/nvd.js'
|
|
9
|
+
import { startInteractiveTable } from '../../utils/tui/navigable-table.js'
|
|
10
|
+
|
|
11
|
+
// Minimum terminal rows required to show the interactive TUI (same threshold as vuln search)
|
|
12
|
+
const MIN_TTY_ROWS = 6
|
|
13
|
+
|
|
14
|
+
// Column widths for the navigable table (match the static findings table)
|
|
15
|
+
const COL_WIDTHS = {
|
|
16
|
+
pkg: 20,
|
|
17
|
+
version: 12,
|
|
18
|
+
severity: 10,
|
|
19
|
+
cve: 20,
|
|
20
|
+
}
|
|
8
21
|
|
|
9
22
|
export default class VulnScan extends Command {
|
|
10
23
|
static description = 'Scan the current directory for known vulnerabilities in dependencies'
|
|
@@ -112,7 +125,7 @@ export default class VulnScan extends Command {
|
|
|
112
125
|
errors,
|
|
113
126
|
}
|
|
114
127
|
|
|
115
|
-
// Write report if requested
|
|
128
|
+
// Write report if requested (always, regardless of TTY mode)
|
|
116
129
|
if (report) {
|
|
117
130
|
const markdown = formatMarkdownReport(result)
|
|
118
131
|
await writeFile(report, markdown, 'utf8')
|
|
@@ -128,17 +141,58 @@ export default class VulnScan extends Command {
|
|
|
128
141
|
return result
|
|
129
142
|
}
|
|
130
143
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
// ── TTY interactive table ──────────────────────────────────────────────────
|
|
145
|
+
// In a real TTY with enough rows and at least one finding, replace the static
|
|
146
|
+
// table with the navigable TUI (same experience as `dvmi vuln search`).
|
|
147
|
+
const ttyRows = process.stdout.rows ?? 0
|
|
148
|
+
const useTUI = process.stdout.isTTY && filteredFindings.length > 0 && ttyRows >= MIN_TTY_ROWS
|
|
149
|
+
|
|
150
|
+
if (useTUI) {
|
|
151
|
+
const count = filteredFindings.length
|
|
152
|
+
const label = count === 1 ? 'finding' : 'findings'
|
|
153
|
+
const heading = `Vulnerability Scan: ${count} ${label}`
|
|
154
|
+
|
|
155
|
+
const termCols = process.stdout.columns || 80
|
|
156
|
+
// Title width: whatever is left after Package + Version + Severity + CVE + separators
|
|
157
|
+
const fixedCols = COL_WIDTHS.pkg + COL_WIDTHS.version + COL_WIDTHS.severity + COL_WIDTHS.cve
|
|
158
|
+
const separators = 5 * 2 // 5 gaps between 5 columns
|
|
159
|
+
const titleWidth = Math.max(15, Math.min(50, termCols - fixedCols - separators))
|
|
160
|
+
|
|
161
|
+
const rows = filteredFindings.map((f) => ({
|
|
162
|
+
id: f.cveId ?? null,
|
|
163
|
+
pkg: f.package,
|
|
164
|
+
version: f.installedVersion ?? '—',
|
|
165
|
+
severity: f.severity,
|
|
166
|
+
cve: f.cveId ?? '—',
|
|
167
|
+
title: truncate(f.title ?? '—', titleWidth),
|
|
168
|
+
advisoryUrl: f.advisoryUrl ?? null,
|
|
169
|
+
}))
|
|
170
|
+
|
|
171
|
+
/** @type {import('../../utils/tui/navigable-table.js').TableColumnDef[]} */
|
|
172
|
+
const columns = [
|
|
173
|
+
{ header: 'Package', key: 'pkg', width: COL_WIDTHS.pkg },
|
|
174
|
+
{ header: 'Version', key: 'version', width: COL_WIDTHS.version },
|
|
175
|
+
{ header: 'Severity', key: 'severity', width: COL_WIDTHS.severity, colorize: (v) => colorSeverity(v) },
|
|
176
|
+
{ header: 'CVE', key: 'cve', width: COL_WIDTHS.cve, colorize: (v) => (v !== '—' ? chalk.cyan(v) : chalk.gray(v)) },
|
|
177
|
+
{ header: 'Title', key: 'title', width: titleWidth },
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
await startInteractiveTable(rows, columns, heading, filteredFindings.length, getCveDetail)
|
|
181
|
+
} else {
|
|
182
|
+
// Non-TTY fallback: static table + summary (unchanged from pre-TUI behaviour)
|
|
183
|
+
if (filteredFindings.length > 0) {
|
|
184
|
+
this.log(chalk.bold(` Findings (${filteredFindings.length} ${filteredFindings.length === 1 ? 'vulnerability' : 'vulnerabilities'})`))
|
|
185
|
+
this.log('')
|
|
186
|
+
this.log(formatFindingsTable(filteredFindings))
|
|
187
|
+
this.log('')
|
|
188
|
+
this.log(chalk.bold(' Summary'))
|
|
189
|
+
this.log(formatScanSummary(summary))
|
|
190
|
+
this.log('')
|
|
191
|
+
this.log(chalk.yellow(` ⚠ ${filteredFindings.length} ${filteredFindings.length === 1 ? 'vulnerability' : 'vulnerabilities'} found. Run \`dvmi vuln detail <CVE-ID>\` for details.`))
|
|
192
|
+
}
|
|
140
193
|
}
|
|
141
194
|
|
|
195
|
+
// Always print audit errors (e.g. tool not installed) after findings/TUI
|
|
142
196
|
if (errors.length > 0) {
|
|
143
197
|
this.log('')
|
|
144
198
|
for (const err of errors) {
|
|
@@ -146,6 +200,7 @@ export default class VulnScan extends Command {
|
|
|
146
200
|
}
|
|
147
201
|
}
|
|
148
202
|
|
|
203
|
+
// Preserve exit code semantics: exit 1 when vulns found (unless --no-fail)
|
|
149
204
|
if (filteredFindings.length > 0 && !noFail) {
|
|
150
205
|
this.exit(1)
|
|
151
206
|
}
|
|
@@ -360,8 +360,16 @@ export async function startInteractiveTable(rows, columns, heading, totalResults
|
|
|
360
360
|
* @returns {Promise<void>}
|
|
361
361
|
*/
|
|
362
362
|
async function openDetail() {
|
|
363
|
-
const
|
|
363
|
+
const row = state.rows[state.selectedIndex]
|
|
364
|
+
const cveId = row?.id
|
|
365
|
+
|
|
364
366
|
if (!cveId) {
|
|
367
|
+
// No CVE ID — check for an advisory URL (e.g. npm/pnpm advisory findings).
|
|
368
|
+
// Open it in the browser and stay in table view rather than showing a modal.
|
|
369
|
+
const advisoryUrl = row?.advisoryUrl
|
|
370
|
+
if (advisoryUrl) {
|
|
371
|
+
await openBrowser(String(advisoryUrl))
|
|
372
|
+
}
|
|
365
373
|
state = { ...state, currentView: 'table' }
|
|
366
374
|
process.stdout.write(buildTableScreen(state))
|
|
367
375
|
return
|