devvami 1.4.2 → 1.5.1
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 +72 -0
- package/oclif.manifest.json +275 -235
- 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 +257 -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 +215 -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 +349 -0
- package/src/services/ai-env-deployer.js +650 -0
- package/src/services/ai-env-scanner.js +983 -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 +117 -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 +1184 -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 +1089 -0
- package/src/utils/typewriter.js +3 -3
- package/src/utils/welcome.js +18 -21
- package/src/validators/repo-name.js +2 -2
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command, Flags} from '@oclif/core'
|
|
2
2
|
import ora from 'ora'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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 {
|
|
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 {
|
|
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.')
|
package/src/commands/open.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command, Args} from '@oclif/core'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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({
|
|
23
|
+
target: Args.string({description: 'Target: repo, pr, actions, aws', required: true}),
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
async run() {
|
|
27
|
-
const {
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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 = {
|
|
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 {
|
|
2
|
-
import {
|
|
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({
|
|
12
|
+
'run-id': Args.integer({description: 'ID del workflow run', required: true}),
|
|
16
13
|
}
|
|
17
14
|
|
|
18
15
|
static flags = {
|
|
19
|
-
job: Flags.string({
|
|
16
|
+
job: Flags.string({description: 'Filtra per job name'}),
|
|
20
17
|
}
|
|
21
18
|
|
|
22
19
|
async run() {
|
|
23
|
-
const {
|
|
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 {
|
|
32
|
+
return {runId: args['run-id'], log: result.stdout}
|
|
36
33
|
}
|
|
37
34
|
|
|
38
35
|
this.log(result.stdout)
|
|
39
|
-
return {
|
|
36
|
+
return {runId: args['run-id'], log: result.stdout}
|
|
40
37
|
}
|
|
41
38
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
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 {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 =
|
|
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({
|
|
21
|
-
'failed-only': Flags.boolean({
|
|
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 {
|
|
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, {
|
|
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({
|
|
51
|
-
if (!ok) {
|
|
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
|
|
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 = {
|
|
63
|
+
const result = {rerun: {id: runId, failedOnly: flags['failed-only'], status: 'queued'}}
|
|
59
64
|
|
|
60
|
-
|
|
61
|
-
|
|
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 {
|
|
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 {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({
|
|
21
|
-
limit: Flags.integer({
|
|
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 {
|
|
25
|
+
const {flags} = await this.parse(PipelineStatus)
|
|
26
26
|
const isJson = flags.json
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
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 {
|
|
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 {
|
|
50
|
+
return {runs: []}
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
this.log(chalk.bold('\nGitHub Actions runs:\n'))
|
|
52
|
-
this.log(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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 {
|
|
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 {
|
|
8
|
-
import {
|
|
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 = {
|
|
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 = {
|
|
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({
|
|
43
|
-
draft: Flags.boolean({
|
|
44
|
-
'dry-run': Flags.boolean({
|
|
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 {
|
|
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
|
-
|
|
57
|
-
|
|
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
|
|
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({
|
|
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 = {
|
|
89
|
+
const preview = {branch, base: 'main', title, labels, draft: flags.draft}
|
|
88
90
|
if (isDryRun) {
|
|
89
|
-
if (isJson) return {
|
|
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 {
|
|
94
|
+
return {pr: preview}
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
if (!isJson) {
|
|
96
|
-
const ok = await confirm({
|
|
97
|
-
if (!ok) {
|
|
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
|
|
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,
|
|
103
|
-
|
|
104
|
-
|
|
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 = {
|
|
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 {
|
|
1
|
+
import {Command, Args, Flags} from '@oclif/core'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import ora from 'ora'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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({
|
|
19
|
+
number: Args.integer({description: 'Numero della PR', required: true}),
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
static flags = {
|
|
23
|
-
repo: Flags.string({
|
|
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 {
|
|
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
|
|
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 {
|
|
1
|
+
import {Command} from '@oclif/core'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import ora from 'ora'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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 {
|
|
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
|
|
28
|
-
|
|
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 {
|
|
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 {
|
|
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
|
-
{
|
|
42
|
-
{
|
|
43
|
-
{
|
|
44
|
-
{
|
|
45
|
-
{
|
|
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 {
|
|
48
|
+
return {reviewing}
|
|
50
49
|
}
|
|
51
50
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
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 {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({
|
|
21
|
-
reviewer: Flags.boolean({
|
|
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 {
|
|
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
|
|
34
|
-
|
|
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(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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 {
|
|
74
|
+
return {authored, reviewing}
|
|
69
75
|
}
|
|
70
76
|
}
|