specweave 0.8.19 ā 0.8.20
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/dist/cli/helpers/issue-tracker/ado.d.ts.map +1 -1
- package/dist/cli/helpers/issue-tracker/ado.js +17 -5
- package/dist/cli/helpers/issue-tracker/ado.js.map +1 -1
- package/dist/cli/helpers/issue-tracker/types.d.ts +3 -0
- package/dist/cli/helpers/issue-tracker/types.d.ts.map +1 -1
- package/dist/cli/helpers/issue-tracker/types.js.map +1 -1
- package/dist/core/credentials-manager.d.ts +3 -0
- package/dist/core/credentials-manager.d.ts.map +1 -1
- package/dist/core/credentials-manager.js +69 -9
- package/dist/core/credentials-manager.js.map +1 -1
- package/dist/integrations/ado/ado-client.d.ts +4 -0
- package/dist/integrations/ado/ado-client.d.ts.map +1 -1
- package/dist/integrations/ado/ado-client.js +18 -0
- package/dist/integrations/ado/ado-client.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave-ado/lib/project-selector.d.ts +42 -0
- package/plugins/specweave-ado/lib/project-selector.d.ts.map +1 -0
- package/plugins/specweave-ado/lib/project-selector.js +211 -0
- package/plugins/specweave-ado/lib/project-selector.js.map +1 -0
- package/plugins/specweave-ado/lib/project-selector.ts +317 -0
- package/plugins/specweave-github/lib/github-client.ts +28 -0
- package/plugins/specweave-github/lib/repo-selector.ts +329 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADO Project Selector with Pagination
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Fetches all ADO projects via API
|
|
6
|
+
* - Interactive multi-select with search
|
|
7
|
+
* - Manual project name entry (comma-separated)
|
|
8
|
+
* - Handles large project lists (50+ projects)
|
|
9
|
+
* - Validates project names
|
|
10
|
+
*/
|
|
11
|
+
import inquirer from 'inquirer';
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// ADO Project Fetching
|
|
14
|
+
// ============================================================================
|
|
15
|
+
/**
|
|
16
|
+
* Fetch all ADO projects from API
|
|
17
|
+
*/
|
|
18
|
+
export async function fetchAllAdoProjects(client) {
|
|
19
|
+
console.log('š Fetching Azure DevOps projects...');
|
|
20
|
+
try {
|
|
21
|
+
const projects = await client.getProjects();
|
|
22
|
+
console.log(`ā
Found ${projects.length} Azure DevOps projects\n`);
|
|
23
|
+
return projects;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
console.error('ā Failed to fetch ADO projects:', error.message);
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Interactive Project Selection
|
|
32
|
+
// ============================================================================
|
|
33
|
+
/**
|
|
34
|
+
* Interactive project selector with search and pagination
|
|
35
|
+
*/
|
|
36
|
+
export async function selectAdoProjects(client, options = {}) {
|
|
37
|
+
const { allowManualEntry = true, allowSelectAll = true, preSelected = [], minSelection = 1, maxSelection, pageSize = 15, } = options;
|
|
38
|
+
// Fetch all projects
|
|
39
|
+
const allProjects = await fetchAllAdoProjects(client);
|
|
40
|
+
if (allProjects.length === 0) {
|
|
41
|
+
console.log('ā ļø No Azure DevOps projects found');
|
|
42
|
+
return {
|
|
43
|
+
selectedNames: [],
|
|
44
|
+
method: 'interactive',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// Show project overview
|
|
48
|
+
console.log('š Available Azure DevOps Projects:\n');
|
|
49
|
+
console.log(` Total: ${allProjects.length} projects\n`);
|
|
50
|
+
// Decide selection method
|
|
51
|
+
const { selectionMethod } = await inquirer.prompt([
|
|
52
|
+
{
|
|
53
|
+
type: 'list',
|
|
54
|
+
name: 'selectionMethod',
|
|
55
|
+
message: 'How would you like to select projects?',
|
|
56
|
+
choices: [
|
|
57
|
+
{
|
|
58
|
+
name: `š Interactive (browse and select from ${allProjects.length} projects)`,
|
|
59
|
+
value: 'interactive',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'āļø Manual entry (type project names)',
|
|
63
|
+
value: 'manual',
|
|
64
|
+
},
|
|
65
|
+
...(allowSelectAll
|
|
66
|
+
? [
|
|
67
|
+
{
|
|
68
|
+
name: `⨠Select all (${allProjects.length} projects)`,
|
|
69
|
+
value: 'all',
|
|
70
|
+
},
|
|
71
|
+
]
|
|
72
|
+
: []),
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
]);
|
|
76
|
+
if (selectionMethod === 'all') {
|
|
77
|
+
return {
|
|
78
|
+
selectedNames: allProjects.map((p) => p.name),
|
|
79
|
+
method: 'all',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (selectionMethod === 'manual') {
|
|
83
|
+
return await manualProjectEntry(allProjects, minSelection, maxSelection);
|
|
84
|
+
}
|
|
85
|
+
// Interactive selection
|
|
86
|
+
return await interactiveProjectSelection(allProjects, preSelected, minSelection, maxSelection, pageSize);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Interactive project selection with checkbox
|
|
90
|
+
*/
|
|
91
|
+
async function interactiveProjectSelection(allProjects, preSelected, minSelection, maxSelection, pageSize) {
|
|
92
|
+
console.log('š” Use <space> to select, <a> to toggle all, <i> to invert\n');
|
|
93
|
+
const choices = allProjects.map((p) => ({
|
|
94
|
+
name: formatProjectChoice(p),
|
|
95
|
+
value: p.name,
|
|
96
|
+
checked: preSelected.includes(p.name),
|
|
97
|
+
}));
|
|
98
|
+
// Add manual entry option at the end
|
|
99
|
+
choices.push(new inquirer.Separator(), {
|
|
100
|
+
name: 'āļø Enter project names manually instead',
|
|
101
|
+
value: '__MANUAL__',
|
|
102
|
+
checked: false,
|
|
103
|
+
});
|
|
104
|
+
const { selectedNames } = await inquirer.prompt([
|
|
105
|
+
{
|
|
106
|
+
type: 'checkbox',
|
|
107
|
+
name: 'selectedNames',
|
|
108
|
+
message: `Select Azure DevOps projects (${minSelection}${maxSelection ? `-${maxSelection}` : '+'} required):`,
|
|
109
|
+
choices,
|
|
110
|
+
pageSize,
|
|
111
|
+
loop: false,
|
|
112
|
+
validate: (selected) => {
|
|
113
|
+
const actualSelected = selected.filter((k) => k !== '__MANUAL__');
|
|
114
|
+
if (actualSelected.length < minSelection) {
|
|
115
|
+
return `Please select at least ${minSelection} project(s)`;
|
|
116
|
+
}
|
|
117
|
+
if (maxSelection && actualSelected.length > maxSelection) {
|
|
118
|
+
return `Please select at most ${maxSelection} project(s)`;
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
]);
|
|
124
|
+
// Check if user chose manual entry
|
|
125
|
+
if (selectedNames.includes('__MANUAL__')) {
|
|
126
|
+
return await manualProjectEntry(allProjects, minSelection, maxSelection);
|
|
127
|
+
}
|
|
128
|
+
console.log(`\nā
Selected ${selectedNames.length} projects: ${selectedNames.join(', ')}\n`);
|
|
129
|
+
return {
|
|
130
|
+
selectedNames,
|
|
131
|
+
method: 'interactive',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Manual project name entry (comma-separated)
|
|
136
|
+
*/
|
|
137
|
+
async function manualProjectEntry(allProjects, minSelection, maxSelection) {
|
|
138
|
+
console.log('\nš Enter project names manually\n');
|
|
139
|
+
console.log('š” Format: Comma-separated project names (e.g., AI Meme Generator,Project 2,Project 3)\n');
|
|
140
|
+
if (allProjects.length > 0) {
|
|
141
|
+
console.log('Available project names:');
|
|
142
|
+
console.log(allProjects
|
|
143
|
+
.map((p) => p.name)
|
|
144
|
+
.join(', ')
|
|
145
|
+
.substring(0, 100) + (allProjects.length > 20 ? '...' : ''));
|
|
146
|
+
console.log('');
|
|
147
|
+
}
|
|
148
|
+
const { manualNames } = await inquirer.prompt([
|
|
149
|
+
{
|
|
150
|
+
type: 'input',
|
|
151
|
+
name: 'manualNames',
|
|
152
|
+
message: 'Project names:',
|
|
153
|
+
validate: (input) => {
|
|
154
|
+
if (!input.trim()) {
|
|
155
|
+
return 'Please enter at least one project name';
|
|
156
|
+
}
|
|
157
|
+
const names = input
|
|
158
|
+
.split(',')
|
|
159
|
+
.map((n) => n.trim())
|
|
160
|
+
.filter((n) => n.length > 0);
|
|
161
|
+
if (names.length < minSelection) {
|
|
162
|
+
return `Please enter at least ${minSelection} project name(s)`;
|
|
163
|
+
}
|
|
164
|
+
if (maxSelection && names.length > maxSelection) {
|
|
165
|
+
return `Please enter at most ${maxSelection} project name(s)`;
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
]);
|
|
171
|
+
const selectedNames = manualNames
|
|
172
|
+
.split(',')
|
|
173
|
+
.map((n) => n.trim())
|
|
174
|
+
.filter((n) => n.length > 0);
|
|
175
|
+
// Warn about unknown projects
|
|
176
|
+
const knownNames = allProjects.map((p) => p.name);
|
|
177
|
+
const unknownNames = selectedNames.filter((n) => !knownNames.includes(n));
|
|
178
|
+
if (unknownNames.length > 0) {
|
|
179
|
+
console.log(`\nā ļø Unknown project names (will be used anyway): ${unknownNames.join(', ')}`);
|
|
180
|
+
console.log(' Make sure these projects exist in your Azure DevOps organization.\n');
|
|
181
|
+
}
|
|
182
|
+
console.log(`ā
Selected ${selectedNames.length} projects: ${selectedNames.join(', ')}\n`);
|
|
183
|
+
return {
|
|
184
|
+
selectedNames,
|
|
185
|
+
method: 'manual',
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
// ============================================================================
|
|
189
|
+
// Helpers
|
|
190
|
+
// ============================================================================
|
|
191
|
+
/**
|
|
192
|
+
* Format project choice for display
|
|
193
|
+
*/
|
|
194
|
+
function formatProjectChoice(project) {
|
|
195
|
+
const visibility = project.visibility || 'private';
|
|
196
|
+
const state = project.state || 'wellFormed';
|
|
197
|
+
return `${project.name.padEnd(30)} - ${project.description || 'No description'} (${visibility}, ${state})`;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Quick project selector - select single project
|
|
201
|
+
*/
|
|
202
|
+
export async function selectSingleAdoProject(client, message = 'Select Azure DevOps project:') {
|
|
203
|
+
const result = await selectAdoProjects(client, {
|
|
204
|
+
allowManualEntry: true,
|
|
205
|
+
allowSelectAll: false,
|
|
206
|
+
minSelection: 1,
|
|
207
|
+
maxSelection: 1,
|
|
208
|
+
});
|
|
209
|
+
return result.selectedNames[0];
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=project-selector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-selector.js","sourceRoot":"","sources":["project-selector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADO Project Selector with Pagination
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Fetches all ADO projects via API
|
|
6
|
+
* - Interactive multi-select with search
|
|
7
|
+
* - Manual project name entry (comma-separated)
|
|
8
|
+
* - Handles large project lists (50+ projects)
|
|
9
|
+
* - Validates project names
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import inquirer from 'inquirer';
|
|
13
|
+
import { AdoClient } from '../../../src/integrations/ado/ado-client.js';
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Types
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
export interface ProjectSelectionResult {
|
|
20
|
+
selectedNames: string[];
|
|
21
|
+
method: 'interactive' | 'manual' | 'all';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ProjectSelectorOptions {
|
|
25
|
+
/** Allow manual entry of project names */
|
|
26
|
+
allowManualEntry?: boolean;
|
|
27
|
+
|
|
28
|
+
/** Allow "Select All" option */
|
|
29
|
+
allowSelectAll?: boolean;
|
|
30
|
+
|
|
31
|
+
/** Pre-select these project names */
|
|
32
|
+
preSelected?: string[];
|
|
33
|
+
|
|
34
|
+
/** Minimum projects to select (0 = optional) */
|
|
35
|
+
minSelection?: number;
|
|
36
|
+
|
|
37
|
+
/** Maximum projects to select (undefined = unlimited) */
|
|
38
|
+
maxSelection?: number;
|
|
39
|
+
|
|
40
|
+
/** Page size for pagination */
|
|
41
|
+
pageSize?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// ADO Project Fetching
|
|
46
|
+
// ============================================================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Fetch all ADO projects from API
|
|
50
|
+
*/
|
|
51
|
+
export async function fetchAllAdoProjects(
|
|
52
|
+
client: AdoClient
|
|
53
|
+
): Promise<any[]> {
|
|
54
|
+
console.log('š Fetching Azure DevOps projects...');
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const projects = await client.getProjects();
|
|
58
|
+
|
|
59
|
+
console.log(`ā
Found ${projects.length} Azure DevOps projects\n`);
|
|
60
|
+
|
|
61
|
+
return projects;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('ā Failed to fetch ADO projects:', (error as Error).message);
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// Interactive Project Selection
|
|
70
|
+
// ============================================================================
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Interactive project selector with search and pagination
|
|
74
|
+
*/
|
|
75
|
+
export async function selectAdoProjects(
|
|
76
|
+
client: AdoClient,
|
|
77
|
+
options: ProjectSelectorOptions = {}
|
|
78
|
+
): Promise<ProjectSelectionResult> {
|
|
79
|
+
const {
|
|
80
|
+
allowManualEntry = true,
|
|
81
|
+
allowSelectAll = true,
|
|
82
|
+
preSelected = [],
|
|
83
|
+
minSelection = 1,
|
|
84
|
+
maxSelection,
|
|
85
|
+
pageSize = 15,
|
|
86
|
+
} = options;
|
|
87
|
+
|
|
88
|
+
// Fetch all projects
|
|
89
|
+
const allProjects = await fetchAllAdoProjects(client);
|
|
90
|
+
|
|
91
|
+
if (allProjects.length === 0) {
|
|
92
|
+
console.log('ā ļø No Azure DevOps projects found');
|
|
93
|
+
return {
|
|
94
|
+
selectedNames: [],
|
|
95
|
+
method: 'interactive',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Show project overview
|
|
100
|
+
console.log('š Available Azure DevOps Projects:\n');
|
|
101
|
+
console.log(` Total: ${allProjects.length} projects\n`);
|
|
102
|
+
|
|
103
|
+
// Decide selection method
|
|
104
|
+
const { selectionMethod } = await inquirer.prompt([
|
|
105
|
+
{
|
|
106
|
+
type: 'list',
|
|
107
|
+
name: 'selectionMethod',
|
|
108
|
+
message: 'How would you like to select projects?',
|
|
109
|
+
choices: [
|
|
110
|
+
{
|
|
111
|
+
name: `š Interactive (browse and select from ${allProjects.length} projects)`,
|
|
112
|
+
value: 'interactive',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'āļø Manual entry (type project names)',
|
|
116
|
+
value: 'manual',
|
|
117
|
+
},
|
|
118
|
+
...(allowSelectAll
|
|
119
|
+
? [
|
|
120
|
+
{
|
|
121
|
+
name: `⨠Select all (${allProjects.length} projects)`,
|
|
122
|
+
value: 'all',
|
|
123
|
+
},
|
|
124
|
+
]
|
|
125
|
+
: []),
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
]);
|
|
129
|
+
|
|
130
|
+
if (selectionMethod === 'all') {
|
|
131
|
+
return {
|
|
132
|
+
selectedNames: allProjects.map((p) => p.name),
|
|
133
|
+
method: 'all',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (selectionMethod === 'manual') {
|
|
138
|
+
return await manualProjectEntry(allProjects, minSelection, maxSelection);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Interactive selection
|
|
142
|
+
return await interactiveProjectSelection(
|
|
143
|
+
allProjects,
|
|
144
|
+
preSelected,
|
|
145
|
+
minSelection,
|
|
146
|
+
maxSelection,
|
|
147
|
+
pageSize
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Interactive project selection with checkbox
|
|
153
|
+
*/
|
|
154
|
+
async function interactiveProjectSelection(
|
|
155
|
+
allProjects: any[],
|
|
156
|
+
preSelected: string[],
|
|
157
|
+
minSelection: number,
|
|
158
|
+
maxSelection: number | undefined,
|
|
159
|
+
pageSize: number
|
|
160
|
+
): Promise<ProjectSelectionResult> {
|
|
161
|
+
console.log('š” Use <space> to select, <a> to toggle all, <i> to invert\n');
|
|
162
|
+
|
|
163
|
+
const choices = allProjects.map((p) => ({
|
|
164
|
+
name: formatProjectChoice(p),
|
|
165
|
+
value: p.name,
|
|
166
|
+
checked: preSelected.includes(p.name),
|
|
167
|
+
}));
|
|
168
|
+
|
|
169
|
+
// Add manual entry option at the end
|
|
170
|
+
choices.push(
|
|
171
|
+
new inquirer.Separator(),
|
|
172
|
+
{
|
|
173
|
+
name: 'āļø Enter project names manually instead',
|
|
174
|
+
value: '__MANUAL__',
|
|
175
|
+
checked: false,
|
|
176
|
+
} as any
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const { selectedNames } = await inquirer.prompt([
|
|
180
|
+
{
|
|
181
|
+
type: 'checkbox',
|
|
182
|
+
name: 'selectedNames',
|
|
183
|
+
message: `Select Azure DevOps projects (${minSelection}${maxSelection ? `-${maxSelection}` : '+'} required):`,
|
|
184
|
+
choices,
|
|
185
|
+
pageSize,
|
|
186
|
+
loop: false,
|
|
187
|
+
validate: (selected: string[]) => {
|
|
188
|
+
const actualSelected = selected.filter((k) => k !== '__MANUAL__');
|
|
189
|
+
|
|
190
|
+
if (actualSelected.length < minSelection) {
|
|
191
|
+
return `Please select at least ${minSelection} project(s)`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (maxSelection && actualSelected.length > maxSelection) {
|
|
195
|
+
return `Please select at most ${maxSelection} project(s)`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return true;
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
]);
|
|
202
|
+
|
|
203
|
+
// Check if user chose manual entry
|
|
204
|
+
if (selectedNames.includes('__MANUAL__')) {
|
|
205
|
+
return await manualProjectEntry(allProjects, minSelection, maxSelection);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
console.log(`\nā
Selected ${selectedNames.length} projects: ${selectedNames.join(', ')}\n`);
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
selectedNames,
|
|
212
|
+
method: 'interactive',
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Manual project name entry (comma-separated)
|
|
218
|
+
*/
|
|
219
|
+
async function manualProjectEntry(
|
|
220
|
+
allProjects: any[],
|
|
221
|
+
minSelection: number,
|
|
222
|
+
maxSelection: number | undefined
|
|
223
|
+
): Promise<ProjectSelectionResult> {
|
|
224
|
+
console.log('\nš Enter project names manually\n');
|
|
225
|
+
console.log('š” Format: Comma-separated project names (e.g., AI Meme Generator,Project 2,Project 3)\n');
|
|
226
|
+
|
|
227
|
+
if (allProjects.length > 0) {
|
|
228
|
+
console.log('Available project names:');
|
|
229
|
+
console.log(
|
|
230
|
+
allProjects
|
|
231
|
+
.map((p) => p.name)
|
|
232
|
+
.join(', ')
|
|
233
|
+
.substring(0, 100) + (allProjects.length > 20 ? '...' : '')
|
|
234
|
+
);
|
|
235
|
+
console.log('');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const { manualNames } = await inquirer.prompt([
|
|
239
|
+
{
|
|
240
|
+
type: 'input',
|
|
241
|
+
name: 'manualNames',
|
|
242
|
+
message: 'Project names:',
|
|
243
|
+
validate: (input: string) => {
|
|
244
|
+
if (!input.trim()) {
|
|
245
|
+
return 'Please enter at least one project name';
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const names = input
|
|
249
|
+
.split(',')
|
|
250
|
+
.map((n) => n.trim())
|
|
251
|
+
.filter((n) => n.length > 0);
|
|
252
|
+
|
|
253
|
+
if (names.length < minSelection) {
|
|
254
|
+
return `Please enter at least ${minSelection} project name(s)`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (maxSelection && names.length > maxSelection) {
|
|
258
|
+
return `Please enter at most ${maxSelection} project name(s)`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return true;
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
]);
|
|
265
|
+
|
|
266
|
+
const selectedNames = manualNames
|
|
267
|
+
.split(',')
|
|
268
|
+
.map((n: string) => n.trim())
|
|
269
|
+
.filter((n: string) => n.length > 0);
|
|
270
|
+
|
|
271
|
+
// Warn about unknown projects
|
|
272
|
+
const knownNames = allProjects.map((p) => p.name);
|
|
273
|
+
const unknownNames = selectedNames.filter((n) => !knownNames.includes(n));
|
|
274
|
+
|
|
275
|
+
if (unknownNames.length > 0) {
|
|
276
|
+
console.log(`\nā ļø Unknown project names (will be used anyway): ${unknownNames.join(', ')}`);
|
|
277
|
+
console.log(' Make sure these projects exist in your Azure DevOps organization.\n');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
console.log(`ā
Selected ${selectedNames.length} projects: ${selectedNames.join(', ')}\n`);
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
selectedNames,
|
|
284
|
+
method: 'manual',
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ============================================================================
|
|
289
|
+
// Helpers
|
|
290
|
+
// ============================================================================
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Format project choice for display
|
|
294
|
+
*/
|
|
295
|
+
function formatProjectChoice(project: any): string {
|
|
296
|
+
const visibility = project.visibility || 'private';
|
|
297
|
+
const state = project.state || 'wellFormed';
|
|
298
|
+
|
|
299
|
+
return `${project.name.padEnd(30)} - ${project.description || 'No description'} (${visibility}, ${state})`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Quick project selector - select single project
|
|
304
|
+
*/
|
|
305
|
+
export async function selectSingleAdoProject(
|
|
306
|
+
client: AdoClient,
|
|
307
|
+
message: string = 'Select Azure DevOps project:'
|
|
308
|
+
): Promise<string> {
|
|
309
|
+
const result = await selectAdoProjects(client, {
|
|
310
|
+
allowManualEntry: true,
|
|
311
|
+
allowSelectAll: false,
|
|
312
|
+
minSelection: 1,
|
|
313
|
+
maxSelection: 1,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
return result.selectedNames[0];
|
|
317
|
+
}
|
|
@@ -327,4 +327,32 @@ export class GitHubClient {
|
|
|
327
327
|
private sleep(ms: number): Promise<void> {
|
|
328
328
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
329
329
|
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Get all repositories accessible to the user
|
|
333
|
+
* @param owner Optional: filter by specific owner/org (e.g., 'octocat', 'my-org')
|
|
334
|
+
* @param limit Maximum number of repos to fetch (default: 100, max: 1000)
|
|
335
|
+
*/
|
|
336
|
+
static async getRepositories(owner?: string, limit: number = 100): Promise<Array<{owner: string, name: string, fullName: string}>> {
|
|
337
|
+
try {
|
|
338
|
+
const ownerFilter = owner ? `${owner}/` : '';
|
|
339
|
+
const cmd = `gh repo list ${ownerFilter} --limit ${limit} --json owner,name,nameWithOwner`;
|
|
340
|
+
|
|
341
|
+
const output = execSync(cmd, { encoding: 'utf-8' }).trim();
|
|
342
|
+
|
|
343
|
+
if (!output) {
|
|
344
|
+
return [];
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const repos = JSON.parse(output);
|
|
348
|
+
|
|
349
|
+
return repos.map((repo: any) => ({
|
|
350
|
+
owner: repo.owner?.login || '',
|
|
351
|
+
name: repo.name,
|
|
352
|
+
fullName: repo.nameWithOwner
|
|
353
|
+
}));
|
|
354
|
+
} catch (error: any) {
|
|
355
|
+
throw new Error(`Failed to fetch repositories: ${error.message}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
330
358
|
}
|