devvami 1.4.1 → 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.
Files changed (96) hide show
  1. package/README.md +7 -0
  2. package/oclif.manifest.json +41 -1
  3. package/package.json +2 -1
  4. package/src/commands/auth/login.js +20 -16
  5. package/src/commands/changelog.js +12 -12
  6. package/src/commands/costs/get.js +14 -24
  7. package/src/commands/costs/trend.js +13 -24
  8. package/src/commands/create/repo.js +72 -54
  9. package/src/commands/docs/list.js +29 -25
  10. package/src/commands/docs/projects.js +58 -24
  11. package/src/commands/docs/read.js +56 -39
  12. package/src/commands/docs/search.js +37 -25
  13. package/src/commands/doctor.js +37 -35
  14. package/src/commands/dotfiles/add.js +51 -39
  15. package/src/commands/dotfiles/setup.js +62 -33
  16. package/src/commands/dotfiles/status.js +18 -18
  17. package/src/commands/dotfiles/sync.js +62 -46
  18. package/src/commands/init.js +143 -132
  19. package/src/commands/logs/index.js +10 -16
  20. package/src/commands/open.js +12 -12
  21. package/src/commands/pipeline/logs.js +8 -11
  22. package/src/commands/pipeline/rerun.js +21 -16
  23. package/src/commands/pipeline/status.js +28 -24
  24. package/src/commands/pr/create.js +40 -27
  25. package/src/commands/pr/detail.js +9 -7
  26. package/src/commands/pr/review.js +18 -19
  27. package/src/commands/pr/status.js +27 -21
  28. package/src/commands/prompts/browse.js +15 -15
  29. package/src/commands/prompts/download.js +15 -16
  30. package/src/commands/prompts/install-speckit.js +11 -12
  31. package/src/commands/prompts/list.js +12 -12
  32. package/src/commands/prompts/run.js +16 -19
  33. package/src/commands/repo/list.js +57 -41
  34. package/src/commands/search.js +20 -18
  35. package/src/commands/security/setup.js +38 -34
  36. package/src/commands/sync-config-ai/index.js +143 -0
  37. package/src/commands/tasks/assigned.js +43 -33
  38. package/src/commands/tasks/list.js +43 -33
  39. package/src/commands/tasks/today.js +32 -30
  40. package/src/commands/upgrade.js +18 -17
  41. package/src/commands/vuln/detail.js +8 -8
  42. package/src/commands/vuln/scan.js +95 -21
  43. package/src/commands/vuln/search.js +23 -18
  44. package/src/commands/welcome.js +2 -2
  45. package/src/commands/whoami.js +19 -23
  46. package/src/formatters/ai-config.js +127 -0
  47. package/src/formatters/charts.js +6 -23
  48. package/src/formatters/cost.js +1 -7
  49. package/src/formatters/dotfiles.js +48 -19
  50. package/src/formatters/markdown.js +11 -6
  51. package/src/formatters/openapi.js +7 -9
  52. package/src/formatters/prompts.js +69 -78
  53. package/src/formatters/security.js +2 -2
  54. package/src/formatters/status.js +1 -1
  55. package/src/formatters/table.js +1 -3
  56. package/src/formatters/vuln.js +33 -20
  57. package/src/help.js +162 -164
  58. package/src/hooks/init.js +1 -3
  59. package/src/hooks/postrun.js +5 -7
  60. package/src/index.js +1 -1
  61. package/src/services/ai-config-store.js +318 -0
  62. package/src/services/ai-env-deployer.js +444 -0
  63. package/src/services/ai-env-scanner.js +242 -0
  64. package/src/services/audit-detector.js +2 -2
  65. package/src/services/audit-runner.js +40 -31
  66. package/src/services/auth.js +9 -9
  67. package/src/services/awesome-copilot.js +7 -4
  68. package/src/services/aws-costs.js +22 -22
  69. package/src/services/clickup.js +26 -26
  70. package/src/services/cloudwatch-logs.js +5 -9
  71. package/src/services/config.js +13 -13
  72. package/src/services/docs.js +19 -20
  73. package/src/services/dotfiles.js +149 -51
  74. package/src/services/github.js +22 -24
  75. package/src/services/nvd.js +21 -31
  76. package/src/services/platform.js +2 -2
  77. package/src/services/prompts.js +23 -35
  78. package/src/services/security.js +135 -61
  79. package/src/services/shell.js +4 -4
  80. package/src/services/skills-sh.js +3 -9
  81. package/src/services/speckit.js +4 -7
  82. package/src/services/version-check.js +10 -10
  83. package/src/types.js +85 -0
  84. package/src/utils/aws-vault.js +18 -41
  85. package/src/utils/banner.js +5 -7
  86. package/src/utils/errors.js +42 -46
  87. package/src/utils/frontmatter.js +4 -4
  88. package/src/utils/gradient.js +18 -16
  89. package/src/utils/open-browser.js +3 -3
  90. package/src/utils/tui/form.js +1006 -0
  91. package/src/utils/tui/modal.js +15 -14
  92. package/src/utils/tui/navigable-table.js +25 -17
  93. package/src/utils/tui/tab-tui.js +800 -0
  94. package/src/utils/typewriter.js +3 -3
  95. package/src/utils/welcome.js +18 -21
  96. package/src/validators/repo-name.js +2 -2
@@ -1,9 +1,9 @@
1
- import { Command, Flags } from '@oclif/core'
1
+ import {Command, Flags} from '@oclif/core'
2
2
  import ora from 'ora'
3
- import { search, input } from '@inquirer/prompts'
4
- import { listLogGroups, filterLogEvents, sinceToEpochMs } from '../../services/cloudwatch-logs.js'
5
- import { loadConfig } from '../../services/config.js'
6
- import { DvmiError } from '../../utils/errors.js'
3
+ import {search, input} from '@inquirer/prompts'
4
+ import {listLogGroups, filterLogEvents, sinceToEpochMs} from '../../services/cloudwatch-logs.js'
5
+ import {loadConfig} from '../../services/config.js'
6
+ import {DvmiError} from '../../utils/errors.js'
7
7
 
8
8
  const SINCE_OPTIONS = ['1h', '24h', '7d']
9
9
 
@@ -44,7 +44,7 @@ export default class Logs extends Command {
44
44
  }
45
45
 
46
46
  async run() {
47
- const { flags } = await this.parse(Logs)
47
+ const {flags} = await this.parse(Logs)
48
48
  const isJson = flags.json
49
49
 
50
50
  // Validate --limit
@@ -86,9 +86,7 @@ export default class Logs extends Command {
86
86
  message: 'Select a log group',
87
87
  source: async (input) => {
88
88
  const term = (input ?? '').toLowerCase()
89
- return groups
90
- .filter((g) => g.name.toLowerCase().includes(term))
91
- .map((g) => ({ name: g.name, value: g.name }))
89
+ return groups.filter((g) => g.name.toLowerCase().includes(term)).map((g) => ({name: g.name, value: g.name}))
92
90
  },
93
91
  })
94
92
 
@@ -102,7 +100,7 @@ export default class Logs extends Command {
102
100
  }
103
101
  }
104
102
 
105
- const { startTime, endTime } = sinceToEpochMs(/** @type {'1h'|'24h'|'7d'} */ (flags.since))
103
+ const {startTime, endTime} = sinceToEpochMs(/** @type {'1h'|'24h'|'7d'} */ (flags.since))
106
104
 
107
105
  const fetchSpinner = isJson ? null : ora('Fetching log events...').start()
108
106
 
@@ -171,14 +169,10 @@ export default class Logs extends Command {
171
169
  _handleAwsError(err, _region, _logGroupName) {
172
170
  const msg = String(err)
173
171
  if (msg.includes('AccessDenied') || msg.includes('UnauthorizedAccess')) {
174
- this.error(
175
- 'Access denied. Ensure your role has logs:DescribeLogGroups and logs:FilterLogEvents permissions.',
176
- )
172
+ this.error('Access denied. Ensure your role has logs:DescribeLogGroups and logs:FilterLogEvents permissions.')
177
173
  }
178
174
  if (msg.includes('ResourceNotFoundException')) {
179
- this.error(
180
- `Log group not found. Check the name and confirm you are using the correct region (--region).`,
181
- )
175
+ this.error(`Log group not found. Check the name and confirm you are using the correct region (--region).`)
182
176
  }
183
177
  if (msg.includes('InvalidParameterException')) {
184
178
  this.error('Invalid filter pattern or parameter. Check the pattern syntax and time range.')
@@ -1,8 +1,8 @@
1
- import { Command, Args } from '@oclif/core'
1
+ import {Command, Args} from '@oclif/core'
2
2
  import chalk from 'chalk'
3
- import { exec } from '../services/shell.js'
4
- import { openBrowser } from '../utils/open-browser.js'
5
- import { loadConfig } from '../services/config.js'
3
+ import {exec} from '../services/shell.js'
4
+ import {openBrowser} from '../utils/open-browser.js'
5
+ import {loadConfig} from '../services/config.js'
6
6
 
7
7
  const VALID_TARGETS = ['repo', 'pr', 'actions', 'aws']
8
8
 
@@ -20,11 +20,11 @@ export default class Open extends Command {
20
20
  static enableJsonFlag = true
21
21
 
22
22
  static args = {
23
- target: Args.string({ description: 'Target: repo, pr, actions, aws', required: true }),
23
+ target: Args.string({description: 'Target: repo, pr, actions, aws', required: true}),
24
24
  }
25
25
 
26
26
  async run() {
27
- const { args, flags } = await this.parse(Open)
27
+ const {args, flags} = await this.parse(Open)
28
28
  const isJson = flags.json
29
29
 
30
30
  if (!VALID_TARGETS.includes(args.target)) {
@@ -55,15 +55,15 @@ export default class Open extends Command {
55
55
  const branch = branchResult.stdout
56
56
  // Try to find open PR for current branch
57
57
  const prResult = await exec('gh', ['pr', 'view', '--json', 'url', '-H', branch])
58
- if (prResult.exitCode === 0) {
59
- url = JSON.parse(prResult.stdout).url
60
- } else {
61
- this.error(`No PR found for branch "${branch}". Create one with \`dvmi pr create\``)
62
- }
58
+ if (prResult.exitCode === 0) {
59
+ url = JSON.parse(prResult.stdout).url
60
+ } else {
61
+ this.error(`No PR found for branch "${branch}". Create one with \`dvmi pr create\``)
62
+ }
63
63
  }
64
64
  }
65
65
 
66
- const result = { target: args.target, url, opened: !isJson }
66
+ const result = {target: args.target, url, opened: !isJson}
67
67
 
68
68
  if (isJson) return result
69
69
 
@@ -1,26 +1,23 @@
1
- import { Command, Args, Flags } from '@oclif/core'
2
- import { exec } from '../../services/shell.js'
1
+ import {Command, Args, Flags} from '@oclif/core'
2
+ import {exec} from '../../services/shell.js'
3
3
 
4
4
  export default class PipelineLogs extends Command {
5
5
  static description = 'Log di un workflow run specifico'
6
6
 
7
- static examples = [
8
- '<%= config.bin %> pipeline logs 12345',
9
- '<%= config.bin %> pipeline logs 12345 --job test',
10
- ]
7
+ static examples = ['<%= config.bin %> pipeline logs 12345', '<%= config.bin %> pipeline logs 12345 --job test']
11
8
 
12
9
  static enableJsonFlag = true
13
10
 
14
11
  static args = {
15
- 'run-id': Args.integer({ description: 'ID del workflow run', required: true }),
12
+ 'run-id': Args.integer({description: 'ID del workflow run', required: true}),
16
13
  }
17
14
 
18
15
  static flags = {
19
- job: Flags.string({ description: 'Filtra per job name' }),
16
+ job: Flags.string({description: 'Filtra per job name'}),
20
17
  }
21
18
 
22
19
  async run() {
23
- const { args, flags } = await this.parse(PipelineLogs)
20
+ const {args, flags} = await this.parse(PipelineLogs)
24
21
  const isJson = flags.json
25
22
 
26
23
  const ghArgs = ['run', 'view', String(args['run-id']), '--log']
@@ -32,10 +29,10 @@ export default class PipelineLogs extends Command {
32
29
  }
33
30
 
34
31
  if (isJson) {
35
- return { runId: args['run-id'], log: result.stdout }
32
+ return {runId: args['run-id'], log: result.stdout}
36
33
  }
37
34
 
38
35
  this.log(result.stdout)
39
- return { runId: args['run-id'], log: result.stdout }
36
+ return {runId: args['run-id'], log: result.stdout}
40
37
  }
41
38
  }
@@ -1,12 +1,12 @@
1
- import { Command, Flags } from '@oclif/core'
1
+ import {Command, Flags} from '@oclif/core'
2
2
  import chalk from 'chalk'
3
3
  import ora from 'ora'
4
- import { confirm } from '@inquirer/prompts'
5
- import { listWorkflowRuns, rerunWorkflow } from '../../services/github.js'
6
- import { exec } from '../../services/shell.js'
4
+ import {confirm} from '@inquirer/prompts'
5
+ import {listWorkflowRuns, rerunWorkflow} from '../../services/github.js'
6
+ import {exec} from '../../services/shell.js'
7
7
 
8
8
  export default class PipelineRerun extends Command {
9
- static description = 'Rilancia l\'ultimo workflow fallito'
9
+ static description = "Rilancia l'ultimo workflow fallito"
10
10
 
11
11
  static examples = [
12
12
  '<%= config.bin %> pipeline rerun',
@@ -17,12 +17,12 @@ export default class PipelineRerun extends Command {
17
17
  static enableJsonFlag = true
18
18
 
19
19
  static flags = {
20
- 'run-id': Flags.integer({ description: 'ID specifico del run' }),
21
- 'failed-only': Flags.boolean({ description: 'Rilancia solo i job falliti', default: false }),
20
+ 'run-id': Flags.integer({description: 'ID specifico del run'}),
21
+ 'failed-only': Flags.boolean({description: 'Rilancia solo i job falliti', default: false}),
22
22
  }
23
23
 
24
24
  async run() {
25
- const { flags } = await this.parse(PipelineRerun)
25
+ const {flags} = await this.parse(PipelineRerun)
26
26
  const isJson = flags.json
27
27
 
28
28
  const remoteResult = await exec('git', ['remote', 'get-url', 'origin'])
@@ -34,7 +34,7 @@ export default class PipelineRerun extends Command {
34
34
  let runId = flags['run-id']
35
35
 
36
36
  if (!runId) {
37
- const runs = await listWorkflowRuns(owner, repo, { limit: 10 })
37
+ const runs = await listWorkflowRuns(owner, repo, {limit: 10})
38
38
  const failed = runs.find((r) => r.conclusion === 'failure')
39
39
  if (!failed) {
40
40
  this.log(chalk.green('No failed runs found.'))
@@ -47,19 +47,24 @@ export default class PipelineRerun extends Command {
47
47
  }
48
48
 
49
49
  if (!isJson) {
50
- const ok = await confirm({ message: `Rerun workflow #${runId}?` })
51
- if (!ok) { this.log('Aborted.'); return }
50
+ const ok = await confirm({message: `Rerun workflow #${runId}?`})
51
+ if (!ok) {
52
+ this.log('Aborted.')
53
+ return
54
+ }
52
55
  }
53
56
 
54
- const spinner = isJson ? null : ora({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Relaunching workflow...') }).start()
57
+ const spinner = isJson
58
+ ? null
59
+ : ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Relaunching workflow...')}).start()
55
60
  await rerunWorkflow(owner, repo, runId, flags['failed-only'])
56
61
  spinner?.succeed(`Workflow #${runId} rerun started`)
57
62
 
58
- const result = { rerun: { id: runId, failedOnly: flags['failed-only'], status: 'queued' } }
63
+ const result = {rerun: {id: runId, failedOnly: flags['failed-only'], status: 'queued'}}
59
64
 
60
- if (!isJson) {
61
- this.log(chalk.dim('Track with `dvmi pipeline status`'))
62
- }
65
+ if (!isJson) {
66
+ this.log(chalk.dim('Track with `dvmi pipeline status`'))
67
+ }
63
68
 
64
69
  return result
65
70
  }
@@ -1,9 +1,9 @@
1
- import { Command, Flags } from '@oclif/core'
1
+ import {Command, Flags} from '@oclif/core'
2
2
  import chalk from 'chalk'
3
3
  import ora from 'ora'
4
- import { listWorkflowRuns } from '../../services/github.js'
5
- import { exec } from '../../services/shell.js'
6
- import { renderTable, colorStatus } from '../../formatters/table.js'
4
+ import {listWorkflowRuns} from '../../services/github.js'
5
+ import {exec} from '../../services/shell.js'
6
+ import {renderTable, colorStatus} from '../../formatters/table.js'
7
7
 
8
8
  export default class PipelineStatus extends Command {
9
9
  static description = 'Stato GitHub Actions per il repo corrente'
@@ -17,46 +17,50 @@ export default class PipelineStatus extends Command {
17
17
  static enableJsonFlag = true
18
18
 
19
19
  static flags = {
20
- branch: Flags.string({ description: 'Filtra per branch' }),
21
- limit: Flags.integer({ description: 'Numero di run da mostrare', default: 10 }),
20
+ branch: Flags.string({description: 'Filtra per branch'}),
21
+ limit: Flags.integer({description: 'Numero di run da mostrare', default: 10}),
22
22
  }
23
23
 
24
24
  async run() {
25
- const { flags } = await this.parse(PipelineStatus)
25
+ const {flags} = await this.parse(PipelineStatus)
26
26
  const isJson = flags.json
27
27
 
28
- // Detect repo from git remote
29
- const remoteResult = await exec('git', ['remote', 'get-url', 'origin'])
30
- if (remoteResult.exitCode !== 0) {
31
- this.error('Not in a Git repository. Navigate to a repo or use `dvmi repo list`')
32
- }
28
+ // Detect repo from git remote
29
+ const remoteResult = await exec('git', ['remote', 'get-url', 'origin'])
30
+ if (remoteResult.exitCode !== 0) {
31
+ this.error('Not in a Git repository. Navigate to a repo or use `dvmi repo list`')
32
+ }
33
33
  const match = remoteResult.stdout.match(/github\.com[:/]([^/]+)\/([^/.]+)/)
34
34
  if (!match) this.error('Could not detect GitHub repository.')
35
35
  const [, owner, repo] = match
36
36
 
37
- const spinner = isJson ? null : ora({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Fetching pipeline runs...') }).start()
37
+ const spinner = isJson
38
+ ? null
39
+ : ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Fetching pipeline runs...')}).start()
38
40
  const runs = await listWorkflowRuns(owner, repo, {
39
41
  branch: flags.branch,
40
42
  limit: flags.limit,
41
43
  })
42
44
  spinner?.stop()
43
45
 
44
- if (isJson) return { runs }
46
+ if (isJson) return {runs}
45
47
 
46
48
  if (runs.length === 0) {
47
49
  this.log(chalk.dim('No workflow runs found.'))
48
- return { runs: [] }
50
+ return {runs: []}
49
51
  }
50
52
 
51
53
  this.log(chalk.bold('\nGitHub Actions runs:\n'))
52
- this.log(renderTable(runs, [
53
- { header: 'Status', key: 'conclusion', width: 10, format: (v) => colorStatus(v ? String(v) : 'pending') },
54
- { header: 'Workflow', key: 'name', width: 25 },
55
- { header: 'Branch', key: 'branch', width: 20 },
56
- { header: 'Duration', key: 'duration', width: 10, format: (v) => `${v}s` },
57
- { header: 'Actor', key: 'actor', width: 15 },
58
- ]))
59
-
60
- return { runs }
54
+ this.log(
55
+ renderTable(runs, [
56
+ {header: 'Status', key: 'conclusion', width: 10, format: (v) => colorStatus(v ? String(v) : 'pending')},
57
+ {header: 'Workflow', key: 'name', width: 25},
58
+ {header: 'Branch', key: 'branch', width: 20},
59
+ {header: 'Duration', key: 'duration', width: 10, format: (v) => `${v}s`},
60
+ {header: 'Actor', key: 'actor', width: 15},
61
+ ]),
62
+ )
63
+
64
+ return {runs}
61
65
  }
62
66
  }
@@ -1,11 +1,11 @@
1
- import { Command, Flags } from '@oclif/core'
1
+ import {Command, Flags} from '@oclif/core'
2
2
  import chalk from 'chalk'
3
3
  import ora from 'ora'
4
- import { confirm, input } from '@inquirer/prompts'
5
- import { createPR } from '../../services/github.js'
6
- import { exec } from '../../services/shell.js'
7
- import { readFile } from 'node:fs/promises'
8
- import { existsSync } from 'node:fs'
4
+ import {confirm, input} from '@inquirer/prompts'
5
+ import {createPR} from '../../services/github.js'
6
+ import {exec} from '../../services/shell.js'
7
+ import {readFile} from 'node:fs/promises'
8
+ import {existsSync} from 'node:fs'
9
9
 
10
10
  /**
11
11
  * @param {string} branchName
@@ -14,7 +14,7 @@ import { existsSync } from 'node:fs'
14
14
  function titleFromBranch(branchName) {
15
15
  const [type, ...rest] = branchName.split('/')
16
16
  const desc = rest.join('/').replace(/-/g, ' ')
17
- const typeMap = { feature: 'Feature', fix: 'Fix', chore: 'Chore', hotfix: 'Hotfix' }
17
+ const typeMap = {feature: 'Feature', fix: 'Fix', chore: 'Chore', hotfix: 'Hotfix'}
18
18
  return `${typeMap[type] ?? type}: ${desc}`
19
19
  }
20
20
 
@@ -23,7 +23,7 @@ function titleFromBranch(branchName) {
23
23
  * @returns {string[]}
24
24
  */
25
25
  function labelFromType(branchType) {
26
- const map = { feature: ['feature'], fix: ['bug'], chore: ['chore'], hotfix: ['critical'] }
26
+ const map = {feature: ['feature'], fix: ['bug'], chore: ['chore'], hotfix: ['critical']}
27
27
  return map[branchType] ?? []
28
28
  }
29
29
 
@@ -39,13 +39,13 @@ export default class PRCreate extends Command {
39
39
  static enableJsonFlag = true
40
40
 
41
41
  static flags = {
42
- title: Flags.string({ description: 'Titolo PR (default: auto-generated)' }),
43
- draft: Flags.boolean({ description: 'Crea come draft', default: false }),
44
- 'dry-run': Flags.boolean({ description: 'Preview senza eseguire', default: false }),
42
+ title: Flags.string({description: 'Titolo PR (default: auto-generated)'}),
43
+ draft: Flags.boolean({description: 'Crea come draft', default: false}),
44
+ 'dry-run': Flags.boolean({description: 'Preview senza eseguire', default: false}),
45
45
  }
46
46
 
47
47
  async run() {
48
- const { flags } = await this.parse(PRCreate)
48
+ const {flags} = await this.parse(PRCreate)
49
49
  const isJson = flags.json
50
50
  const isDryRun = flags['dry-run']
51
51
  // Get current branch
@@ -53,9 +53,9 @@ export default class PRCreate extends Command {
53
53
  if (branchResult.exitCode !== 0) this.error('Not in a Git repository.')
54
54
  const branch = branchResult.stdout
55
55
 
56
- if (['main', 'master', 'develop'].includes(branch)) {
57
- this.error(`You're on the default branch "${branch}". Create a feature branch first with \`dvmi branch create\``)
58
- }
56
+ if (['main', 'master', 'develop'].includes(branch)) {
57
+ this.error(`You're on the default branch "${branch}". Create a feature branch first with \`dvmi branch create\``)
58
+ }
59
59
 
60
60
  // Check for commits
61
61
  const repoUrl = await exec('git', ['remote', 'get-url', 'origin'])
@@ -64,7 +64,9 @@ export default class PRCreate extends Command {
64
64
  const [, owner, repo] = repoMatch
65
65
 
66
66
  // Push branch if needed
67
- const pushSpinner = isJson ? null : ora({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Pushing branch...') }).start()
67
+ const pushSpinner = isJson
68
+ ? null
69
+ : ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Pushing branch...')}).start()
68
70
  await exec('git', ['push', '-u', 'origin', branch])
69
71
  pushSpinner?.stop()
70
72
 
@@ -80,32 +82,43 @@ export default class PRCreate extends Command {
80
82
 
81
83
  // Generate title
82
84
  const autoTitle = titleFromBranch(branch)
83
- const title = flags.title ?? (isJson ? autoTitle : await input({ message: 'PR title:', default: autoTitle }))
85
+ const title = flags.title ?? (isJson ? autoTitle : await input({message: 'PR title:', default: autoTitle}))
84
86
  const branchType = branch.split('/')[0]
85
87
  const labels = labelFromType(branchType)
86
88
 
87
- const preview = { branch, base: 'main', title, labels, draft: flags.draft }
89
+ const preview = {branch, base: 'main', title, labels, draft: flags.draft}
88
90
  if (isDryRun) {
89
- if (isJson) return { pr: preview }
91
+ if (isJson) return {pr: preview}
90
92
  this.log(chalk.bold('Dry run — would create PR:'))
91
93
  this.log(JSON.stringify(preview, null, 2))
92
- return { pr: preview }
94
+ return {pr: preview}
93
95
  }
94
96
 
95
97
  if (!isJson) {
96
- const ok = await confirm({ message: `Create PR "${title}"?` })
97
- if (!ok) { this.log('Aborted.'); return }
98
+ const ok = await confirm({message: `Create PR "${title}"?`})
99
+ if (!ok) {
100
+ this.log('Aborted.')
101
+ return
102
+ }
98
103
  }
99
104
 
100
- const spinner = isJson ? null : ora({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Creating PR...') }).start()
105
+ const spinner = isJson
106
+ ? null
107
+ : ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Creating PR...')}).start()
101
108
  const pr = await createPR({
102
- owner, repo, title, body,
103
- head: branch, base: 'main',
104
- draft: flags.draft, labels, reviewers: [],
109
+ owner,
110
+ repo,
111
+ title,
112
+ body,
113
+ head: branch,
114
+ base: 'main',
115
+ draft: flags.draft,
116
+ labels,
117
+ reviewers: [],
105
118
  })
106
119
  spinner?.succeed(`PR created: ${pr.htmlUrl}`)
107
120
 
108
- const result = { pr: { number: pr.number, title, url: pr.htmlUrl, labels, draft: flags.draft } }
121
+ const result = {pr: {number: pr.number, title, url: pr.htmlUrl, labels, draft: flags.draft}}
109
122
 
110
123
  if (isJson) return result
111
124
  this.log(chalk.green('✓') + ' ' + pr.htmlUrl)
@@ -1,8 +1,8 @@
1
- import { Command, Args, Flags } from '@oclif/core'
1
+ import {Command, Args, Flags} from '@oclif/core'
2
2
  import chalk from 'chalk'
3
3
  import ora from 'ora'
4
- import { getPRDetail } from '../../services/github.js'
5
- import { exec } from '../../services/shell.js'
4
+ import {getPRDetail} from '../../services/github.js'
5
+ import {exec} from '../../services/shell.js'
6
6
 
7
7
  export default class PRDetail extends Command {
8
8
  static description = 'Dettaglio PR con commenti QA e checklist degli step'
@@ -16,15 +16,15 @@ export default class PRDetail extends Command {
16
16
  static enableJsonFlag = true
17
17
 
18
18
  static args = {
19
- number: Args.integer({ description: 'Numero della PR', required: true }),
19
+ number: Args.integer({description: 'Numero della PR', required: true}),
20
20
  }
21
21
 
22
22
  static flags = {
23
- repo: Flags.string({ description: 'Repository nel formato owner/repo (default: rilevato da git remote)' }),
23
+ repo: Flags.string({description: 'Repository nel formato owner/repo (default: rilevato da git remote)'}),
24
24
  }
25
25
 
26
26
  async run() {
27
- const { args, flags } = await this.parse(PRDetail)
27
+ const {args, flags} = await this.parse(PRDetail)
28
28
  const isJson = flags.json
29
29
 
30
30
  let owner, repo
@@ -39,7 +39,9 @@ export default class PRDetail extends Command {
39
39
  ;[, owner, repo] = match
40
40
  }
41
41
 
42
- const spinner = isJson ? null : ora({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Caricamento PR...') }).start()
42
+ const spinner = isJson
43
+ ? null
44
+ : ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Caricamento PR...')}).start()
43
45
  const detail = await getPRDetail(owner, repo, args.number)
44
46
  spinner?.stop()
45
47
 
@@ -1,22 +1,19 @@
1
- import { Command } from '@oclif/core'
1
+ import {Command} from '@oclif/core'
2
2
  import chalk from 'chalk'
3
3
  import ora from 'ora'
4
- import { listMyPRs } from '../../services/github.js'
5
- import { loadConfig } from '../../services/config.js'
6
- import { renderTable, colorStatus } from '../../formatters/table.js'
4
+ import {listMyPRs} from '../../services/github.js'
5
+ import {loadConfig} from '../../services/config.js'
6
+ import {renderTable, colorStatus} from '../../formatters/table.js'
7
7
 
8
8
  export default class PRReview extends Command {
9
9
  static description = 'Lista PR assegnate a te per la code review'
10
10
 
11
- static examples = [
12
- '<%= config.bin %> pr review',
13
- '<%= config.bin %> pr review --json',
14
- ]
11
+ static examples = ['<%= config.bin %> pr review', '<%= config.bin %> pr review --json']
15
12
 
16
13
  static enableJsonFlag = true
17
14
 
18
15
  async run() {
19
- const { flags } = await this.parse(PRReview)
16
+ const {flags} = await this.parse(PRReview)
20
17
  const isJson = flags.json
21
18
  const config = await loadConfig()
22
19
 
@@ -24,28 +21,30 @@ export default class PRReview extends Command {
24
21
  this.error("GitHub org non configurata. Esegui `dvmi init` per configurare l'ambiente.")
25
22
  }
26
23
 
27
- const spinner = isJson ? null : ora({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Caricamento PR in review...') }).start()
28
- const { reviewing } = await listMyPRs(config.org)
24
+ const spinner = isJson
25
+ ? null
26
+ : ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Caricamento PR in review...')}).start()
27
+ const {reviewing} = await listMyPRs(config.org)
29
28
  spinner?.stop()
30
29
 
31
- if (isJson) return { reviewing }
30
+ if (isJson) return {reviewing}
32
31
 
33
32
  if (reviewing.length === 0) {
34
33
  this.log(chalk.dim('Nessuna PR assegnata per review.'))
35
- return { reviewing }
34
+ return {reviewing}
36
35
  }
37
36
 
38
37
  this.log(chalk.bold(`\nPR ASSEGNATE PER REVIEW (${reviewing.length}):`))
39
38
  this.log(
40
39
  renderTable(reviewing, [
41
- { header: '#', key: 'number', width: 6 },
42
- { header: 'Titolo', key: 'title', width: 45 },
43
- { header: 'Autore', key: 'author', width: 20 },
44
- { header: 'Branch', key: 'headBranch', width: 30 },
45
- { header: 'CI', key: 'ciStatus', width: 10, format: (v) => colorStatus(String(v)) },
40
+ {header: '#', key: 'number', width: 6},
41
+ {header: 'Titolo', key: 'title', width: 45},
42
+ {header: 'Autore', key: 'author', width: 20},
43
+ {header: 'Branch', key: 'headBranch', width: 30},
44
+ {header: 'CI', key: 'ciStatus', width: 10, format: (v) => colorStatus(String(v))},
46
45
  ]),
47
46
  )
48
47
 
49
- return { reviewing }
48
+ return {reviewing}
50
49
  }
51
50
  }
@@ -1,9 +1,9 @@
1
- import { Command, Flags } from '@oclif/core'
1
+ import {Command, Flags} from '@oclif/core'
2
2
  import chalk from 'chalk'
3
3
  import ora from 'ora'
4
- import { listMyPRs } from '../../services/github.js'
5
- import { loadConfig } from '../../services/config.js'
6
- import { renderTable, colorStatus } from '../../formatters/table.js'
4
+ import {listMyPRs} from '../../services/github.js'
5
+ import {loadConfig} from '../../services/config.js'
6
+ import {renderTable, colorStatus} from '../../formatters/table.js'
7
7
 
8
8
  export default class PRStatus extends Command {
9
9
  static description = 'Stato delle tue PR aperte (come autore e come reviewer)'
@@ -17,12 +17,12 @@ export default class PRStatus extends Command {
17
17
  static enableJsonFlag = true
18
18
 
19
19
  static flags = {
20
- author: Flags.boolean({ description: 'Solo PR dove sei autore', default: false }),
21
- reviewer: Flags.boolean({ description: 'Solo PR dove sei reviewer', default: false }),
20
+ author: Flags.boolean({description: 'Solo PR dove sei autore', default: false}),
21
+ reviewer: Flags.boolean({description: 'Solo PR dove sei reviewer', default: false}),
22
22
  }
23
23
 
24
24
  async run() {
25
- const { flags } = await this.parse(PRStatus)
25
+ const {flags} = await this.parse(PRStatus)
26
26
  const isJson = flags.json
27
27
  const config = await loadConfig()
28
28
 
@@ -30,8 +30,10 @@ export default class PRStatus extends Command {
30
30
  this.error('GitHub org not configured. Run `dvmi init` to set up your environment.')
31
31
  }
32
32
 
33
- const spinner = isJson ? null : ora({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Fetching PRs...') }).start()
34
- const { authored, reviewing } = await listMyPRs(config.org)
33
+ const spinner = isJson
34
+ ? null
35
+ : ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Fetching PRs...')}).start()
36
+ const {authored, reviewing} = await listMyPRs(config.org)
35
37
  spinner?.stop()
36
38
 
37
39
  const showAuthored = !flags.reviewer || flags.author
@@ -46,25 +48,29 @@ export default class PRStatus extends Command {
46
48
 
47
49
  if (showAuthored && authored.length > 0) {
48
50
  this.log(chalk.bold('\nYOUR PRS:'))
49
- this.log(renderTable(authored, [
50
- { header: 'Repo', key: 'headBranch', width: 30, format: (v) => String(v).split('/')[0] },
51
- { header: 'Title', key: 'title', width: 40 },
52
- { header: 'CI', key: 'ciStatus', width: 10, format: (v) => colorStatus(String(v)) },
53
- { header: 'Review', key: 'reviewStatus', width: 20, format: (v) => colorStatus(String(v)) },
54
- ]))
51
+ this.log(
52
+ renderTable(authored, [
53
+ {header: 'Repo', key: 'headBranch', width: 30, format: (v) => String(v).split('/')[0]},
54
+ {header: 'Title', key: 'title', width: 40},
55
+ {header: 'CI', key: 'ciStatus', width: 10, format: (v) => colorStatus(String(v))},
56
+ {header: 'Review', key: 'reviewStatus', width: 20, format: (v) => colorStatus(String(v))},
57
+ ]),
58
+ )
55
59
  } else if (showAuthored) {
56
60
  this.log(chalk.dim('No authored PRs found.'))
57
61
  }
58
62
 
59
63
  if (showReviewing && reviewing.length > 0) {
60
64
  this.log(chalk.bold('\nREVIEW REQUESTED:'))
61
- this.log(renderTable(reviewing, [
62
- { header: 'Title', key: 'title', width: 40 },
63
- { header: 'Author', key: 'author', width: 20 },
64
- { header: 'CI', key: 'ciStatus', width: 10, format: (v) => colorStatus(String(v)) },
65
- ]))
65
+ this.log(
66
+ renderTable(reviewing, [
67
+ {header: 'Title', key: 'title', width: 40},
68
+ {header: 'Author', key: 'author', width: 20},
69
+ {header: 'CI', key: 'ciStatus', width: 10, format: (v) => colorStatus(String(v))},
70
+ ]),
71
+ )
66
72
  }
67
73
 
68
- return { authored, reviewing }
74
+ return {authored, reviewing}
69
75
  }
70
76
  }