codeant-cli 0.4.10 → 0.4.12
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/package.json +1 -1
- package/src/commands/settings/analysis-feature-flags.js +32 -0
- package/src/commands/settings/audit-logs.js +9 -0
- package/src/commands/settings/branches.js +33 -0
- package/src/commands/settings/cve-reporting.js +40 -0
- package/src/commands/settings/feature-flags.js +34 -0
- package/src/commands/settings/index.js +413 -0
- package/src/commands/settings/pr-instructions.js +44 -0
- package/src/commands/settings/recurring-scans.js +40 -0
- package/src/commands/settings/repos.js +2 -0
- package/src/commands/settings/sprint-reports.js +40 -0
- package/src/commands/settings/team-management.js +55 -0
- package/src/index.js +4 -0
- package/src/scm/gitlab/client.js +2 -2
- package/src/scm/index.js +1 -1
- package/src/utils/fetchApi.js +1 -0
- package/src/utils/reviewApiHelper.js +38 -31
package/package.json
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { fetchApi } from '../../utils/fetchApi.js';
|
|
2
|
+
|
|
3
|
+
export async function runAnalysisFeatureFlagsGet({ repo } = {}) {
|
|
4
|
+
if (!repo) {
|
|
5
|
+
const err = new Error('--repo is required');
|
|
6
|
+
err.exitCode = 1;
|
|
7
|
+
throw err;
|
|
8
|
+
}
|
|
9
|
+
return fetchApi('/extension/analysis/config/feature-flags/get', 'POST', { repo });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function runAnalysisFeatureFlagsUpdate({ repo, flags } = {}) {
|
|
13
|
+
if (!repo) {
|
|
14
|
+
const err = new Error('--repo is required');
|
|
15
|
+
err.exitCode = 1;
|
|
16
|
+
throw err;
|
|
17
|
+
}
|
|
18
|
+
if (!flags) {
|
|
19
|
+
const err = new Error('--flags is required');
|
|
20
|
+
err.exitCode = 1;
|
|
21
|
+
throw err;
|
|
22
|
+
}
|
|
23
|
+
let feature_flags;
|
|
24
|
+
try {
|
|
25
|
+
feature_flags = JSON.parse(flags);
|
|
26
|
+
} catch {
|
|
27
|
+
const err = new Error('--flags must be valid JSON (e.g. \'{"sast_analysis":"enabled"}\')');
|
|
28
|
+
err.exitCode = 1;
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
return fetchApi('/extension/analysis/config/feature-flags/update', 'POST', { repo, feature_flags });
|
|
32
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { fetchApi } from '../../utils/fetchApi.js';
|
|
2
|
+
|
|
3
|
+
export async function runAuditLogsSettings({ days, page, limit } = {}) {
|
|
4
|
+
return fetchApi('/extension/account/audit/settings', 'POST', {
|
|
5
|
+
days: days !== undefined ? Number(days) : 30,
|
|
6
|
+
page: page !== undefined ? Number(page) : 1,
|
|
7
|
+
limit: limit !== undefined ? Number(limit) : 50,
|
|
8
|
+
});
|
|
9
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { fetchApi } from '../../utils/fetchApi.js';
|
|
2
|
+
|
|
3
|
+
export async function runBranchesAll({ repo } = {}) {
|
|
4
|
+
if (!repo) {
|
|
5
|
+
const err = new Error('--repo is required');
|
|
6
|
+
err.exitCode = 1;
|
|
7
|
+
throw err;
|
|
8
|
+
}
|
|
9
|
+
return fetchApi('/extension/branches/all', 'POST', { repo });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function runBranchesDefault({ repo } = {}) {
|
|
13
|
+
if (!repo) {
|
|
14
|
+
const err = new Error('--repo is required');
|
|
15
|
+
err.exitCode = 1;
|
|
16
|
+
throw err;
|
|
17
|
+
}
|
|
18
|
+
return fetchApi('/extension/branches/default', 'POST', { repo });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function runBranchesUpdateDefault({ repo, branch } = {}) {
|
|
22
|
+
if (!repo) {
|
|
23
|
+
const err = new Error('--repo is required');
|
|
24
|
+
err.exitCode = 1;
|
|
25
|
+
throw err;
|
|
26
|
+
}
|
|
27
|
+
if (!branch) {
|
|
28
|
+
const err = new Error('--branch is required');
|
|
29
|
+
err.exitCode = 1;
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
return fetchApi('/extension/branches/update-default', 'POST', { repo, branch });
|
|
33
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { fetchApi } from '../../utils/fetchApi.js';
|
|
2
|
+
|
|
3
|
+
export async function runCveReportingList({ repo, status, reportId } = {}) {
|
|
4
|
+
return fetchApi('/extension/analysis/cve-reporting/list', 'POST', { repo, status, report_id: reportId });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function runCveReportingCreate({ name, repo, scheduleConfig, reportConfig, description, notificationConfig, createdBy } = {}) {
|
|
8
|
+
if (!name) {
|
|
9
|
+
const err = new Error('--name is required');
|
|
10
|
+
err.exitCode = 1;
|
|
11
|
+
throw err;
|
|
12
|
+
}
|
|
13
|
+
return fetchApi('/extension/analysis/cve-reporting/create', 'POST', {
|
|
14
|
+
name,
|
|
15
|
+
repo,
|
|
16
|
+
schedule_config: scheduleConfig ? JSON.parse(scheduleConfig) : undefined,
|
|
17
|
+
report_config: reportConfig ? JSON.parse(reportConfig) : undefined,
|
|
18
|
+
description,
|
|
19
|
+
notification_config: notificationConfig ? JSON.parse(notificationConfig) : undefined,
|
|
20
|
+
created_by: createdBy,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function runCveReportingUpdate({ reportId, repo, status, name, description, scheduleConfig, reportConfig, notificationConfig } = {}) {
|
|
25
|
+
if (!reportId) {
|
|
26
|
+
const err = new Error('--report-id is required');
|
|
27
|
+
err.exitCode = 1;
|
|
28
|
+
throw err;
|
|
29
|
+
}
|
|
30
|
+
return fetchApi('/extension/analysis/cve-reporting/update', 'POST', {
|
|
31
|
+
report_id: reportId,
|
|
32
|
+
repo,
|
|
33
|
+
status,
|
|
34
|
+
name,
|
|
35
|
+
description,
|
|
36
|
+
schedule_config: scheduleConfig ? JSON.parse(scheduleConfig) : undefined,
|
|
37
|
+
report_config: reportConfig ? JSON.parse(reportConfig) : undefined,
|
|
38
|
+
notification_config: notificationConfig ? JSON.parse(notificationConfig) : undefined,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { fetchApi } from '../../utils/fetchApi.js';
|
|
2
|
+
|
|
3
|
+
export async function runFeatureFlagsGet({ repo, v2 = false } = {}) {
|
|
4
|
+
if (!repo) {
|
|
5
|
+
const err = new Error('--repo is required');
|
|
6
|
+
err.exitCode = 1;
|
|
7
|
+
throw err;
|
|
8
|
+
}
|
|
9
|
+
const version = v2 ? 'v2' : 'v1';
|
|
10
|
+
return fetchApi(`/extension/config/feature-flags/${version}/get`, 'POST', { repo });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function runFeatureFlagsUpdate({ repo, flags, v2 = false } = {}) {
|
|
14
|
+
if (!repo) {
|
|
15
|
+
const err = new Error('--repo is required');
|
|
16
|
+
err.exitCode = 1;
|
|
17
|
+
throw err;
|
|
18
|
+
}
|
|
19
|
+
if (!flags) {
|
|
20
|
+
const err = new Error('--flags is required');
|
|
21
|
+
err.exitCode = 1;
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
24
|
+
let feature_flags;
|
|
25
|
+
try {
|
|
26
|
+
feature_flags = JSON.parse(flags);
|
|
27
|
+
} catch {
|
|
28
|
+
const err = new Error('--flags must be valid JSON (e.g. \'{"pr_review":"enable"}\')');
|
|
29
|
+
err.exitCode = 1;
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
const version = v2 ? 'v2' : 'v1';
|
|
33
|
+
return fetchApi(`/extension/config/feature-flags/${version}/update`, 'POST', { repo, feature_flags });
|
|
34
|
+
}
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { runAuditLogsSettings } from './audit-logs.js';
|
|
2
|
+
import { runRepos } from './repos.js';
|
|
3
|
+
import { runBranchesAll, runBranchesDefault, runBranchesUpdateDefault } from './branches.js';
|
|
4
|
+
import { runFeatureFlagsGet, runFeatureFlagsUpdate } from './feature-flags.js';
|
|
5
|
+
import { runAnalysisFeatureFlagsGet, runAnalysisFeatureFlagsUpdate } from './analysis-feature-flags.js';
|
|
6
|
+
import { runPrInstructionsGet, runPrInstructionsSave, runPrInstructionsEdit, runPrInstructionsDelete } from './pr-instructions.js';
|
|
7
|
+
import { runRecurringScanslist, runRecurringScansCreate, runRecurringScansUpdate } from './recurring-scans.js';
|
|
8
|
+
import { runSprintReportsList, runSprintReportsCreate, runSprintReportsUpdate } from './sprint-reports.js';
|
|
9
|
+
import { runCveReportingList, runCveReportingCreate, runCveReportingUpdate } from './cve-reporting.js';
|
|
10
|
+
import {
|
|
11
|
+
runTeamSubscriptionsList,
|
|
12
|
+
runTeamUsersList,
|
|
13
|
+
runTeamUserGet,
|
|
14
|
+
runTeamUserCreate,
|
|
15
|
+
runTeamUserDelete,
|
|
16
|
+
runTeamUserUpdate,
|
|
17
|
+
runTeamRbacGet,
|
|
18
|
+
runTeamRbacSave,
|
|
19
|
+
runTeamPlanEnd,
|
|
20
|
+
} from './team-management.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Register all `codeant settings <verb>` subcommands.
|
|
24
|
+
*
|
|
25
|
+
* Each subcommand implementation lives in its own file alongside this index
|
|
26
|
+
* and exports a single async function named run<Command>(opts).
|
|
27
|
+
*
|
|
28
|
+
* To add a new subcommand:
|
|
29
|
+
* 1. Create src/commands/settings/<name>.js exporting run<Name>(opts).
|
|
30
|
+
* 2. Import it here.
|
|
31
|
+
* 3. Add a settings.command(...).action(...) block below following the
|
|
32
|
+
* existing pattern.
|
|
33
|
+
*
|
|
34
|
+
* @param {import('commander').Command} program
|
|
35
|
+
* @param {{ runCmd: Function }} helpers
|
|
36
|
+
*/
|
|
37
|
+
export default function registerSettingsCommands(program, { runCmd }) {
|
|
38
|
+
const settings = program
|
|
39
|
+
.command('settings')
|
|
40
|
+
.description('Manage CodeAnt AI settings');
|
|
41
|
+
|
|
42
|
+
// ── repos ──────────────────────────────────────────────────────────────────
|
|
43
|
+
settings
|
|
44
|
+
.command('repos')
|
|
45
|
+
.description('List repositories')
|
|
46
|
+
.option('--org <org>', 'Organization name (auto-picked when only one is authenticated)')
|
|
47
|
+
.action((opts) => runCmd(() => runRepos({ org: opts.org })));
|
|
48
|
+
|
|
49
|
+
// ── audit-logs settings ────────────────────────────────────────────────────
|
|
50
|
+
settings
|
|
51
|
+
.command('audit-logs')
|
|
52
|
+
.description('List settings change audit logs for the org')
|
|
53
|
+
.option('--days <n>', 'Number of days to look back', '30')
|
|
54
|
+
.option('--page <n>', 'Page number', '1')
|
|
55
|
+
.option('--limit <n>', 'Results per page (max 200)', '50')
|
|
56
|
+
.action((opts) => runCmd(() => runAuditLogsSettings({ days: opts.days, page: opts.page, limit: opts.limit })));
|
|
57
|
+
|
|
58
|
+
// ── branches all ───────────────────────────────────────────────────────────
|
|
59
|
+
settings
|
|
60
|
+
.command('branches-all')
|
|
61
|
+
.description('List all branches for a repository')
|
|
62
|
+
.requiredOption('--repo <repo>', 'Repository (owner/repo)')
|
|
63
|
+
.action((opts) => runCmd(() => runBranchesAll({ repo: opts.repo })));
|
|
64
|
+
|
|
65
|
+
// ── branches default ───────────────────────────────────────────────────────
|
|
66
|
+
settings
|
|
67
|
+
.command('branches-default')
|
|
68
|
+
.description('Get the default branch for a repository')
|
|
69
|
+
.requiredOption('--repo <repo>', 'Repository (owner/repo)')
|
|
70
|
+
.action((opts) => runCmd(() => runBranchesDefault({ repo: opts.repo })));
|
|
71
|
+
|
|
72
|
+
// ── branches update-default ────────────────────────────────────────────────
|
|
73
|
+
settings
|
|
74
|
+
.command('branches-update-default')
|
|
75
|
+
.description('Set the default branch for a repository')
|
|
76
|
+
.requiredOption('--repo <repo>', 'Repository (owner/repo)')
|
|
77
|
+
.requiredOption('--branch <name>', 'Branch name to set as default')
|
|
78
|
+
.action((opts) => runCmd(() => runBranchesUpdateDefault({ repo: opts.repo, branch: opts.branch })));
|
|
79
|
+
|
|
80
|
+
// ── feature-flags get ──────────────────────────────────────────────────────
|
|
81
|
+
settings
|
|
82
|
+
.command('feature-flags-get')
|
|
83
|
+
.description('Get feature flags for a repository')
|
|
84
|
+
.requiredOption('--repo <repo>', 'Repository (owner/repo)')
|
|
85
|
+
.option('--v2', 'Use v2 feature flags', false)
|
|
86
|
+
.action((opts) => runCmd(() => runFeatureFlagsGet({ repo: opts.repo, v2: opts.v2 })));
|
|
87
|
+
|
|
88
|
+
// ── feature-flags update ───────────────────────────────────────────────────
|
|
89
|
+
settings
|
|
90
|
+
.command('feature-flags-update')
|
|
91
|
+
.description('Update feature flags for a repository')
|
|
92
|
+
.requiredOption('--repo <repo>', 'Repository (owner/repo)')
|
|
93
|
+
.requiredOption('--flags <json>', 'Feature flags as a JSON string (e.g. \'{"pr_review":"enable"}\')')
|
|
94
|
+
.option('--v2', 'Use v2 feature flags', false)
|
|
95
|
+
.action((opts) => runCmd(() => runFeatureFlagsUpdate({ repo: opts.repo, flags: opts.flags, v2: opts.v2 })));
|
|
96
|
+
|
|
97
|
+
// ── analysis feature-flags get ─────────────────────────────────────────────
|
|
98
|
+
settings
|
|
99
|
+
.command('analysis-feature-flags-get')
|
|
100
|
+
.description('Get analysis feature flags for a repository')
|
|
101
|
+
.requiredOption('--repo <repo>', 'Repository (owner/repo)')
|
|
102
|
+
.action((opts) => runCmd(() => runAnalysisFeatureFlagsGet({ repo: opts.repo })));
|
|
103
|
+
|
|
104
|
+
// ── analysis feature-flags update ─────────────────────────────────────────
|
|
105
|
+
settings
|
|
106
|
+
.command('analysis-feature-flags-update')
|
|
107
|
+
.description('Update analysis feature flags for a repository')
|
|
108
|
+
.requiredOption('--repo <repo>', 'Repository (owner/repo)')
|
|
109
|
+
.requiredOption('--flags <json>', 'Feature flags as a JSON string (e.g. \'{"sast_analysis":"enabled"}\')')
|
|
110
|
+
.action((opts) => runCmd(() => runAnalysisFeatureFlagsUpdate({ repo: opts.repo, flags: opts.flags })));
|
|
111
|
+
|
|
112
|
+
// ── pr-instructions get ────────────────────────────────────────────────────
|
|
113
|
+
settings
|
|
114
|
+
.command('pr-instructions-get')
|
|
115
|
+
.description('Get PR review instructions')
|
|
116
|
+
.requiredOption('--type <type>', 'Instruction type (e.g. custom)')
|
|
117
|
+
.action((opts) => runCmd(() => runPrInstructionsGet({ instructionsType: opts.type })));
|
|
118
|
+
|
|
119
|
+
// ── pr-instructions save ───────────────────────────────────────────────────
|
|
120
|
+
settings
|
|
121
|
+
.command('pr-instructions-save')
|
|
122
|
+
.description('Save a new PR review instruction')
|
|
123
|
+
.option('--type <type>', 'Instruction type (e.g. custom)')
|
|
124
|
+
.option('--file-pattern <pattern>', 'File pattern (e.g. *.py)')
|
|
125
|
+
.option('--description <text>', 'Instruction description')
|
|
126
|
+
.option('--description-file <path>', 'Path to a file containing the description')
|
|
127
|
+
.option('--instruction-id <id>', 'Instruction ID (for upsert)')
|
|
128
|
+
.action((opts) => runCmd(() => runPrInstructionsSave({
|
|
129
|
+
instructionType: opts.type,
|
|
130
|
+
filePattern: opts.filePattern,
|
|
131
|
+
description: opts.description,
|
|
132
|
+
descriptionFile: opts.descriptionFile,
|
|
133
|
+
instructionId: opts.instructionId,
|
|
134
|
+
})));
|
|
135
|
+
|
|
136
|
+
// ── pr-instructions edit ───────────────────────────────────────────────────
|
|
137
|
+
settings
|
|
138
|
+
.command('pr-instructions-edit')
|
|
139
|
+
.description('Edit an existing PR review instruction')
|
|
140
|
+
.requiredOption('--instruction-id <id>', 'Instruction ID to edit')
|
|
141
|
+
.option('--type <type>', 'Instruction type (e.g. custom)')
|
|
142
|
+
.option('--description <text>', 'Updated description')
|
|
143
|
+
.option('--description-file <path>', 'Path to a file containing the description')
|
|
144
|
+
.option('--file-pattern <pattern>', 'Updated file pattern')
|
|
145
|
+
.action((opts) => runCmd(() => runPrInstructionsEdit({
|
|
146
|
+
instructionType: opts.type,
|
|
147
|
+
instructionId: opts.instructionId,
|
|
148
|
+
description: opts.description,
|
|
149
|
+
descriptionFile: opts.descriptionFile,
|
|
150
|
+
filePattern: opts.filePattern,
|
|
151
|
+
})));
|
|
152
|
+
|
|
153
|
+
// ── pr-instructions delete ─────────────────────────────────────────────────
|
|
154
|
+
settings
|
|
155
|
+
.command('pr-instructions-delete')
|
|
156
|
+
.description('Delete a PR review instruction')
|
|
157
|
+
.requiredOption('--instruction-id <id>', 'Instruction ID to delete')
|
|
158
|
+
.option('--type <type>', 'Instruction type (e.g. custom)')
|
|
159
|
+
.action((opts) => runCmd(() => runPrInstructionsDelete({
|
|
160
|
+
instructionType: opts.type,
|
|
161
|
+
instructionId: opts.instructionId,
|
|
162
|
+
})));
|
|
163
|
+
|
|
164
|
+
// ── recurring-scans list ───────────────────────────────────────────────────
|
|
165
|
+
settings
|
|
166
|
+
.command('recurring-scans-list')
|
|
167
|
+
.description('List recurring scan schedules')
|
|
168
|
+
.option('--repo <repo>', 'Filter by repository (owner/repo)')
|
|
169
|
+
.option('--status <status>', 'Filter by status (e.g. ACTIVE)')
|
|
170
|
+
.option('--schedule-id <id>', 'Filter by schedule ID')
|
|
171
|
+
.action((opts) => runCmd(() => runRecurringScanslist({ repo: opts.repo, status: opts.status, scheduleId: opts.scheduleId })));
|
|
172
|
+
|
|
173
|
+
// ── recurring-scans create ─────────────────────────────────────────────────
|
|
174
|
+
settings
|
|
175
|
+
.command('recurring-scans-create')
|
|
176
|
+
.description('Create a recurring scan schedule')
|
|
177
|
+
.requiredOption('--name <name>', 'Schedule name')
|
|
178
|
+
.option('--repo <repo>', 'Repository (owner/repo)')
|
|
179
|
+
.option('--schedule-config <json>', 'Schedule config as JSON (e.g. \'{"frequency":"weekly"}\')')
|
|
180
|
+
.option('--scan-config <json>', 'Scan config as JSON')
|
|
181
|
+
.option('--description <text>', 'Description')
|
|
182
|
+
.option('--notification-config <json>', 'Notification config as JSON')
|
|
183
|
+
.option('--created-by <user>', 'Creator identifier')
|
|
184
|
+
.action((opts) => runCmd(() => runRecurringScansCreate({
|
|
185
|
+
name: opts.name,
|
|
186
|
+
repo: opts.repo,
|
|
187
|
+
scheduleConfig: opts.scheduleConfig,
|
|
188
|
+
scanConfig: opts.scanConfig,
|
|
189
|
+
description: opts.description,
|
|
190
|
+
notificationConfig: opts.notificationConfig,
|
|
191
|
+
createdBy: opts.createdBy,
|
|
192
|
+
})));
|
|
193
|
+
|
|
194
|
+
// ── recurring-scans update ─────────────────────────────────────────────────
|
|
195
|
+
settings
|
|
196
|
+
.command('recurring-scans-update')
|
|
197
|
+
.description('Update a recurring scan schedule')
|
|
198
|
+
.requiredOption('--schedule-id <id>', 'Schedule ID to update')
|
|
199
|
+
.requiredOption('--repo <repo>', 'Repository (owner/repo)')
|
|
200
|
+
.option('--status <status>', 'New status (e.g. ACTIVE, INACTIVE)')
|
|
201
|
+
.option('--name <name>', 'New name')
|
|
202
|
+
.option('--description <text>', 'New description')
|
|
203
|
+
.option('--schedule-config <json>', 'Schedule config as JSON')
|
|
204
|
+
.option('--scan-config <json>', 'Scan config as JSON')
|
|
205
|
+
.option('--notification-config <json>', 'Notification config as JSON')
|
|
206
|
+
.action((opts) => runCmd(() => runRecurringScansUpdate({
|
|
207
|
+
scheduleId: opts.scheduleId,
|
|
208
|
+
repo: opts.repo,
|
|
209
|
+
status: opts.status,
|
|
210
|
+
name: opts.name,
|
|
211
|
+
description: opts.description,
|
|
212
|
+
scheduleConfig: opts.scheduleConfig,
|
|
213
|
+
scanConfig: opts.scanConfig,
|
|
214
|
+
notificationConfig: opts.notificationConfig,
|
|
215
|
+
})));
|
|
216
|
+
|
|
217
|
+
// ── sprint-reports list ────────────────────────────────────────────────────
|
|
218
|
+
settings
|
|
219
|
+
.command('sprint-reports-list')
|
|
220
|
+
.description('List sprint report configurations')
|
|
221
|
+
.option('--repo <repo>', 'Filter by repository (owner/repo)')
|
|
222
|
+
.option('--status <status>', 'Filter by status (e.g. ACTIVE)')
|
|
223
|
+
.option('--config-id <id>', 'Filter by config ID')
|
|
224
|
+
.action((opts) => runCmd(() => runSprintReportsList({ repo: opts.repo, status: opts.status, configId: opts.configId })));
|
|
225
|
+
|
|
226
|
+
// ── sprint-reports create ──────────────────────────────────────────────────
|
|
227
|
+
settings
|
|
228
|
+
.command('sprint-reports-create')
|
|
229
|
+
.description('Create a sprint report configuration')
|
|
230
|
+
.requiredOption('--name <name>', 'Report name')
|
|
231
|
+
.option('--repo <repo>', 'Repository (owner/repo)')
|
|
232
|
+
.option('--schedule-config <json>', 'Schedule config as JSON (e.g. \'{"frequency":"weekly"}\')')
|
|
233
|
+
.option('--report-config <json>', 'Report config as JSON')
|
|
234
|
+
.option('--description <text>', 'Description')
|
|
235
|
+
.option('--notification-config <json>', 'Notification config as JSON')
|
|
236
|
+
.option('--created-by <user>', 'Creator identifier')
|
|
237
|
+
.action((opts) => runCmd(() => runSprintReportsCreate({
|
|
238
|
+
name: opts.name,
|
|
239
|
+
repo: opts.repo,
|
|
240
|
+
scheduleConfig: opts.scheduleConfig,
|
|
241
|
+
reportConfig: opts.reportConfig,
|
|
242
|
+
description: opts.description,
|
|
243
|
+
notificationConfig: opts.notificationConfig,
|
|
244
|
+
createdBy: opts.createdBy,
|
|
245
|
+
})));
|
|
246
|
+
|
|
247
|
+
// ── sprint-reports update ──────────────────────────────────────────────────
|
|
248
|
+
settings
|
|
249
|
+
.command('sprint-reports-update')
|
|
250
|
+
.description('Update a sprint report configuration')
|
|
251
|
+
.requiredOption('--config-id <id>', 'Config ID to update')
|
|
252
|
+
.requiredOption('--repo <repo>', 'Repository (owner/repo)')
|
|
253
|
+
.option('--status <status>', 'New status (e.g. ACTIVE, INACTIVE)')
|
|
254
|
+
.option('--name <name>', 'New name')
|
|
255
|
+
.option('--description <text>', 'New description')
|
|
256
|
+
.option('--schedule-config <json>', 'Schedule config as JSON')
|
|
257
|
+
.option('--report-config <json>', 'Report config as JSON')
|
|
258
|
+
.option('--notification-config <json>', 'Notification config as JSON')
|
|
259
|
+
.action((opts) => runCmd(() => runSprintReportsUpdate({
|
|
260
|
+
configId: opts.configId,
|
|
261
|
+
repo: opts.repo,
|
|
262
|
+
status: opts.status,
|
|
263
|
+
name: opts.name,
|
|
264
|
+
description: opts.description,
|
|
265
|
+
scheduleConfig: opts.scheduleConfig,
|
|
266
|
+
reportConfig: opts.reportConfig,
|
|
267
|
+
notificationConfig: opts.notificationConfig,
|
|
268
|
+
})));
|
|
269
|
+
|
|
270
|
+
// ── cve-reporting list ─────────────────────────────────────────────────────
|
|
271
|
+
settings
|
|
272
|
+
.command('cve-reporting-list')
|
|
273
|
+
.description('List CVE report configurations')
|
|
274
|
+
.option('--repo <repo>', 'Filter by repository (owner/repo)')
|
|
275
|
+
.option('--status <status>', 'Filter by status (e.g. ACTIVE)')
|
|
276
|
+
.option('--report-id <id>', 'Filter by report ID')
|
|
277
|
+
.action((opts) => runCmd(() => runCveReportingList({ repo: opts.repo, status: opts.status, reportId: opts.reportId })));
|
|
278
|
+
|
|
279
|
+
// ── cve-reporting create ───────────────────────────────────────────────────
|
|
280
|
+
settings
|
|
281
|
+
.command('cve-reporting-create')
|
|
282
|
+
.description('Create a CVE report configuration')
|
|
283
|
+
.requiredOption('--name <name>', 'Report name')
|
|
284
|
+
.option('--repo <repo>', 'Repository (owner/repo)')
|
|
285
|
+
.option('--schedule-config <json>', 'Schedule config as JSON (e.g. \'{"frequency":"weekly"}\')')
|
|
286
|
+
.option('--report-config <json>', 'Report config as JSON')
|
|
287
|
+
.option('--description <text>', 'Description')
|
|
288
|
+
.option('--notification-config <json>', 'Notification config as JSON')
|
|
289
|
+
.option('--created-by <user>', 'Creator identifier')
|
|
290
|
+
.action((opts) => runCmd(() => runCveReportingCreate({
|
|
291
|
+
name: opts.name,
|
|
292
|
+
repo: opts.repo,
|
|
293
|
+
scheduleConfig: opts.scheduleConfig,
|
|
294
|
+
reportConfig: opts.reportConfig,
|
|
295
|
+
description: opts.description,
|
|
296
|
+
notificationConfig: opts.notificationConfig,
|
|
297
|
+
createdBy: opts.createdBy,
|
|
298
|
+
})));
|
|
299
|
+
|
|
300
|
+
// ── cve-reporting update ───────────────────────────────────────────────────
|
|
301
|
+
settings
|
|
302
|
+
.command('cve-reporting-update')
|
|
303
|
+
.description('Update a CVE report configuration')
|
|
304
|
+
.requiredOption('--report-id <id>', 'Report ID to update')
|
|
305
|
+
.requiredOption('--repo <repo>', 'Repository (owner/repo)')
|
|
306
|
+
.option('--status <status>', 'New status (e.g. ACTIVE, INACTIVE)')
|
|
307
|
+
.option('--name <name>', 'New name')
|
|
308
|
+
.option('--description <text>', 'New description')
|
|
309
|
+
.option('--schedule-config <json>', 'Schedule config as JSON')
|
|
310
|
+
.option('--report-config <json>', 'Report config as JSON')
|
|
311
|
+
.option('--notification-config <json>', 'Notification config as JSON')
|
|
312
|
+
.action((opts) => runCmd(() => runCveReportingUpdate({
|
|
313
|
+
reportId: opts.reportId,
|
|
314
|
+
repo: opts.repo,
|
|
315
|
+
status: opts.status,
|
|
316
|
+
name: opts.name,
|
|
317
|
+
description: opts.description,
|
|
318
|
+
scheduleConfig: opts.scheduleConfig,
|
|
319
|
+
reportConfig: opts.reportConfig,
|
|
320
|
+
notificationConfig: opts.notificationConfig,
|
|
321
|
+
})));
|
|
322
|
+
|
|
323
|
+
// ── team-subscriptions-list ────────────────────────────────────────────────
|
|
324
|
+
settings
|
|
325
|
+
.command('team-subscriptions-list')
|
|
326
|
+
.description('List all subscriptions for the account')
|
|
327
|
+
.action(() => runCmd(() => runTeamSubscriptionsList()));
|
|
328
|
+
|
|
329
|
+
// ── team-users-list ────────────────────────────────────────────────────────
|
|
330
|
+
settings
|
|
331
|
+
.command('team-users-list')
|
|
332
|
+
.description('List users for a subscription')
|
|
333
|
+
.requiredOption('--subscription-id <id>', 'Subscription ID')
|
|
334
|
+
.action((opts) => runCmd(() => runTeamUsersList({ subscriptionId: opts.subscriptionId })));
|
|
335
|
+
|
|
336
|
+
// ── team-user-get ──────────────────────────────────────────────────────────
|
|
337
|
+
settings
|
|
338
|
+
.command('team-user-get')
|
|
339
|
+
.description('Get a user by ID')
|
|
340
|
+
.requiredOption('--user-id <id>', 'User ID')
|
|
341
|
+
.action((opts) => runCmd(() => runTeamUserGet({ userId: opts.userId })));
|
|
342
|
+
|
|
343
|
+
// ── team-user-create ───────────────────────────────────────────────────────
|
|
344
|
+
settings
|
|
345
|
+
.command('team-user-create')
|
|
346
|
+
.description('Create a new team user')
|
|
347
|
+
.requiredOption('--user-id <id>', 'User ID')
|
|
348
|
+
.requiredOption('--email <email>', 'User email')
|
|
349
|
+
.requiredOption('--name <name>', 'User display name')
|
|
350
|
+
.requiredOption('--role <role>', 'User role (e.g. developer, admin)')
|
|
351
|
+
.option('--plan-ids <json>', 'Plan IDs as JSON array (e.g. \'["plan_1"]\')', '[]')
|
|
352
|
+
.option('--subscription-ids <json>', 'Subscription IDs as JSON array', '[]')
|
|
353
|
+
.option('--invited', 'Send invitation email', false)
|
|
354
|
+
.action((opts) => runCmd(() => runTeamUserCreate({
|
|
355
|
+
userId: opts.userId,
|
|
356
|
+
email: opts.email,
|
|
357
|
+
name: opts.name,
|
|
358
|
+
role: opts.role,
|
|
359
|
+
planIds: opts.planIds,
|
|
360
|
+
subscriptionIds: opts.subscriptionIds,
|
|
361
|
+
invited: opts.invited,
|
|
362
|
+
})));
|
|
363
|
+
|
|
364
|
+
// ── team-user-delete ───────────────────────────────────────────────────────
|
|
365
|
+
settings
|
|
366
|
+
.command('team-user-delete')
|
|
367
|
+
.description('Delete a team user')
|
|
368
|
+
.requiredOption('--user-id <id>', 'User ID to delete')
|
|
369
|
+
.action((opts) => runCmd(() => runTeamUserDelete({ userId: opts.userId })));
|
|
370
|
+
|
|
371
|
+
// ── team-user-update ───────────────────────────────────────────────────────
|
|
372
|
+
settings
|
|
373
|
+
.command('team-user-update')
|
|
374
|
+
.description('Update a team user')
|
|
375
|
+
.requiredOption('--user-id <id>', 'User ID to update')
|
|
376
|
+
.requiredOption('--email <email>', 'User email')
|
|
377
|
+
.requiredOption('--name <name>', 'User display name')
|
|
378
|
+
.requiredOption('--role <role>', 'User role (e.g. developer, admin)')
|
|
379
|
+
.requiredOption('--status <status>', 'User status (e.g. active, inactive)')
|
|
380
|
+
.option('--plan-ids <json>', 'Plan IDs as JSON array', '[]')
|
|
381
|
+
.option('--subscription-ids <json>', 'Subscription IDs as JSON array', '[]')
|
|
382
|
+
.action((opts) => runCmd(() => runTeamUserUpdate({
|
|
383
|
+
userId: opts.userId,
|
|
384
|
+
email: opts.email,
|
|
385
|
+
name: opts.name,
|
|
386
|
+
role: opts.role,
|
|
387
|
+
status: opts.status,
|
|
388
|
+
planIds: opts.planIds,
|
|
389
|
+
subscriptionIds: opts.subscriptionIds,
|
|
390
|
+
})));
|
|
391
|
+
|
|
392
|
+
// ── team-rbac-get ──────────────────────────────────────────────────────────
|
|
393
|
+
settings
|
|
394
|
+
.command('team-rbac-get')
|
|
395
|
+
.description('Get RBAC settings for a repository')
|
|
396
|
+
.requiredOption('--repo <repo>', 'Repository (owner/repo)')
|
|
397
|
+
.action((opts) => runCmd(() => runTeamRbacGet({ repo: opts.repo })));
|
|
398
|
+
|
|
399
|
+
// ── team-rbac-save ─────────────────────────────────────────────────────────
|
|
400
|
+
settings
|
|
401
|
+
.command('team-rbac-save')
|
|
402
|
+
.description('Save RBAC settings for a repository')
|
|
403
|
+
.requiredOption('--repo <repo>', 'Repository (owner/repo)')
|
|
404
|
+
.requiredOption('--data <json>', 'RBAC settings as JSON (e.g. \'{"enabled":true}\')')
|
|
405
|
+
.action((opts) => runCmd(() => runTeamRbacSave({ repo: opts.repo, data: opts.data })));
|
|
406
|
+
|
|
407
|
+
// ── team-plan-end ──────────────────────────────────────────────────────────
|
|
408
|
+
settings
|
|
409
|
+
.command('team-plan-end')
|
|
410
|
+
.description('End/expire the active subscription plan')
|
|
411
|
+
.option('--include-users', 'Also remove all users from the plan', false)
|
|
412
|
+
.action((opts) => runCmd(() => runTeamPlanEnd({ includeUsers: opts.includeUsers })));
|
|
413
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { fetchApi } from '../../utils/fetchApi.js';
|
|
2
|
+
|
|
3
|
+
export async function runPrInstructionsGet({ instructionsType } = {}) {
|
|
4
|
+
return fetchApi('/extension/config/pullrequest/instructions/get', 'POST', {
|
|
5
|
+
instructions_type: instructionsType,
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function runPrInstructionsSave({ instructionType, filePattern, description, descriptionFile, instructionId } = {}) {
|
|
10
|
+
return fetchApi('/extension/config/pullrequest/instructions/save', 'POST', {
|
|
11
|
+
instruction_type: instructionType,
|
|
12
|
+
file_pattern: filePattern,
|
|
13
|
+
description,
|
|
14
|
+
description_file: descriptionFile,
|
|
15
|
+
instruction_id: instructionId,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function runPrInstructionsEdit({ instructionType, instructionId, description, descriptionFile, filePattern } = {}) {
|
|
20
|
+
if (!instructionId) {
|
|
21
|
+
const err = new Error('--instruction-id is required');
|
|
22
|
+
err.exitCode = 1;
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
return fetchApi('/extension/config/pullrequest/instructions/edit', 'POST', {
|
|
26
|
+
instruction_type: instructionType,
|
|
27
|
+
instruction_id: instructionId,
|
|
28
|
+
description,
|
|
29
|
+
description_file: descriptionFile,
|
|
30
|
+
file_pattern: filePattern,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function runPrInstructionsDelete({ instructionType, instructionId } = {}) {
|
|
35
|
+
if (!instructionId) {
|
|
36
|
+
const err = new Error('--instruction-id is required');
|
|
37
|
+
err.exitCode = 1;
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
return fetchApi('/extension/config/pullrequest/instructions/delete', 'POST', {
|
|
41
|
+
instruction_type: instructionType,
|
|
42
|
+
instruction_id: instructionId,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { fetchApi } from '../../utils/fetchApi.js';
|
|
2
|
+
|
|
3
|
+
export async function runRecurringScanslist({ repo, status, scheduleId } = {}) {
|
|
4
|
+
return fetchApi('/extension/analysis/recurring-scans/list', 'POST', { repo, status, schedule_id: scheduleId });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function runRecurringScansCreate({ name, repo, scheduleConfig, scanConfig, description, notificationConfig, createdBy } = {}) {
|
|
8
|
+
if (!name) {
|
|
9
|
+
const err = new Error('--name is required');
|
|
10
|
+
err.exitCode = 1;
|
|
11
|
+
throw err;
|
|
12
|
+
}
|
|
13
|
+
return fetchApi('/extension/analysis/recurring-scans/create', 'POST', {
|
|
14
|
+
name,
|
|
15
|
+
repo,
|
|
16
|
+
schedule_config: scheduleConfig ? JSON.parse(scheduleConfig) : undefined,
|
|
17
|
+
scan_config: scanConfig ? JSON.parse(scanConfig) : undefined,
|
|
18
|
+
description,
|
|
19
|
+
notification_config: notificationConfig ? JSON.parse(notificationConfig) : undefined,
|
|
20
|
+
created_by: createdBy,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function runRecurringScansUpdate({ scheduleId, repo, status, name, description, scheduleConfig, scanConfig, notificationConfig } = {}) {
|
|
25
|
+
if (!scheduleId) {
|
|
26
|
+
const err = new Error('--schedule-id is required');
|
|
27
|
+
err.exitCode = 1;
|
|
28
|
+
throw err;
|
|
29
|
+
}
|
|
30
|
+
return fetchApi('/extension/analysis/recurring-scans/update', 'POST', {
|
|
31
|
+
schedule_id: scheduleId,
|
|
32
|
+
repo,
|
|
33
|
+
status,
|
|
34
|
+
name,
|
|
35
|
+
description,
|
|
36
|
+
schedule_config: scheduleConfig ? JSON.parse(scheduleConfig) : undefined,
|
|
37
|
+
scan_config: scanConfig ? JSON.parse(scanConfig) : undefined,
|
|
38
|
+
notification_config: notificationConfig ? JSON.parse(notificationConfig) : undefined,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { fetchApi } from '../../utils/fetchApi.js';
|
|
2
|
+
|
|
3
|
+
export async function runSprintReportsList({ repo, status, configId } = {}) {
|
|
4
|
+
return fetchApi('/extension/metrics/sprint-reports/list', 'POST', { repo, status, config_id: configId });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function runSprintReportsCreate({ name, repo, scheduleConfig, reportConfig, description, notificationConfig, createdBy } = {}) {
|
|
8
|
+
if (!name) {
|
|
9
|
+
const err = new Error('--name is required');
|
|
10
|
+
err.exitCode = 1;
|
|
11
|
+
throw err;
|
|
12
|
+
}
|
|
13
|
+
return fetchApi('/extension/metrics/sprint-reports/create', 'POST', {
|
|
14
|
+
name,
|
|
15
|
+
repo,
|
|
16
|
+
schedule_config: scheduleConfig ? JSON.parse(scheduleConfig) : undefined,
|
|
17
|
+
report_config: reportConfig ? JSON.parse(reportConfig) : undefined,
|
|
18
|
+
description,
|
|
19
|
+
notification_config: notificationConfig ? JSON.parse(notificationConfig) : undefined,
|
|
20
|
+
created_by: createdBy,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function runSprintReportsUpdate({ configId, repo, status, name, description, scheduleConfig, reportConfig, notificationConfig } = {}) {
|
|
25
|
+
if (!configId) {
|
|
26
|
+
const err = new Error('--config-id is required');
|
|
27
|
+
err.exitCode = 1;
|
|
28
|
+
throw err;
|
|
29
|
+
}
|
|
30
|
+
return fetchApi('/extension/metrics/sprint-reports/update', 'POST', {
|
|
31
|
+
config_id: configId,
|
|
32
|
+
repo,
|
|
33
|
+
status,
|
|
34
|
+
name,
|
|
35
|
+
description,
|
|
36
|
+
schedule_config: scheduleConfig ? JSON.parse(scheduleConfig) : undefined,
|
|
37
|
+
report_config: reportConfig ? JSON.parse(reportConfig) : undefined,
|
|
38
|
+
notification_config: notificationConfig ? JSON.parse(notificationConfig) : undefined,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { fetchApi } from '../../utils/fetchApi.js';
|
|
2
|
+
|
|
3
|
+
export async function runTeamSubscriptionsList() {
|
|
4
|
+
return fetchApi('/extension/account/subscriptions/list', 'POST', {});
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function runTeamUsersList({ subscriptionId } = {}) {
|
|
8
|
+
return fetchApi('/extension/account/subscription/users/list', 'POST', { subscription_id: subscriptionId });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function runTeamUserGet({ userId } = {}) {
|
|
12
|
+
return fetchApi('/extension/account/users/user/get', 'POST', { user_id: userId });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function runTeamUserCreate({ userId, email, name, role, planIds, subscriptionIds, invited } = {}) {
|
|
16
|
+
return fetchApi('/extension/account/users/user/create', 'POST', {
|
|
17
|
+
users: [{
|
|
18
|
+
user_id: userId,
|
|
19
|
+
email,
|
|
20
|
+
name,
|
|
21
|
+
role,
|
|
22
|
+
plan_ids: planIds ? JSON.parse(planIds) : [],
|
|
23
|
+
subscription_ids: subscriptionIds ? JSON.parse(subscriptionIds) : [],
|
|
24
|
+
}],
|
|
25
|
+
invited: invited ?? false,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function runTeamUserDelete({ userId } = {}) {
|
|
30
|
+
return fetchApi('/extension/account/users/user/delete', 'POST', { user_id: userId });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function runTeamUserUpdate({ userId, email, name, role, status, planIds, subscriptionIds } = {}) {
|
|
34
|
+
return fetchApi('/extension/account/users/user/update', 'POST', {
|
|
35
|
+
user_id: userId,
|
|
36
|
+
email,
|
|
37
|
+
name,
|
|
38
|
+
role,
|
|
39
|
+
status,
|
|
40
|
+
plan_ids: planIds ? JSON.parse(planIds) : [],
|
|
41
|
+
subscription_ids: subscriptionIds ? JSON.parse(subscriptionIds) : [],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function runTeamRbacGet({ repo } = {}) {
|
|
46
|
+
return fetchApi('/extension/account/rbac/settings/get', 'POST', { repo });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function runTeamRbacSave({ repo, data } = {}) {
|
|
50
|
+
return fetchApi('/extension/account/rbac/settings/save', 'POST', { repo, data: JSON.parse(data) });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function runTeamPlanEnd({ includeUsers } = {}) {
|
|
54
|
+
return fetchApi('/extension/account/plan/subscriptions/end', 'POST', { include_users: includeUsers ?? false });
|
|
55
|
+
}
|
package/src/index.js
CHANGED
|
@@ -19,6 +19,7 @@ import * as scm from './scm/index.js';
|
|
|
19
19
|
import { setConfigValue } from './utils/config.js';
|
|
20
20
|
import { track, shutdown as analyticsShutdown, isTelemetryDisabled } from './utils/analytics.js';
|
|
21
21
|
import registerScansCommands from './commands/scans/index.js';
|
|
22
|
+
import registerSettingsCommands from './commands/settings/index.js';
|
|
22
23
|
|
|
23
24
|
// Read version from package.json
|
|
24
25
|
const require = createRequire(import.meta.url);
|
|
@@ -393,6 +394,9 @@ program
|
|
|
393
394
|
// ─── Scans commands ───
|
|
394
395
|
registerScansCommands(program, { runCmd });
|
|
395
396
|
|
|
397
|
+
// ─── Settings commands ───
|
|
398
|
+
registerSettingsCommands(program, { runCmd });
|
|
399
|
+
|
|
396
400
|
// ─── Telemetry control ───
|
|
397
401
|
program
|
|
398
402
|
.command('set-telemetry <enabled>')
|
package/src/scm/gitlab/client.js
CHANGED
|
@@ -10,7 +10,7 @@ export function getClient() {
|
|
|
10
10
|
return new Gitlab(opts);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
// GitLab uses project ID or
|
|
13
|
+
// GitLab uses project ID or the full path — gitbeaker's endpoint tag encodes it
|
|
14
14
|
export function projectId(name) {
|
|
15
|
-
return
|
|
15
|
+
return name;
|
|
16
16
|
}
|
package/src/scm/index.js
CHANGED
|
@@ -67,7 +67,7 @@ export function detectRepoName() {
|
|
|
67
67
|
if (!origin) return null;
|
|
68
68
|
// Handle SSH: git@github.com:owner/repo.git
|
|
69
69
|
// Handle HTTPS: https://github.com/owner/repo.git
|
|
70
|
-
const match = origin.match(/[/:]([\w.-]
|
|
70
|
+
const match = origin.match(/[/:]([\w.-]+(?:\/[\w.-]+)+?)(?:\.git)?$/);
|
|
71
71
|
return match ? match[1] : null;
|
|
72
72
|
}
|
|
73
73
|
|
package/src/utils/fetchApi.js
CHANGED
|
@@ -23,6 +23,7 @@ const fetchApi = async (endpoint, method = 'GET', body = null) => {
|
|
|
23
23
|
|
|
24
24
|
try {
|
|
25
25
|
const response = await fetch(url, options);
|
|
26
|
+
console.error('API Response Status:', response.status);
|
|
26
27
|
|
|
27
28
|
if (response.status === 403) {
|
|
28
29
|
throw new Error('Access denied (403). Please run `codeant logout` and then `codeant login` to re-authenticate.');
|
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import { minimatch } from 'minimatch';
|
|
5
5
|
|
|
6
|
-
const MAX_REVIEW_FILES =
|
|
6
|
+
const MAX_REVIEW_FILES = 15;
|
|
7
7
|
const MAX_FILE_LINES = 5000;
|
|
8
8
|
const MAX_FILE_SIZE_BYTES = 200 * 1024; // 200 KB
|
|
9
9
|
|
|
@@ -184,41 +184,20 @@ class ReviewApiHelper extends CommonApiHelper {
|
|
|
184
184
|
return true;
|
|
185
185
|
});
|
|
186
186
|
|
|
187
|
-
// Dedupe by filename, keeping all hunks
|
|
187
|
+
// Dedupe by filename, keeping all hunks (no cap yet — applied after size filtering)
|
|
188
188
|
const fileOrder = [];
|
|
189
189
|
const fileHunks = new Map();
|
|
190
|
-
let capped = false;
|
|
191
190
|
for (const d of reviewable) {
|
|
192
191
|
const f = d.filename_str;
|
|
193
192
|
if (!fileHunks.has(f)) {
|
|
194
|
-
if (fileOrder.length >= MAX_REVIEW_FILES) {
|
|
195
|
-
capped = true;
|
|
196
|
-
if (!seenSkipped.has(f)) {
|
|
197
|
-
dedupedSkipped.push({ file: f, reason: `exceeded ${MAX_REVIEW_FILES}-file limit` });
|
|
198
|
-
seenSkipped.add(f);
|
|
199
|
-
}
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
193
|
fileOrder.push(f);
|
|
203
194
|
fileHunks.set(f, []);
|
|
204
195
|
}
|
|
205
196
|
fileHunks.get(f).push(d);
|
|
206
197
|
}
|
|
207
198
|
|
|
208
|
-
// Reconstruct a combined diff string from the filtered hunks
|
|
209
|
-
const parts = [];
|
|
210
|
-
for (const f of fileOrder) {
|
|
211
|
-
const hunks = fileHunks.get(f);
|
|
212
|
-
// Build a file-level diff header + all hunk patches
|
|
213
|
-
const header = `diff --git a/${f} b/${f}\n--- a/${f}\n+++ b/${f}`;
|
|
214
|
-
const hunkPatches = hunks.map(h => h.patch_str).filter(Boolean);
|
|
215
|
-
if (hunkPatches.length > 0) {
|
|
216
|
-
parts.push(header + '\n' + hunkPatches.join('\n'));
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
199
|
// Use head_file_str from diffs so file content matches the diff version (avoids working tree mismatch for --last-commit etc.)
|
|
221
|
-
|
|
200
|
+
// Filter out files that exceed size or line limits before applying the cap
|
|
222
201
|
const skippedLargeFiles = new Set();
|
|
223
202
|
for (const f of fileOrder) {
|
|
224
203
|
const hunks = fileHunks.get(f);
|
|
@@ -234,21 +213,49 @@ class ReviewApiHelper extends CommonApiHelper {
|
|
|
234
213
|
if (lineCount > MAX_FILE_LINES) {
|
|
235
214
|
skippedLargeFiles.add(f);
|
|
236
215
|
dedupedSkipped.push({ file: f, reason: `too large (${lineCount} lines, max ${MAX_FILE_LINES})` });
|
|
237
|
-
continue;
|
|
238
216
|
}
|
|
239
|
-
fileContents[f] = content;
|
|
240
217
|
}
|
|
241
218
|
}
|
|
242
219
|
|
|
243
|
-
//
|
|
244
|
-
|
|
220
|
+
// Apply MAX_REVIEW_FILES cap after all filtering (binary, deleted, pattern, size)
|
|
221
|
+
let capped = false;
|
|
222
|
+
const cappedFileOrder = [];
|
|
223
|
+
for (const f of fileOrder) {
|
|
224
|
+
if (skippedLargeFiles.has(f)) continue;
|
|
225
|
+
if (cappedFileOrder.length >= MAX_REVIEW_FILES) {
|
|
226
|
+
capped = true;
|
|
227
|
+
if (!seenSkipped.has(f)) {
|
|
228
|
+
dedupedSkipped.push({ file: f, reason: `exceeded ${MAX_REVIEW_FILES}-file limit` });
|
|
229
|
+
seenSkipped.add(f);
|
|
230
|
+
}
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
cappedFileOrder.push(f);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Build file contents for the capped set
|
|
237
|
+
const fileContents = {};
|
|
238
|
+
for (const f of cappedFileOrder) {
|
|
239
|
+
const content = fileHunks.get(f)[0]?.head_file_str || '';
|
|
240
|
+
if (content) fileContents[f] = content;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Reconstruct a combined diff string from the capped set
|
|
244
|
+
const parts = [];
|
|
245
|
+
for (const f of cappedFileOrder) {
|
|
246
|
+
const hunks = fileHunks.get(f);
|
|
247
|
+
const header = `diff --git a/${f} b/${f}\n--- a/${f}\n+++ b/${f}`;
|
|
248
|
+
const hunkPatches = hunks.map(h => h.patch_str).filter(Boolean);
|
|
249
|
+
if (hunkPatches.length > 0) {
|
|
250
|
+
parts.push(header + '\n' + hunkPatches.join('\n'));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
245
253
|
|
|
246
|
-
const reviewedFiles = fileOrder.filter(f => !skippedLargeFiles.has(f));
|
|
247
254
|
return {
|
|
248
|
-
diff_content:
|
|
255
|
+
diff_content: parts.join('\n'),
|
|
249
256
|
file_contents: fileContents,
|
|
250
257
|
_meta: {
|
|
251
|
-
reviewed_files:
|
|
258
|
+
reviewed_files: cappedFileOrder,
|
|
252
259
|
total_changed: allUniqueFiles.size,
|
|
253
260
|
skipped: dedupedSkipped,
|
|
254
261
|
capped,
|