specweave 0.32.5 → 0.32.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +3 -0
  2. package/bin/specweave.js +78 -0
  3. package/dist/src/cli/commands/docs.d.ts +45 -0
  4. package/dist/src/cli/commands/docs.d.ts.map +1 -0
  5. package/dist/src/cli/commands/docs.js +307 -0
  6. package/dist/src/cli/commands/docs.js.map +1 -0
  7. package/dist/src/cli/commands/init.d.ts.map +1 -1
  8. package/dist/src/cli/commands/init.js +16 -0
  9. package/dist/src/cli/commands/init.js.map +1 -1
  10. package/dist/src/cli/helpers/init/bitbucket-repo-cloning.d.ts +42 -0
  11. package/dist/src/cli/helpers/init/bitbucket-repo-cloning.d.ts.map +1 -0
  12. package/dist/src/cli/helpers/init/bitbucket-repo-cloning.js +256 -0
  13. package/dist/src/cli/helpers/init/bitbucket-repo-cloning.js.map +1 -0
  14. package/dist/src/cli/helpers/init/github-repo-cloning.d.ts +40 -0
  15. package/dist/src/cli/helpers/init/github-repo-cloning.d.ts.map +1 -0
  16. package/dist/src/cli/helpers/init/github-repo-cloning.js +284 -0
  17. package/dist/src/cli/helpers/init/github-repo-cloning.js.map +1 -0
  18. package/dist/src/cli/helpers/init/repository-setup.d.ts +38 -2
  19. package/dist/src/cli/helpers/init/repository-setup.d.ts.map +1 -1
  20. package/dist/src/cli/helpers/init/repository-setup.js +485 -187
  21. package/dist/src/cli/helpers/init/repository-setup.js.map +1 -1
  22. package/dist/src/cli/helpers/issue-tracker/github.d.ts +35 -0
  23. package/dist/src/cli/helpers/issue-tracker/github.d.ts.map +1 -1
  24. package/dist/src/cli/helpers/issue-tracker/github.js +203 -0
  25. package/dist/src/cli/helpers/issue-tracker/github.js.map +1 -1
  26. package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
  27. package/dist/src/cli/helpers/issue-tracker/index.js +127 -13
  28. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  29. package/dist/src/cli/helpers/issue-tracker/jira.d.ts +18 -2
  30. package/dist/src/cli/helpers/issue-tracker/jira.d.ts.map +1 -1
  31. package/dist/src/cli/helpers/issue-tracker/jira.js +331 -32
  32. package/dist/src/cli/helpers/issue-tracker/jira.js.map +1 -1
  33. package/dist/src/cli/helpers/issue-tracker/sync-config-writer.d.ts.map +1 -1
  34. package/dist/src/cli/helpers/issue-tracker/sync-config-writer.js +61 -1
  35. package/dist/src/cli/helpers/issue-tracker/sync-config-writer.js.map +1 -1
  36. package/dist/src/cli/helpers/issue-tracker/types.d.ts +25 -0
  37. package/dist/src/cli/helpers/issue-tracker/types.d.ts.map +1 -1
  38. package/dist/src/cli/helpers/issue-tracker/types.js.map +1 -1
  39. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js +1 -1
  40. package/dist/src/utils/docs-preview/config-generator.d.ts.map +1 -1
  41. package/dist/src/utils/docs-preview/config-generator.js +12 -16
  42. package/dist/src/utils/docs-preview/config-generator.js.map +1 -1
  43. package/dist/src/utils/docs-preview/docusaurus-setup.d.ts.map +1 -1
  44. package/dist/src/utils/docs-preview/docusaurus-setup.js +24 -5
  45. package/dist/src/utils/docs-preview/docusaurus-setup.js.map +1 -1
  46. package/dist/src/utils/docs-preview/package-installer.d.ts +3 -0
  47. package/dist/src/utils/docs-preview/package-installer.d.ts.map +1 -1
  48. package/dist/src/utils/docs-preview/package-installer.js +18 -9
  49. package/dist/src/utils/docs-preview/package-installer.js.map +1 -1
  50. package/dist/src/utils/docs-preview/server-manager.d.ts.map +1 -1
  51. package/dist/src/utils/docs-preview/server-manager.js +2 -1
  52. package/dist/src/utils/docs-preview/server-manager.js.map +1 -1
  53. package/dist/src/utils/validators/jira-validator.d.ts +37 -1
  54. package/dist/src/utils/validators/jira-validator.d.ts.map +1 -1
  55. package/dist/src/utils/validators/jira-validator.js +289 -183
  56. package/dist/src/utils/validators/jira-validator.js.map +1 -1
  57. package/package.json +3 -3
  58. package/plugins/specweave/commands/specweave-done.md +34 -0
  59. package/plugins/specweave/commands/specweave-increment.md +13 -5
  60. package/plugins/specweave/hooks/v2/detectors/lifecycle-detector.sh +3 -1
  61. package/plugins/specweave/hooks/docs-changed.sh.backup +0 -79
  62. package/plugins/specweave/hooks/human-input-required.sh.backup +0 -75
  63. package/plugins/specweave/hooks/post-first-increment.sh.backup +0 -61
  64. package/plugins/specweave/hooks/post-increment-change.sh.backup +0 -98
  65. package/plugins/specweave/hooks/post-increment-completion.sh.backup +0 -231
  66. package/plugins/specweave/hooks/post-increment-planning.sh.backup +0 -1048
  67. package/plugins/specweave/hooks/post-increment-status-change.sh.backup +0 -147
  68. package/plugins/specweave/hooks/post-spec-update.sh.backup +0 -158
  69. package/plugins/specweave/hooks/post-user-story-complete.sh.backup +0 -179
  70. package/plugins/specweave/hooks/pre-command-deduplication.sh.backup +0 -83
  71. package/plugins/specweave/hooks/pre-implementation.sh.backup +0 -67
  72. package/plugins/specweave/hooks/pre-task-completion.sh.backup +0 -194
  73. package/plugins/specweave/hooks/pre-tool-use.sh.backup +0 -133
  74. package/plugins/specweave/hooks/user-prompt-submit.sh.backup +0 -386
  75. package/plugins/specweave-ado/hooks/post-living-docs-update.sh.backup +0 -353
  76. package/plugins/specweave-ado/hooks/post-task-completion.sh.backup +0 -172
  77. package/plugins/specweave-ado/lib/enhanced-ado-sync.js +0 -170
  78. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +0 -1262
  79. package/plugins/specweave-github/hooks/post-task-completion.sh.backup +0 -258
  80. package/plugins/specweave-github/lib/enhanced-github-sync.js +0 -220
  81. package/plugins/specweave-jira/hooks/post-task-completion.sh.backup +0 -172
  82. package/plugins/specweave-jira/lib/enhanced-jira-sync.js +0 -134
  83. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +0 -1254
  84. package/plugins/specweave-release/hooks/post-task-completion.sh.backup +0 -110
@@ -56,6 +56,212 @@ async function safeParseJsonResponse(response, context) {
56
56
  ` Response preview: ${preview}${responseText.length > 100 ? '...' : ''}`);
57
57
  }
58
58
  }
59
+ /**
60
+ * Fetch all accessible projects for an ADO organization
61
+ */
62
+ async function fetchAdoProjects(org, pat) {
63
+ const auth = Buffer.from(`:${pat}`).toString('base64');
64
+ const endpoint = `https://dev.azure.com/${org}/_apis/projects?api-version=7.0`;
65
+ const response = await fetch(endpoint, {
66
+ headers: { 'Authorization': `Basic ${auth}`, 'Accept': 'application/json' }
67
+ });
68
+ if (!response.ok) {
69
+ // Read response body to check for HTML error pages
70
+ const errorBody = await response.text();
71
+ const looksLikeHtml = errorBody.trim().startsWith('<!DOCTYPE') || errorBody.trim().startsWith('<html');
72
+ if (looksLikeHtml) {
73
+ throw new Error(`Azure DevOps returned an error page (HTTP ${response.status}).\n` +
74
+ ` This usually indicates an authentication or configuration issue.\n` +
75
+ ` \n` +
76
+ ` Possible causes:\n` +
77
+ ` • Invalid or expired Personal Access Token (PAT)\n` +
78
+ ` • Incorrect organization name "${org}"\n` +
79
+ ` • Corporate firewall or proxy intercepting the request\n` +
80
+ ` • SSO/authentication redirect (try accessing Azure DevOps in browser first)`);
81
+ }
82
+ throw new Error(`Failed to fetch projects: ${response.status} - ${errorBody.substring(0, 200)}`);
83
+ }
84
+ // Use safe JSON parser to detect HTML responses even on 200 OK
85
+ const data = await safeParseJsonResponse(response, 'Azure DevOps');
86
+ return (data.value || []).map((p) => ({ name: p.name, id: p.id }));
87
+ }
88
+ /**
89
+ * Prompt user for ADO organization, credentials, and project selection
90
+ * For repository cloning in multi-repo setups
91
+ */
92
+ async function promptAdoProjectSelection(targetDir, strings) {
93
+ // Check for existing credentials in .env or environment
94
+ let existingOrg;
95
+ let existingPat;
96
+ const envContent = readEnvFile(targetDir);
97
+ if (envContent) {
98
+ const parsed = parseEnvFile(envContent);
99
+ existingOrg = parsed.AZURE_DEVOPS_ORG;
100
+ existingPat = parsed.AZURE_DEVOPS_PAT;
101
+ }
102
+ // Fall back to environment variables
103
+ if (!existingOrg || !existingPat) {
104
+ const auth = getAzureDevOpsAuth();
105
+ if (auth) {
106
+ existingOrg = existingOrg || auth.org;
107
+ existingPat = existingPat || auth.pat;
108
+ }
109
+ }
110
+ // Prompt for organization
111
+ const org = await input({
112
+ message: strings.adoOrgPrompt,
113
+ default: existingOrg,
114
+ validate: (value) => {
115
+ if (!value.trim())
116
+ return 'Organization name is required';
117
+ return true;
118
+ }
119
+ });
120
+ // Prompt for PAT
121
+ const pat = await password({
122
+ message: strings.adoPatPrompt,
123
+ mask: true,
124
+ validate: (value) => {
125
+ if (!value.trim())
126
+ return 'Personal Access Token is required';
127
+ return true;
128
+ }
129
+ });
130
+ // Fetch projects
131
+ const spinner = ora(strings.adoFetchingProjects).start();
132
+ let projects;
133
+ try {
134
+ projects = await fetchAdoProjects(org, pat);
135
+ spinner.succeed();
136
+ }
137
+ catch (error) {
138
+ spinner.fail(strings.adoConnectionFailed);
139
+ console.log(chalk.red(` ${error instanceof Error ? error.message : 'Unknown error'}`));
140
+ return null;
141
+ }
142
+ if (projects.length === 0) {
143
+ console.log(chalk.yellow(` ${strings.adoNoProjects}`));
144
+ return null;
145
+ }
146
+ // Let user select projects
147
+ const selectedProjects = await checkbox({
148
+ message: strings.adoProjectPrompt,
149
+ choices: projects.map((p, index) => ({
150
+ name: p.name,
151
+ value: p.name,
152
+ checked: index === 0 // First project selected by default
153
+ })),
154
+ required: true
155
+ });
156
+ console.log(chalk.green(` āœ“ ${strings.adoProjectsSelected.replace('{count}', String(selectedProjects.length))}`));
157
+ return {
158
+ org,
159
+ pat,
160
+ projects: selectedProjects
161
+ };
162
+ }
163
+ /**
164
+ * Prompt user for GitHub organization and PAT for multi-repo cloning
165
+ *
166
+ * @param targetDir - Target directory (for reading existing .env)
167
+ * @param gitHubRemote - Detected GitHub remote (provides default org)
168
+ * @param strings - Translated strings
169
+ * @returns GitHub selection or null if cancelled
170
+ */
171
+ async function promptGitHubRepoSelection(targetDir, gitHubRemote, strings) {
172
+ // Check for existing credentials in .env or environment
173
+ let existingOrg;
174
+ let existingPat;
175
+ const envContent = readEnvFile(targetDir);
176
+ if (envContent) {
177
+ const parsed = parseEnvFile(envContent);
178
+ existingOrg = parsed.GITHUB_ORG;
179
+ existingPat = parsed.GITHUB_PAT || parsed.GITHUB_TOKEN;
180
+ }
181
+ // Fall back to environment variables
182
+ existingOrg = existingOrg || process.env.GITHUB_ORG;
183
+ existingPat = existingPat || process.env.GITHUB_PAT || process.env.GITHUB_TOKEN;
184
+ // Use detected remote owner as default
185
+ const defaultOrg = existingOrg || gitHubRemote?.owner;
186
+ // Prompt for organization
187
+ const org = await input({
188
+ message: strings.githubOrgPrompt,
189
+ default: defaultOrg,
190
+ validate: (value) => {
191
+ if (!value.trim())
192
+ return 'Organization or username is required';
193
+ return true;
194
+ }
195
+ });
196
+ // Prompt for PAT
197
+ const pat = await password({
198
+ message: strings.githubPatPrompt,
199
+ mask: true,
200
+ validate: (value) => {
201
+ if (!value.trim())
202
+ return 'Personal Access Token is required';
203
+ return true;
204
+ }
205
+ });
206
+ console.log(chalk.green(` āœ“ ${strings.githubSelected.replace('{org}', org)}`));
207
+ return { org, pat };
208
+ }
209
+ /**
210
+ * Prompt user for Bitbucket workspace and credentials for multi-repo cloning
211
+ *
212
+ * @param targetDir - Target directory (for reading existing .env)
213
+ * @param strings - Translated strings
214
+ * @returns Bitbucket selection or null if cancelled
215
+ */
216
+ async function promptBitbucketRepoSelection(targetDir, strings) {
217
+ // Check for existing credentials in .env or environment
218
+ let existingWorkspace;
219
+ let existingUsername;
220
+ let existingAppPassword;
221
+ const envContent = readEnvFile(targetDir);
222
+ if (envContent) {
223
+ const parsed = parseEnvFile(envContent);
224
+ existingWorkspace = parsed.BITBUCKET_WORKSPACE;
225
+ existingUsername = parsed.BITBUCKET_USERNAME;
226
+ existingAppPassword = parsed.BITBUCKET_APP_PASSWORD;
227
+ }
228
+ // Fall back to environment variables
229
+ existingWorkspace = existingWorkspace || process.env.BITBUCKET_WORKSPACE;
230
+ existingUsername = existingUsername || process.env.BITBUCKET_USERNAME;
231
+ existingAppPassword = existingAppPassword || process.env.BITBUCKET_APP_PASSWORD;
232
+ // Prompt for workspace
233
+ const workspace = await input({
234
+ message: strings.bitbucketWorkspacePrompt,
235
+ default: existingWorkspace,
236
+ validate: (value) => {
237
+ if (!value.trim())
238
+ return 'Workspace slug is required';
239
+ return true;
240
+ }
241
+ });
242
+ // Prompt for username
243
+ const username = await input({
244
+ message: strings.bitbucketUsernamePrompt,
245
+ default: existingUsername,
246
+ validate: (value) => {
247
+ if (!value.trim())
248
+ return 'Username is required';
249
+ return true;
250
+ }
251
+ });
252
+ // Prompt for app password
253
+ const appPassword = await password({
254
+ message: strings.bitbucketAppPasswordPrompt,
255
+ mask: true,
256
+ validate: (value) => {
257
+ if (!value.trim())
258
+ return 'App Password is required';
259
+ return true;
260
+ }
261
+ });
262
+ console.log(chalk.green(` āœ“ ${strings.bitbucketSelected.replace('{workspace}', workspace)}`));
263
+ return { workspace, username, appPassword };
264
+ }
59
265
  /**
60
266
  * Get translated strings for repository setup
61
267
  */
@@ -77,7 +283,7 @@ function getRepoStrings(language) {
77
283
  other: 'Other (GitLab, etc - coming soon)',
78
284
  adoCloneQuestion: 'Enter repo name pattern to clone (e.g., sw-* or leave empty to skip):',
79
285
  adoCloneSkip: 'Skipping repo cloning - you can configure later',
80
- // New unified selection strings
286
+ // Unified selection strings
81
287
  adoSelectStrategy: 'How do you want to select repositories to clone?',
82
288
  adoAllRepos: 'All',
83
289
  adoAllReposDesc: 'Clone all repositories from the project',
@@ -105,6 +311,21 @@ function getRepoStrings(language) {
105
311
  adoFetchingProjects: 'Fetching projects...',
106
312
  adoNoProjects: 'No projects found in this organization',
107
313
  adoConnectionFailed: 'Failed to connect to Azure DevOps',
314
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
315
+ githubMultiRepoHeader: 'šŸ“ GitHub Multi-Repository Selection',
316
+ githubMultiRepoDesc: 'Select which repositories to work with from your GitHub organization.',
317
+ githubSelectStrategy: 'How do you want to select GitHub repositories?',
318
+ bitbucketMultiRepoHeader: 'šŸ“ Bitbucket Multi-Repository Selection',
319
+ bitbucketMultiRepoDesc: 'Select which repositories to work with from your Bitbucket workspace.',
320
+ bitbucketSelectStrategy: 'How do you want to select Bitbucket repositories?',
321
+ // GitHub/Bitbucket credential prompts (v0.32.7+)
322
+ githubOrgPrompt: 'GitHub organization or username:',
323
+ githubPatPrompt: 'GitHub Personal Access Token (PAT):',
324
+ githubSelected: 'GitHub organization "{org}" configured for cloning',
325
+ bitbucketWorkspacePrompt: 'Bitbucket workspace slug:',
326
+ bitbucketUsernamePrompt: 'Bitbucket username:',
327
+ bitbucketAppPasswordPrompt: 'Bitbucket App Password:',
328
+ bitbucketSelected: 'Bitbucket workspace "{workspace}" configured for cloning',
108
329
  },
109
330
  ru: {
110
331
  header: 'šŸ“¦ Єостинг Ń€ŠµŠæŠ¾Š·ŠøŃ‚Š¾Ń€ŠøŃ',
@@ -122,7 +343,7 @@ function getRepoStrings(language) {
122
343
  other: 'Š”Ń€ŃƒŠ³Š¾Š¹ (GitLab Šø т.Š“. - скоро)',
123
344
  adoCloneQuestion: 'ВвеГите шаблон имени репо Š“Š»Ń ŠŗŠ»Š¾Š½ŠøŃ€Š¾Š²Š°Š½ŠøŃ (напр., sw-* или Š¾ŃŃ‚Š°Š²ŃŒŃ‚Šµ ŠæŃƒŃŃ‚Ń‹Š¼):',
124
345
  adoCloneSkip: 'ŠŸŃ€Š¾ŠæŃƒŃŠŗ ŠŗŠ»Š¾Š½ŠøŃ€Š¾Š²Š°Š½ŠøŃ - можно Š½Š°ŃŃ‚Ń€Š¾ŠøŃ‚ŃŒ позже',
125
- // New unified selection strings
346
+ // Unified selection strings
126
347
  adoSelectStrategy: 'Как вы хотите Š²Ń‹Š±Ń€Š°Ń‚ŃŒ репозитории Š“Š»Ń ŠŗŠ»Š¾Š½ŠøŃ€Š¾Š²Š°Š½ŠøŃ?',
127
348
  adoAllRepos: 'Все',
128
349
  adoAllReposDesc: 'ŠšŠ»Š¾Š½ŠøŃ€Š¾Š²Š°Ń‚ŃŒ все репозитории ŠøŠ· проекта',
@@ -150,6 +371,20 @@ function getRepoStrings(language) {
150
371
  adoFetchingProjects: 'Š—Š°Š³Ń€ŃƒŠ·ŠŗŠ° проектов...',
151
372
  adoNoProjects: 'ŠŸŃ€Š¾ŠµŠŗŃ‚Ń‹ не найГены в ŃŃ‚Š¾Š¹ организации',
152
373
  adoConnectionFailed: 'ŠŠµ уГалось ŠæŠ¾Š“ŠŗŠ»ŃŽŃ‡ŠøŃ‚ŃŒŃŃ Šŗ Azure DevOps',
374
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
375
+ githubMultiRepoHeader: 'šŸ“ Выбор репозиториев GitHub',
376
+ githubMultiRepoDesc: 'Выберите репозитории Š“Š»Ń работы ŠøŠ· вашей организации GitHub.',
377
+ githubSelectStrategy: 'Как вы хотите Š²Ń‹Š±Ń€Š°Ń‚ŃŒ репозитории GitHub?',
378
+ bitbucketMultiRepoHeader: 'šŸ“ Выбор репозиториев Bitbucket',
379
+ bitbucketMultiRepoDesc: 'Выберите репозитории Š“Š»Ń работы ŠøŠ· вашего рабочего пространства Bitbucket.',
380
+ bitbucketSelectStrategy: 'Как вы хотите Š²Ń‹Š±Ń€Š°Ń‚ŃŒ репозитории Bitbucket?',
381
+ githubOrgPrompt: 'ŠžŃ€Š³Š°Š½ŠøŠ·Š°Ń†ŠøŃ или ŠøŠ¼Ń ŠæŠ¾Š»ŃŒŠ·Š¾Š²Š°Ń‚ŠµŠ»Ń GitHub:',
382
+ githubPatPrompt: 'Personal Access Token (PAT) GitHub:',
383
+ githubSelected: 'ŠžŃ€Š³Š°Š½ŠøŠ·Š°Ń†ŠøŃ GitHub "{org}" настроена Š“Š»Ń ŠŗŠ»Š¾Š½ŠøŃ€Š¾Š²Š°Š½ŠøŃ',
384
+ bitbucketWorkspacePrompt: 'Workspace Bitbucket:',
385
+ bitbucketUsernamePrompt: 'Š˜Š¼Ń ŠæŠ¾Š»ŃŒŠ·Š¾Š²Š°Ń‚ŠµŠ»Ń Bitbucket:',
386
+ bitbucketAppPasswordPrompt: 'App Password Bitbucket:',
387
+ bitbucketSelected: 'Workspace Bitbucket "{workspace}" настроен Š“Š»Ń ŠŗŠ»Š¾Š½ŠøŃ€Š¾Š²Š°Š½ŠøŃ',
153
388
  },
154
389
  es: {
155
390
  header: 'šŸ“¦ Alojamiento del repositorio',
@@ -167,7 +402,7 @@ function getRepoStrings(language) {
167
402
  other: 'Otro (GitLab, etc - próximamente)',
168
403
  adoCloneQuestion: 'Ingrese patrón de nombre de repo a clonar (ej., sw-* o deje vacĆ­o):',
169
404
  adoCloneSkip: 'Omitiendo clonación - puede configurar despuĆ©s',
170
- // New unified selection strings
405
+ // Unified selection strings
171
406
  adoSelectStrategy: 'ĀæCómo quiere seleccionar repositorios para clonar?',
172
407
  adoAllRepos: 'Todos',
173
408
  adoAllReposDesc: 'Clonar todos los repositorios del proyecto',
@@ -195,6 +430,20 @@ function getRepoStrings(language) {
195
430
  adoFetchingProjects: 'Obteniendo proyectos...',
196
431
  adoNoProjects: 'No se encontraron proyectos en esta organización',
197
432
  adoConnectionFailed: 'Error al conectar con Azure DevOps',
433
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
434
+ githubMultiRepoHeader: 'šŸ“ Selección de repositorios GitHub',
435
+ githubMultiRepoDesc: 'Seleccione los repositorios de su organización GitHub.',
436
+ githubSelectStrategy: '¿Cómo desea seleccionar los repositorios de GitHub?',
437
+ bitbucketMultiRepoHeader: 'šŸ“ Selección de repositorios Bitbucket',
438
+ bitbucketMultiRepoDesc: 'Seleccione los repositorios de su espacio de trabajo Bitbucket.',
439
+ bitbucketSelectStrategy: '¿Cómo desea seleccionar los repositorios de Bitbucket?',
440
+ githubOrgPrompt: 'Organización o nombre de usuario de GitHub:',
441
+ githubPatPrompt: 'Token de acceso personal (PAT) de GitHub:',
442
+ githubSelected: 'Organización de GitHub "{org}" configurada para clonar',
443
+ bitbucketWorkspacePrompt: 'Workspace de Bitbucket:',
444
+ bitbucketUsernamePrompt: 'Nombre de usuario de Bitbucket:',
445
+ bitbucketAppPasswordPrompt: 'Contraseña de aplicación de Bitbucket:',
446
+ bitbucketSelected: 'Workspace de Bitbucket "{workspace}" configurado para clonar',
198
447
  },
199
448
  zh: {
200
449
  header: 'šŸ“¦ ä»“åŗ“ę‰˜ē®”',
@@ -212,7 +461,7 @@ function getRepoStrings(language) {
212
461
  other: 'å…¶ä»–ļ¼ˆGitLab ē­‰ - å³å°†ęŽØå‡ŗļ¼‰',
213
462
  adoCloneQuestion: 'č¾“å…„č¦å…‹éš†ēš„ä»“åŗ“åē§°ęØ”å¼ļ¼ˆä¾‹å¦‚ sw-* ęˆ–ē•™ē©ŗč·³čæ‡ļ¼‰ļ¼š',
214
463
  adoCloneSkip: 'č·³čæ‡å…‹éš† - ēØåŽåÆä»„é…ē½®',
215
- // New unified selection strings
464
+ // Unified selection strings
216
465
  adoSelectStrategy: 'ę‚Øęƒ³å¦‚ä½•é€‰ę‹©č¦å…‹éš†ēš„ä»“åŗ“ļ¼Ÿ',
217
466
  adoAllRepos: 'å…ØéƒØ',
218
467
  adoAllReposDesc: 'å…‹éš†é”¹ē›®äø­ēš„ę‰€ęœ‰ä»“åŗ“',
@@ -240,6 +489,20 @@ function getRepoStrings(language) {
240
489
  adoFetchingProjects: 'ę­£åœØčŽ·å–é”¹ē›®...',
241
490
  adoNoProjects: 'åœØę­¤ē»„ē»‡äø­ęœŖę‰¾åˆ°é”¹ē›®',
242
491
  adoConnectionFailed: 'ę— ę³•čæžęŽ„åˆ° Azure DevOps',
492
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
493
+ githubMultiRepoHeader: 'šŸ“ GitHub å¤šä»“åŗ“é€‰ę‹©',
494
+ githubMultiRepoDesc: 'ä»Žę‚Øēš„ GitHub ē»„ē»‡äø­é€‰ę‹©č¦ä½æē”Øēš„ä»“åŗ“ć€‚',
495
+ githubSelectStrategy: 'ę‚Øęƒ³å¦‚ä½•é€‰ę‹© GitHub ä»“åŗ“ļ¼Ÿ',
496
+ bitbucketMultiRepoHeader: 'šŸ“ Bitbucket å¤šä»“åŗ“é€‰ę‹©',
497
+ bitbucketMultiRepoDesc: 'ä»Žę‚Øēš„ Bitbucket å·„ä½œåŒŗäø­é€‰ę‹©č¦ä½æē”Øēš„ä»“åŗ“ć€‚',
498
+ bitbucketSelectStrategy: 'ę‚Øęƒ³å¦‚ä½•é€‰ę‹© Bitbucket ä»“åŗ“ļ¼Ÿ',
499
+ githubOrgPrompt: 'GitHub ē»„ē»‡ęˆ–ē”Øęˆ·åļ¼š',
500
+ githubPatPrompt: 'GitHub äøŖäŗŗč®æé—®ä»¤ē‰Œ (PAT):',
501
+ githubSelected: 'GitHub 组织 "{org}" å·²é…ē½®ē”ØäŗŽå…‹éš†',
502
+ bitbucketWorkspacePrompt: 'Bitbucket 巄作区:',
503
+ bitbucketUsernamePrompt: 'Bitbucket ē”Øęˆ·åļ¼š',
504
+ bitbucketAppPasswordPrompt: 'Bitbucket åŗ”ē”ØåÆ†ē ļ¼š',
505
+ bitbucketSelected: 'Bitbucket 巄作区 "{workspace}" å·²é…ē½®ē”ØäŗŽå…‹éš†',
243
506
  },
244
507
  de: {
245
508
  header: 'šŸ“¦ Repository-Hosting',
@@ -257,7 +520,7 @@ function getRepoStrings(language) {
257
520
  other: 'Andere (GitLab, etc - kommt bald)',
258
521
  adoCloneQuestion: 'Repo-Namensmuster zum Klonen eingeben (z.B. sw-* oder leer lassen):',
259
522
  adoCloneSkip: 'Klonen übersprungen - spƤter konfigurierbar',
260
- // New unified selection strings
523
+ // Unified selection strings
261
524
  adoSelectStrategy: 'Wie mƶchten Sie Repositories zum Klonen auswƤhlen?',
262
525
  adoAllRepos: 'Alle',
263
526
  adoAllReposDesc: 'Alle Repositories aus dem Projekt klonen',
@@ -285,6 +548,20 @@ function getRepoStrings(language) {
285
548
  adoFetchingProjects: 'Projekte werden abgerufen...',
286
549
  adoNoProjects: 'Keine Projekte in dieser Organisation gefunden',
287
550
  adoConnectionFailed: 'Verbindung zu Azure DevOps fehlgeschlagen',
551
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
552
+ githubMultiRepoHeader: 'šŸ“ GitHub Multi-Repository Auswahl',
553
+ githubMultiRepoDesc: 'WƤhlen Sie die Repositories aus Ihrer GitHub-Organisation.',
554
+ githubSelectStrategy: 'Wie mƶchten Sie GitHub-Repositories auswƤhlen?',
555
+ bitbucketMultiRepoHeader: 'šŸ“ Bitbucket Multi-Repository Auswahl',
556
+ bitbucketMultiRepoDesc: 'WƤhlen Sie die Repositories aus Ihrem Bitbucket-Workspace.',
557
+ bitbucketSelectStrategy: 'Wie mƶchten Sie Bitbucket-Repositories auswƤhlen?',
558
+ githubOrgPrompt: 'GitHub-Organisation oder Benutzername:',
559
+ githubPatPrompt: 'GitHub Personal Access Token (PAT):',
560
+ githubSelected: 'GitHub-Organisation "{org}" für Klonen konfiguriert',
561
+ bitbucketWorkspacePrompt: 'Bitbucket-Workspace:',
562
+ bitbucketUsernamePrompt: 'Bitbucket-Benutzername:',
563
+ bitbucketAppPasswordPrompt: 'Bitbucket-App-Passwort:',
564
+ bitbucketSelected: 'Bitbucket-Workspace "{workspace}" für Klonen konfiguriert',
288
565
  },
289
566
  fr: {
290
567
  header: 'šŸ“¦ HĆ©bergement du dĆ©pĆ“t',
@@ -302,7 +579,7 @@ function getRepoStrings(language) {
302
579
  other: 'Autre (GitLab, etc - bientĆ“t)',
303
580
  adoCloneQuestion: 'Entrez le modĆØle de nom de repo Ć  cloner (ex. sw-* ou laissez vide):',
304
581
  adoCloneSkip: 'Clonage ignorĆ© - configurable plus tard',
305
- // New unified selection strings
582
+ // Unified selection strings
306
583
  adoSelectStrategy: 'Comment voulez-vous sĆ©lectionner les repos Ć  cloner?',
307
584
  adoAllRepos: 'Tous',
308
585
  adoAllReposDesc: 'Cloner tous les repos du projet',
@@ -330,6 +607,20 @@ function getRepoStrings(language) {
330
607
  adoFetchingProjects: 'RĆ©cupĆ©ration des projets...',
331
608
  adoNoProjects: 'Aucun projet trouvĆ© dans cette organisation',
332
609
  adoConnectionFailed: 'Ɖchec de connexion Ć  Azure DevOps',
610
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
611
+ githubMultiRepoHeader: 'šŸ“ SĆ©lection multi-dĆ©pĆ“t GitHub',
612
+ githubMultiRepoDesc: 'SƩlectionnez les dƩpƓts de votre organisation GitHub.',
613
+ githubSelectStrategy: 'Comment voulez-vous sƩlectionner les dƩpƓts GitHub?',
614
+ bitbucketMultiRepoHeader: 'šŸ“ SĆ©lection multi-dĆ©pĆ“t Bitbucket',
615
+ bitbucketMultiRepoDesc: 'SƩlectionnez les dƩpƓts de votre espace de travail Bitbucket.',
616
+ bitbucketSelectStrategy: 'Comment voulez-vous sƩlectionner les dƩpƓts Bitbucket?',
617
+ githubOrgPrompt: 'Organisation ou nom d\'utilisateur GitHub :',
618
+ githubPatPrompt: 'Token d\'accĆØs personnel GitHub (PAT) :',
619
+ githubSelected: 'Organisation GitHub "{org}" configurƩe pour le clonage',
620
+ bitbucketWorkspacePrompt: 'Workspace Bitbucket :',
621
+ bitbucketUsernamePrompt: 'Nom d\'utilisateur Bitbucket :',
622
+ bitbucketAppPasswordPrompt: 'Mot de passe d\'application Bitbucket :',
623
+ bitbucketSelected: 'Workspace Bitbucket "{workspace}" configurƩ pour le clonage',
333
624
  },
334
625
  ja: {
335
626
  header: 'šŸ“¦ ćƒŖćƒć‚øćƒˆćƒŖćƒ›ć‚¹ćƒ†ć‚£ćƒ³ć‚°',
@@ -347,7 +638,7 @@ function getRepoStrings(language) {
347
638
  other: 'ćć®ä»–ļ¼ˆGitLabなど - 近旄公開)',
348
639
  adoCloneQuestion: 'ć‚Æćƒ­ćƒ¼ćƒ³ć™ć‚‹ćƒŖćƒåćƒ‘ć‚æćƒ¼ćƒ³ć‚’å…„åŠ›ļ¼ˆä¾‹: sw-* ć¾ćŸćÆē©ŗē™½ć§ć‚¹ć‚­ćƒƒćƒ—ļ¼‰:',
349
640
  adoCloneSkip: 'ć‚Æćƒ­ćƒ¼ćƒ³ć‚’ć‚¹ć‚­ćƒƒćƒ— - å¾Œć§čØ­å®šåÆčƒ½',
350
- // New unified selection strings
641
+ // Unified selection strings
351
642
  adoSelectStrategy: 'ć‚Æćƒ­ćƒ¼ćƒ³ć™ć‚‹ćƒŖćƒć‚øćƒˆćƒŖć‚’ć©ć®ć‚ˆć†ć«éøęŠžć—ć¾ć™ć‹ļ¼Ÿ',
352
643
  adoAllRepos: 'すべて',
353
644
  adoAllReposDesc: 'ćƒ—ćƒ­ć‚øć‚§ć‚Æćƒˆć®ć™ć¹ć¦ć®ćƒŖćƒć‚øćƒˆćƒŖć‚’ć‚Æćƒ­ćƒ¼ćƒ³',
@@ -375,6 +666,20 @@ function getRepoStrings(language) {
375
666
  adoFetchingProjects: 'ćƒ—ćƒ­ć‚øć‚§ć‚Æćƒˆć‚’å–å¾—äø­...',
376
667
  adoNoProjects: 'ć“ć®ēµ„ē¹”ć«ćƒ—ćƒ­ć‚øć‚§ć‚ÆćƒˆćŒč¦‹ć¤ć‹ć‚Šć¾ć›ć‚“',
377
668
  adoConnectionFailed: 'Azure DevOps ćøć®ęŽ„ē¶šć«å¤±ę•—ć—ć¾ć—ćŸ',
669
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
670
+ githubMultiRepoHeader: 'šŸ“ GitHub ćƒžćƒ«ćƒćƒŖćƒć‚øćƒˆćƒŖéøęŠž',
671
+ githubMultiRepoDesc: 'GitHubēµ„ē¹”ć‹ć‚‰ćƒŖćƒć‚øćƒˆćƒŖć‚’éøęŠžć—ć¦ćć ć•ć„ć€‚',
672
+ githubSelectStrategy: 'GitHubćƒŖćƒć‚øćƒˆćƒŖć‚’ć©ć®ć‚ˆć†ć«éøęŠžć—ć¾ć™ć‹ļ¼Ÿ',
673
+ bitbucketMultiRepoHeader: 'šŸ“ Bitbucket ćƒžćƒ«ćƒćƒŖćƒć‚øćƒˆćƒŖéøęŠž',
674
+ bitbucketMultiRepoDesc: 'BitbucketćƒÆćƒ¼ć‚Æć‚¹ćƒšćƒ¼ć‚¹ć‹ć‚‰ćƒŖćƒć‚øćƒˆćƒŖć‚’éøęŠžć—ć¦ćć ć•ć„ć€‚',
675
+ bitbucketSelectStrategy: 'BitbucketćƒŖćƒć‚øćƒˆćƒŖć‚’ć©ć®ć‚ˆć†ć«éøęŠžć—ć¾ć™ć‹ļ¼Ÿ',
676
+ githubOrgPrompt: 'GitHubēµ„ē¹”ć¾ćŸćÆćƒ¦ćƒ¼ć‚¶ćƒ¼åļ¼š',
677
+ githubPatPrompt: 'GitHub Personal Access Token (PAT):',
678
+ githubSelected: 'GitHub組織 "{org}" ćŒć‚Æćƒ­ćƒ¼ćƒ³ē”Øć«čØ­å®šć•ć‚Œć¾ć—ćŸ',
679
+ bitbucketWorkspacePrompt: 'BitbucketćƒÆćƒ¼ć‚Æć‚¹ćƒšćƒ¼ć‚¹ļ¼š',
680
+ bitbucketUsernamePrompt: 'Bitbucketćƒ¦ćƒ¼ć‚¶ćƒ¼åļ¼š',
681
+ bitbucketAppPasswordPrompt: 'Bitbucketć‚¢ćƒ—ćƒŖćƒ‘ć‚¹ćƒÆćƒ¼ćƒ‰ļ¼š',
682
+ bitbucketSelected: 'BitbucketćƒÆćƒ¼ć‚Æć‚¹ćƒšćƒ¼ć‚¹ "{workspace}" ćŒć‚Æćƒ­ćƒ¼ćƒ³ē”Øć«čØ­å®šć•ć‚Œć¾ć—ćŸ',
378
683
  },
379
684
  ko: {
380
685
  header: 'šŸ“¦ ģ €ģž„ģ†Œ ķ˜øģŠ¤ķŒ…',
@@ -392,7 +697,7 @@ function getRepoStrings(language) {
392
697
  other: 'źø°ķƒ€ (GitLab 등 - ź³§ ģ¶œģ‹œ)',
393
698
  adoCloneQuestion: 'ė³µģ œķ•  ģ €ģž„ģ†Œ ģ“ė¦„ ķŒØķ„“ ģž…ė „ (예: sw-* ė˜ėŠ” ė¹„ģ›Œė‘źø°):',
394
699
  adoCloneSkip: '복제 ź±“ė„ˆėœ€ - ė‚˜ģ¤‘ģ— 설정 ź°€ėŠ„',
395
- // New unified selection strings
700
+ // Unified selection strings
396
701
  adoSelectStrategy: 'ė³µģ œķ•  ģ €ģž„ģ†Œė„¼ ģ–“ė–»ź²Œ ģ„ ķƒķ•˜ģ‹œź² ģŠµė‹ˆź¹Œ?',
397
702
  adoAllRepos: '모두',
398
703
  adoAllReposDesc: 'ķ”„ė”œģ ķŠøģ˜ ėŖØė“  ģ €ģž„ģ†Œ 복제',
@@ -420,6 +725,20 @@ function getRepoStrings(language) {
420
725
  adoFetchingProjects: 'ķ”„ė”œģ ķŠø ź°€ģ øģ˜¤ėŠ” 중...',
421
726
  adoNoProjects: 'ģ“ ģ”°ģ§ģ—ģ„œ ķ”„ė”œģ ķŠøė„¼ ģ°¾ģ„ 수 ģ—†ģŠµė‹ˆė‹¤',
422
727
  adoConnectionFailed: 'Azure DevOps ģ—°ź²° ģ‹¤ķŒØ',
728
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
729
+ githubMultiRepoHeader: 'šŸ“ GitHub 다중 ģ €ģž„ģ†Œ ģ„ ķƒ',
730
+ githubMultiRepoDesc: 'GitHub ģ”°ģ§ģ—ģ„œ ģ €ģž„ģ†Œė„¼ ģ„ ķƒķ•˜ģ„øģš”.',
731
+ githubSelectStrategy: 'GitHub ģ €ģž„ģ†Œė„¼ ģ–“ė–»ź²Œ ģ„ ķƒķ•˜ģ‹œź² ģŠµė‹ˆź¹Œ?',
732
+ bitbucketMultiRepoHeader: 'šŸ“ Bitbucket 다중 ģ €ģž„ģ†Œ ģ„ ķƒ',
733
+ bitbucketMultiRepoDesc: 'Bitbucket ģ›Œķ¬ģŠ¤ķŽ˜ģ“ģŠ¤ģ—ģ„œ ģ €ģž„ģ†Œė„¼ ģ„ ķƒķ•˜ģ„øģš”.',
734
+ bitbucketSelectStrategy: 'Bitbucket ģ €ģž„ģ†Œė„¼ ģ–“ė–»ź²Œ ģ„ ķƒķ•˜ģ‹œź² ģŠµė‹ˆź¹Œ?',
735
+ githubOrgPrompt: 'GitHub 씰직 ė˜ėŠ” ģ‚¬ģš©ģž ģ“ė¦„:',
736
+ githubPatPrompt: 'GitHub Personal Access Token (PAT):',
737
+ githubSelected: 'GitHub 씰직 "{org}"ģ“(ź°€) 복제용으딜 źµ¬ģ„±ė˜ģ—ˆģŠµė‹ˆė‹¤',
738
+ bitbucketWorkspacePrompt: 'Bitbucket ģ›Œķ¬ģŠ¤ķŽ˜ģ“ģŠ¤:',
739
+ bitbucketUsernamePrompt: 'Bitbucket ģ‚¬ģš©ģž ģ“ė¦„:',
740
+ bitbucketAppPasswordPrompt: 'Bitbucket 앱 ė¹„ė°€ė²ˆķ˜ø:',
741
+ bitbucketSelected: 'Bitbucket ģ›Œķ¬ģŠ¤ķŽ˜ģ“ģŠ¤ "{workspace}"ģ“(ź°€) 복제용으딜 źµ¬ģ„±ė˜ģ—ˆģŠµė‹ˆė‹¤',
423
742
  },
424
743
  pt: {
425
744
  header: 'šŸ“¦ Hospedagem do repositório',
@@ -437,7 +756,7 @@ function getRepoStrings(language) {
437
756
  other: 'Outro (GitLab, etc - em breve)',
438
757
  adoCloneQuestion: 'Digite padrĆ£o de nome de repo para clonar (ex. sw-* ou deixe vazio):',
439
758
  adoCloneSkip: 'Clonagem ignorada - configurĆ”vel depois',
440
- // New unified selection strings
759
+ // Unified selection strings
441
760
  adoSelectStrategy: 'Como vocĆŖ quer selecionar repositórios para clonar?',
442
761
  adoAllRepos: 'Todos',
443
762
  adoAllReposDesc: 'Clonar todos os repositórios do projeto',
@@ -465,113 +784,119 @@ function getRepoStrings(language) {
465
784
  adoFetchingProjects: 'Buscando projetos...',
466
785
  adoNoProjects: 'Nenhum projeto encontrado nesta organização',
467
786
  adoConnectionFailed: 'Falha ao conectar ao Azure DevOps',
787
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
788
+ githubMultiRepoHeader: 'šŸ“ Seleção de mĆŗltiplos repositórios GitHub',
789
+ githubMultiRepoDesc: 'Selecione os repositórios da sua organização GitHub.',
790
+ githubSelectStrategy: 'Como você quer selecionar os repositórios do GitHub?',
791
+ bitbucketMultiRepoHeader: 'šŸ“ Seleção de mĆŗltiplos repositórios Bitbucket',
792
+ bitbucketMultiRepoDesc: 'Selecione os repositórios do seu workspace Bitbucket.',
793
+ bitbucketSelectStrategy: 'Como você quer selecionar os repositórios do Bitbucket?',
794
+ githubOrgPrompt: 'Organização ou nome de usuÔrio do GitHub:',
795
+ githubPatPrompt: 'Token de acesso pessoal do GitHub (PAT):',
796
+ githubSelected: 'Organização do GitHub "{org}" configurada para clonagem',
797
+ bitbucketWorkspacePrompt: 'Workspace do Bitbucket:',
798
+ bitbucketUsernamePrompt: 'Nome de usuƔrio do Bitbucket:',
799
+ bitbucketAppPasswordPrompt: 'Senha de aplicativo do Bitbucket:',
800
+ bitbucketSelected: 'Workspace do Bitbucket "{workspace}" configurado para clonagem',
468
801
  },
469
802
  };
470
803
  return strings[language] || strings.en;
471
804
  }
472
805
  /**
473
- * Fetch all accessible projects for an ADO organization
806
+ * Prompt for multi-repo pattern selection (unified for GitHub, Bitbucket, ADO)
807
+ *
808
+ * @param provider - The git provider ('github' | 'bitbucket' | 'ado')
809
+ * @param strings - Localized strings
810
+ * @returns Selected pattern result
474
811
  */
475
- async function fetchAdoProjects(org, pat) {
476
- const auth = Buffer.from(`:${pat}`).toString('base64');
477
- const endpoint = `https://dev.azure.com/${org}/_apis/projects?api-version=7.0`;
478
- const response = await fetch(endpoint, {
479
- headers: { 'Authorization': `Basic ${auth}`, 'Accept': 'application/json' }
480
- });
481
- if (!response.ok) {
482
- // Read response body to check for HTML error pages
483
- const errorBody = await response.text();
484
- const looksLikeHtml = errorBody.trim().startsWith('<!DOCTYPE') || errorBody.trim().startsWith('<html');
485
- if (looksLikeHtml) {
486
- throw new Error(`Azure DevOps returned an error page (HTTP ${response.status}).\n` +
487
- ` This usually indicates an authentication or configuration issue.\n` +
488
- ` \n` +
489
- ` Possible causes:\n` +
490
- ` • Invalid or expired Personal Access Token (PAT)\n` +
491
- ` • Incorrect organization name "${org}"\n` +
492
- ` • Corporate firewall or proxy intercepting the request\n` +
493
- ` • SSO/authentication redirect (try accessing Azure DevOps in browser first)`);
494
- }
495
- throw new Error(`Failed to fetch projects: ${response.status} - ${errorBody.substring(0, 200)}`);
812
+ async function promptMultiRepoPatternSelection(provider, strings) {
813
+ // Display provider-specific header
814
+ if (provider === 'github') {
815
+ console.log(chalk.blue(`\n${strings.githubMultiRepoHeader}\n`));
816
+ console.log(chalk.gray(` ${strings.githubMultiRepoDesc}\n`));
496
817
  }
497
- // Use safe JSON parser to detect HTML responses even on 200 OK
498
- const data = await safeParseJsonResponse(response, 'Azure DevOps');
499
- return (data.value || []).map((p) => ({ name: p.name, id: p.id }));
500
- }
501
- /**
502
- * Prompt user for ADO organization, credentials, and project selection
503
- * For repository cloning in multi-repo setups
504
- */
505
- async function promptAdoProjectSelection(targetDir, strings) {
506
- // Check for existing credentials in .env or environment
507
- let existingOrg;
508
- let existingPat;
509
- const envContent = readEnvFile(targetDir);
510
- if (envContent) {
511
- const parsed = parseEnvFile(envContent);
512
- existingOrg = parsed.AZURE_DEVOPS_ORG;
513
- existingPat = parsed.AZURE_DEVOPS_PAT;
818
+ else if (provider === 'bitbucket') {
819
+ console.log(chalk.blue(`\n${strings.bitbucketMultiRepoHeader}\n`));
820
+ console.log(chalk.gray(` ${strings.bitbucketMultiRepoDesc}\n`));
514
821
  }
515
- // Fall back to environment variables
516
- if (!existingOrg || !existingPat) {
517
- const auth = getAzureDevOpsAuth();
518
- if (auth) {
519
- existingOrg = existingOrg || auth.org;
520
- existingPat = existingPat || auth.pat;
521
- }
822
+ else {
823
+ console.log(chalk.blue('\nšŸ“ Azure DevOps Repository Selection\n'));
824
+ console.log(chalk.gray(' Select which repositories to clone from your ADO project.\n'));
522
825
  }
523
- // Prompt for organization
524
- const org = await input({
525
- message: strings.adoOrgPrompt,
526
- default: existingOrg,
527
- validate: (value) => {
528
- if (!value.trim())
529
- return 'Organization name is required';
530
- return true;
531
- }
826
+ // Get provider-specific strategy message
827
+ const strategyMessage = provider === 'github'
828
+ ? strings.githubSelectStrategy
829
+ : provider === 'bitbucket'
830
+ ? strings.bitbucketSelectStrategy
831
+ : strings.adoSelectStrategy;
832
+ const strategyChoices = [
833
+ {
834
+ name: `${chalk.green('āœ“')} ${strings.adoAllRepos} ${chalk.gray(`- ${strings.adoAllReposDesc}`)}`,
835
+ value: 'all',
836
+ },
837
+ {
838
+ name: `${strings.adoPatternGlob} ${chalk.gray(`- ${strings.adoPatternGlobDesc}`)}`,
839
+ value: 'pattern-glob',
840
+ },
841
+ {
842
+ name: `${strings.adoPatternRegex} ${chalk.gray(`- ${strings.adoPatternRegexDesc}`)}`,
843
+ value: 'pattern-regex',
844
+ },
845
+ {
846
+ name: `${strings.adoSkipOption} ${chalk.gray(`- ${strings.adoSkipDesc}`)}`,
847
+ value: 'skip',
848
+ },
849
+ ];
850
+ const strategy = await select({
851
+ message: strategyMessage,
852
+ choices: strategyChoices,
853
+ default: 'all',
532
854
  });
533
- // Prompt for PAT
534
- const pat = await password({
535
- message: strings.adoPatPrompt,
536
- mask: true,
537
- validate: (value) => {
538
- if (!value.trim())
539
- return 'Personal Access Token is required';
540
- return true;
855
+ switch (strategy) {
856
+ case 'all': {
857
+ console.log(chalk.green(` āœ“ ${strings.adoSelectedAll}`));
858
+ return { strategy: 'all', pattern: '*' };
859
+ }
860
+ case 'pattern-glob': {
861
+ console.log(chalk.gray(`\n šŸ’” ${strings.adoPatternHint}`));
862
+ console.log(chalk.gray(` šŸ’” ${strings.adoShortcutsHint}\n`));
863
+ const pattern = await input({
864
+ message: strings.adoPatternPrompt,
865
+ validate: (value) => {
866
+ if (!value.trim())
867
+ return strings.adoPatternRequired;
868
+ return true;
869
+ },
870
+ });
871
+ // Parse shortcuts (starts:, ends:, contains:)
872
+ const parsedPattern = parsePatternShortcut(pattern.trim());
873
+ const message = strings.adoSelectedPattern.replace('{pattern}', parsedPattern);
874
+ console.log(chalk.green(` āœ“ ${message}`));
875
+ return { strategy: 'pattern-glob', pattern: parsedPattern };
876
+ }
877
+ case 'pattern-regex': {
878
+ console.log(chalk.gray(`\n šŸ’” ${strings.adoRegexHint}\n`));
879
+ const pattern = await input({
880
+ message: strings.adoRegexPrompt,
881
+ validate: (value) => {
882
+ if (!value.trim())
883
+ return strings.adoPatternRequired;
884
+ const validation = validateRegex(value.trim());
885
+ if (validation !== true) {
886
+ return `${strings.adoInvalidRegex}: ${validation}`;
887
+ }
888
+ return true;
889
+ },
890
+ });
891
+ const message = strings.adoSelectedRegex.replace('{pattern}', pattern.trim());
892
+ console.log(chalk.green(` āœ“ ${message}`));
893
+ return { strategy: 'pattern-regex', pattern: pattern.trim(), isRegex: true };
894
+ }
895
+ case 'skip': {
896
+ console.log(chalk.gray(` → ${strings.adoCloneSkip}`));
897
+ return { strategy: 'skip' };
541
898
  }
542
- });
543
- // Fetch projects
544
- const spinner = ora(strings.adoFetchingProjects).start();
545
- let projects;
546
- try {
547
- projects = await fetchAdoProjects(org, pat);
548
- spinner.succeed();
549
- }
550
- catch (error) {
551
- spinner.fail(strings.adoConnectionFailed);
552
- console.log(chalk.red(` ${error instanceof Error ? error.message : 'Unknown error'}`));
553
- return null;
554
- }
555
- if (projects.length === 0) {
556
- console.log(chalk.yellow(` ${strings.adoNoProjects}`));
557
- return null;
558
899
  }
559
- // Let user select projects
560
- const selectedProjects = await checkbox({
561
- message: strings.adoProjectPrompt,
562
- choices: projects.map((p, index) => ({
563
- name: p.name,
564
- value: p.name,
565
- checked: index === 0 // First project selected by default
566
- })),
567
- required: true
568
- });
569
- console.log(chalk.green(` āœ“ ${strings.adoProjectsSelected.replace('{count}', String(selectedProjects.length))}`));
570
- return {
571
- org,
572
- pat,
573
- projects: selectedProjects
574
- };
575
900
  }
576
901
  /**
577
902
  * Prompt user for repository hosting configuration
@@ -643,96 +968,69 @@ export async function setupRepositoryHosting(options) {
643
968
  else {
644
969
  repositoryHosting = `${provider}-${structure}`;
645
970
  }
646
- // Step 3: For ADO multi-repo, prompt for project selection and clone pattern
971
+ // Step 3: For multi-repo setups, prompt for pattern selection
972
+ // Now unified for ADO, GitHub, and Bitbucket (v0.32.6+)
647
973
  let adoClonePattern;
648
974
  let adoClonePatternResult;
649
975
  let adoProjectSelection;
650
- if (provider === 'ado' && isMultiRepo) {
651
- // Step 3a: Prompt for ADO organization and project selection
652
- console.log(chalk.blue('\nšŸ“ Azure DevOps Project Selection\n'));
653
- console.log(chalk.gray(' Select which project(s) to clone repositories from.\n'));
654
- const projectSelection = await promptAdoProjectSelection(options.targetDir, strings);
655
- if (projectSelection) {
656
- adoProjectSelection = projectSelection;
657
- }
658
- // Step 3b: Show unified strategy selection
659
- const strategyChoices = [
660
- {
661
- name: `${chalk.green('āœ“')} ${strings.adoAllRepos} ${chalk.gray(`- ${strings.adoAllReposDesc}`)}`,
662
- value: 'all',
663
- },
664
- {
665
- name: `${strings.adoPatternGlob} ${chalk.gray(`- ${strings.adoPatternGlobDesc}`)}`,
666
- value: 'pattern-glob',
667
- },
668
- {
669
- name: `${strings.adoPatternRegex} ${chalk.gray(`- ${strings.adoPatternRegexDesc}`)}`,
670
- value: 'pattern-regex',
671
- },
672
- {
673
- name: `${strings.adoSkipOption} ${chalk.gray(`- ${strings.adoSkipDesc}`)}`,
674
- value: 'skip',
675
- },
676
- ];
677
- const strategy = await select({
678
- message: strings.adoSelectStrategy,
679
- choices: strategyChoices,
680
- default: 'all',
681
- });
682
- switch (strategy) {
683
- case 'all': {
684
- adoClonePattern = '*';
685
- adoClonePatternResult = { strategy: 'all', pattern: '*' };
686
- console.log(chalk.green(` āœ“ ${strings.adoSelectedAll}`));
687
- break;
976
+ let githubRepoSelection;
977
+ let bitbucketRepoSelection;
978
+ if (isMultiRepo && (provider === 'ado' || provider === 'github' || provider === 'bitbucket')) {
979
+ // Step 3a: Provider-specific credential prompts
980
+ if (provider === 'ado') {
981
+ console.log(chalk.blue('\nšŸ“ Azure DevOps Project Selection\n'));
982
+ console.log(chalk.gray(' Select which project(s) to clone repositories from.\n'));
983
+ const projectSelection = await promptAdoProjectSelection(options.targetDir, strings);
984
+ if (projectSelection) {
985
+ adoProjectSelection = projectSelection;
688
986
  }
689
- case 'pattern-glob': {
690
- console.log(chalk.gray(`\n šŸ’” ${strings.adoPatternHint}`));
691
- console.log(chalk.gray(` šŸ’” ${strings.adoShortcutsHint}\n`));
692
- const pattern = await input({
693
- message: strings.adoPatternPrompt,
694
- validate: (value) => {
695
- if (!value.trim())
696
- return strings.adoPatternRequired;
697
- return true;
698
- },
699
- });
700
- // Parse shortcuts (starts:, ends:, contains:)
701
- const parsedPattern = parsePatternShortcut(pattern.trim());
702
- adoClonePattern = parsedPattern;
703
- adoClonePatternResult = { strategy: 'pattern-glob', pattern: parsedPattern };
704
- const message = strings.adoSelectedPattern.replace('{pattern}', parsedPattern);
705
- console.log(chalk.green(` āœ“ ${message}`));
706
- break;
707
- }
708
- case 'pattern-regex': {
709
- console.log(chalk.gray(`\n šŸ’” ${strings.adoRegexHint}\n`));
710
- const pattern = await input({
711
- message: strings.adoRegexPrompt,
712
- validate: (value) => {
713
- if (!value.trim())
714
- return strings.adoPatternRequired;
715
- const validation = validateRegex(value.trim());
716
- if (validation !== true) {
717
- return `${strings.adoInvalidRegex}: ${validation}`;
718
- }
719
- return true;
720
- },
721
- });
722
- // Store as regex: prefix for downstream processing
723
- adoClonePattern = `regex:${pattern.trim()}`;
724
- adoClonePatternResult = { strategy: 'pattern-regex', pattern: pattern.trim(), isRegex: true };
725
- const message = strings.adoSelectedRegex.replace('{pattern}', pattern.trim());
726
- console.log(chalk.green(` āœ“ ${message}`));
727
- break;
987
+ }
988
+ else if (provider === 'github') {
989
+ // GitHub multi-repo credential prompt (v0.32.7+)
990
+ console.log(chalk.blue(`\n${strings.githubMultiRepoHeader}\n`));
991
+ console.log(chalk.gray(` ${strings.githubMultiRepoDesc}\n`));
992
+ const githubSelection = await promptGitHubRepoSelection(options.targetDir, gitHubRemote, strings);
993
+ if (githubSelection) {
994
+ githubRepoSelection = githubSelection;
728
995
  }
729
- case 'skip': {
730
- adoClonePatternResult = { strategy: 'skip' };
731
- console.log(chalk.gray(` → ${strings.adoCloneSkip}`));
732
- break;
996
+ }
997
+ else if (provider === 'bitbucket') {
998
+ // Bitbucket multi-repo credential prompt (v0.32.7+)
999
+ console.log(chalk.blue(`\n${strings.bitbucketMultiRepoHeader}\n`));
1000
+ console.log(chalk.gray(` ${strings.bitbucketMultiRepoDesc}\n`));
1001
+ const bitbucketSelection = await promptBitbucketRepoSelection(options.targetDir, strings);
1002
+ if (bitbucketSelection) {
1003
+ bitbucketRepoSelection = bitbucketSelection;
733
1004
  }
734
1005
  }
1006
+ // Step 3b: Show unified strategy selection (ALL providers)
1007
+ const patternResult = await promptMultiRepoPatternSelection(provider, strings);
1008
+ // Map to ADO-style result for backward compatibility
1009
+ adoClonePatternResult = {
1010
+ strategy: patternResult.strategy,
1011
+ pattern: patternResult.pattern,
1012
+ isRegex: patternResult.isRegex,
1013
+ };
1014
+ // Set the clone pattern string
1015
+ if (patternResult.strategy === 'all') {
1016
+ adoClonePattern = '*';
1017
+ }
1018
+ else if (patternResult.strategy === 'pattern-glob') {
1019
+ adoClonePattern = patternResult.pattern;
1020
+ }
1021
+ else if (patternResult.strategy === 'pattern-regex') {
1022
+ adoClonePattern = `regex:${patternResult.pattern}`;
1023
+ }
1024
+ // For 'skip', adoClonePattern remains undefined
735
1025
  }
736
- return { hosting: repositoryHosting, isMultiRepo, adoClonePattern, adoClonePatternResult, adoProjectSelection };
1026
+ return {
1027
+ hosting: repositoryHosting,
1028
+ isMultiRepo,
1029
+ adoClonePattern,
1030
+ adoClonePatternResult,
1031
+ adoProjectSelection,
1032
+ githubRepoSelection,
1033
+ bitbucketRepoSelection
1034
+ };
737
1035
  }
738
1036
  //# sourceMappingURL=repository-setup.js.map