specweave 0.32.5 → 0.32.6

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 (41) hide show
  1. package/bin/specweave.js +78 -0
  2. package/dist/src/cli/commands/docs.d.ts +45 -0
  3. package/dist/src/cli/commands/docs.d.ts.map +1 -0
  4. package/dist/src/cli/commands/docs.js +307 -0
  5. package/dist/src/cli/commands/docs.js.map +1 -0
  6. package/dist/src/cli/helpers/init/repository-setup.d.ts +14 -2
  7. package/dist/src/cli/helpers/init/repository-setup.d.ts.map +1 -1
  8. package/dist/src/cli/helpers/init/repository-setup.js +292 -188
  9. package/dist/src/cli/helpers/init/repository-setup.js.map +1 -1
  10. package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
  11. package/dist/src/cli/helpers/issue-tracker/index.js +9 -3
  12. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  13. package/dist/src/cli/helpers/issue-tracker/jira.d.ts +18 -2
  14. package/dist/src/cli/helpers/issue-tracker/jira.d.ts.map +1 -1
  15. package/dist/src/cli/helpers/issue-tracker/jira.js +325 -25
  16. package/dist/src/cli/helpers/issue-tracker/jira.js.map +1 -1
  17. package/dist/src/cli/helpers/issue-tracker/sync-config-writer.d.ts.map +1 -1
  18. package/dist/src/cli/helpers/issue-tracker/sync-config-writer.js +61 -1
  19. package/dist/src/cli/helpers/issue-tracker/sync-config-writer.js.map +1 -1
  20. package/dist/src/cli/helpers/issue-tracker/types.d.ts +25 -0
  21. package/dist/src/cli/helpers/issue-tracker/types.d.ts.map +1 -1
  22. package/dist/src/cli/helpers/issue-tracker/types.js.map +1 -1
  23. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js +1 -1
  24. package/dist/src/utils/docs-preview/config-generator.d.ts.map +1 -1
  25. package/dist/src/utils/docs-preview/config-generator.js +12 -16
  26. package/dist/src/utils/docs-preview/config-generator.js.map +1 -1
  27. package/dist/src/utils/docs-preview/docusaurus-setup.d.ts.map +1 -1
  28. package/dist/src/utils/docs-preview/docusaurus-setup.js +24 -5
  29. package/dist/src/utils/docs-preview/docusaurus-setup.js.map +1 -1
  30. package/dist/src/utils/docs-preview/package-installer.d.ts +3 -0
  31. package/dist/src/utils/docs-preview/package-installer.d.ts.map +1 -1
  32. package/dist/src/utils/docs-preview/package-installer.js +18 -9
  33. package/dist/src/utils/docs-preview/package-installer.js.map +1 -1
  34. package/dist/src/utils/docs-preview/server-manager.d.ts.map +1 -1
  35. package/dist/src/utils/docs-preview/server-manager.js +2 -1
  36. package/dist/src/utils/docs-preview/server-manager.js.map +1 -1
  37. package/dist/src/utils/validators/jira-validator.d.ts +25 -1
  38. package/dist/src/utils/validators/jira-validator.d.ts.map +1 -1
  39. package/dist/src/utils/validators/jira-validator.js +254 -172
  40. package/dist/src/utils/validators/jira-validator.js.map +1 -1
  41. package/package.json +1 -1
@@ -56,6 +56,110 @@ 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
+ }
59
163
  /**
60
164
  * Get translated strings for repository setup
61
165
  */
@@ -77,7 +181,7 @@ function getRepoStrings(language) {
77
181
  other: 'Other (GitLab, etc - coming soon)',
78
182
  adoCloneQuestion: 'Enter repo name pattern to clone (e.g., sw-* or leave empty to skip):',
79
183
  adoCloneSkip: 'Skipping repo cloning - you can configure later',
80
- // New unified selection strings
184
+ // Unified selection strings
81
185
  adoSelectStrategy: 'How do you want to select repositories to clone?',
82
186
  adoAllRepos: 'All',
83
187
  adoAllReposDesc: 'Clone all repositories from the project',
@@ -105,6 +209,13 @@ function getRepoStrings(language) {
105
209
  adoFetchingProjects: 'Fetching projects...',
106
210
  adoNoProjects: 'No projects found in this organization',
107
211
  adoConnectionFailed: 'Failed to connect to Azure DevOps',
212
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
213
+ githubMultiRepoHeader: '📁 GitHub Multi-Repository Selection',
214
+ githubMultiRepoDesc: 'Select which repositories to work with from your GitHub organization.',
215
+ githubSelectStrategy: 'How do you want to select GitHub repositories?',
216
+ bitbucketMultiRepoHeader: '📁 Bitbucket Multi-Repository Selection',
217
+ bitbucketMultiRepoDesc: 'Select which repositories to work with from your Bitbucket workspace.',
218
+ bitbucketSelectStrategy: 'How do you want to select Bitbucket repositories?',
108
219
  },
109
220
  ru: {
110
221
  header: '📦 Хостинг репозитория',
@@ -122,7 +233,7 @@ function getRepoStrings(language) {
122
233
  other: 'Другой (GitLab и т.д. - скоро)',
123
234
  adoCloneQuestion: 'Введите шаблон имени репо для клонирования (напр., sw-* или оставьте пустым):',
124
235
  adoCloneSkip: 'Пропуск клонирования - можно настроить позже',
125
- // New unified selection strings
236
+ // Unified selection strings
126
237
  adoSelectStrategy: 'Как вы хотите выбрать репозитории для клонирования?',
127
238
  adoAllRepos: 'Все',
128
239
  adoAllReposDesc: 'Клонировать все репозитории из проекта',
@@ -150,6 +261,13 @@ function getRepoStrings(language) {
150
261
  adoFetchingProjects: 'Загрузка проектов...',
151
262
  adoNoProjects: 'Проекты не найдены в этой организации',
152
263
  adoConnectionFailed: 'Не удалось подключиться к Azure DevOps',
264
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
265
+ githubMultiRepoHeader: '📁 Выбор репозиториев GitHub',
266
+ githubMultiRepoDesc: 'Выберите репозитории для работы из вашей организации GitHub.',
267
+ githubSelectStrategy: 'Как вы хотите выбрать репозитории GitHub?',
268
+ bitbucketMultiRepoHeader: '📁 Выбор репозиториев Bitbucket',
269
+ bitbucketMultiRepoDesc: 'Выберите репозитории для работы из вашего рабочего пространства Bitbucket.',
270
+ bitbucketSelectStrategy: 'Как вы хотите выбрать репозитории Bitbucket?',
153
271
  },
154
272
  es: {
155
273
  header: '📦 Alojamiento del repositorio',
@@ -167,7 +285,7 @@ function getRepoStrings(language) {
167
285
  other: 'Otro (GitLab, etc - próximamente)',
168
286
  adoCloneQuestion: 'Ingrese patrón de nombre de repo a clonar (ej., sw-* o deje vacío):',
169
287
  adoCloneSkip: 'Omitiendo clonación - puede configurar después',
170
- // New unified selection strings
288
+ // Unified selection strings
171
289
  adoSelectStrategy: '¿Cómo quiere seleccionar repositorios para clonar?',
172
290
  adoAllRepos: 'Todos',
173
291
  adoAllReposDesc: 'Clonar todos los repositorios del proyecto',
@@ -195,6 +313,13 @@ function getRepoStrings(language) {
195
313
  adoFetchingProjects: 'Obteniendo proyectos...',
196
314
  adoNoProjects: 'No se encontraron proyectos en esta organización',
197
315
  adoConnectionFailed: 'Error al conectar con Azure DevOps',
316
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
317
+ githubMultiRepoHeader: '📁 Selección de repositorios GitHub',
318
+ githubMultiRepoDesc: 'Seleccione los repositorios de su organización GitHub.',
319
+ githubSelectStrategy: '¿Cómo desea seleccionar los repositorios de GitHub?',
320
+ bitbucketMultiRepoHeader: '📁 Selección de repositorios Bitbucket',
321
+ bitbucketMultiRepoDesc: 'Seleccione los repositorios de su espacio de trabajo Bitbucket.',
322
+ bitbucketSelectStrategy: '¿Cómo desea seleccionar los repositorios de Bitbucket?',
198
323
  },
199
324
  zh: {
200
325
  header: '📦 仓库托管',
@@ -212,7 +337,7 @@ function getRepoStrings(language) {
212
337
  other: '其他(GitLab 等 - 即将推出)',
213
338
  adoCloneQuestion: '输入要克隆的仓库名称模式(例如 sw-* 或留空跳过):',
214
339
  adoCloneSkip: '跳过克隆 - 稍后可以配置',
215
- // New unified selection strings
340
+ // Unified selection strings
216
341
  adoSelectStrategy: '您想如何选择要克隆的仓库?',
217
342
  adoAllRepos: '全部',
218
343
  adoAllReposDesc: '克隆项目中的所有仓库',
@@ -240,6 +365,13 @@ function getRepoStrings(language) {
240
365
  adoFetchingProjects: '正在获取项目...',
241
366
  adoNoProjects: '在此组织中未找到项目',
242
367
  adoConnectionFailed: '无法连接到 Azure DevOps',
368
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
369
+ githubMultiRepoHeader: '📁 GitHub 多仓库选择',
370
+ githubMultiRepoDesc: '从您的 GitHub 组织中选择要使用的仓库。',
371
+ githubSelectStrategy: '您想如何选择 GitHub 仓库?',
372
+ bitbucketMultiRepoHeader: '📁 Bitbucket 多仓库选择',
373
+ bitbucketMultiRepoDesc: '从您的 Bitbucket 工作区中选择要使用的仓库。',
374
+ bitbucketSelectStrategy: '您想如何选择 Bitbucket 仓库?',
243
375
  },
244
376
  de: {
245
377
  header: '📦 Repository-Hosting',
@@ -257,7 +389,7 @@ function getRepoStrings(language) {
257
389
  other: 'Andere (GitLab, etc - kommt bald)',
258
390
  adoCloneQuestion: 'Repo-Namensmuster zum Klonen eingeben (z.B. sw-* oder leer lassen):',
259
391
  adoCloneSkip: 'Klonen übersprungen - später konfigurierbar',
260
- // New unified selection strings
392
+ // Unified selection strings
261
393
  adoSelectStrategy: 'Wie möchten Sie Repositories zum Klonen auswählen?',
262
394
  adoAllRepos: 'Alle',
263
395
  adoAllReposDesc: 'Alle Repositories aus dem Projekt klonen',
@@ -285,6 +417,13 @@ function getRepoStrings(language) {
285
417
  adoFetchingProjects: 'Projekte werden abgerufen...',
286
418
  adoNoProjects: 'Keine Projekte in dieser Organisation gefunden',
287
419
  adoConnectionFailed: 'Verbindung zu Azure DevOps fehlgeschlagen',
420
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
421
+ githubMultiRepoHeader: '📁 GitHub Multi-Repository Auswahl',
422
+ githubMultiRepoDesc: 'Wählen Sie die Repositories aus Ihrer GitHub-Organisation.',
423
+ githubSelectStrategy: 'Wie möchten Sie GitHub-Repositories auswählen?',
424
+ bitbucketMultiRepoHeader: '📁 Bitbucket Multi-Repository Auswahl',
425
+ bitbucketMultiRepoDesc: 'Wählen Sie die Repositories aus Ihrem Bitbucket-Workspace.',
426
+ bitbucketSelectStrategy: 'Wie möchten Sie Bitbucket-Repositories auswählen?',
288
427
  },
289
428
  fr: {
290
429
  header: '📦 Hébergement du dépôt',
@@ -302,7 +441,7 @@ function getRepoStrings(language) {
302
441
  other: 'Autre (GitLab, etc - bientôt)',
303
442
  adoCloneQuestion: 'Entrez le modèle de nom de repo à cloner (ex. sw-* ou laissez vide):',
304
443
  adoCloneSkip: 'Clonage ignoré - configurable plus tard',
305
- // New unified selection strings
444
+ // Unified selection strings
306
445
  adoSelectStrategy: 'Comment voulez-vous sélectionner les repos à cloner?',
307
446
  adoAllRepos: 'Tous',
308
447
  adoAllReposDesc: 'Cloner tous les repos du projet',
@@ -330,6 +469,13 @@ function getRepoStrings(language) {
330
469
  adoFetchingProjects: 'Récupération des projets...',
331
470
  adoNoProjects: 'Aucun projet trouvé dans cette organisation',
332
471
  adoConnectionFailed: 'Échec de connexion à Azure DevOps',
472
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
473
+ githubMultiRepoHeader: '📁 Sélection multi-dépôt GitHub',
474
+ githubMultiRepoDesc: 'Sélectionnez les dépôts de votre organisation GitHub.',
475
+ githubSelectStrategy: 'Comment voulez-vous sélectionner les dépôts GitHub?',
476
+ bitbucketMultiRepoHeader: '📁 Sélection multi-dépôt Bitbucket',
477
+ bitbucketMultiRepoDesc: 'Sélectionnez les dépôts de votre espace de travail Bitbucket.',
478
+ bitbucketSelectStrategy: 'Comment voulez-vous sélectionner les dépôts Bitbucket?',
333
479
  },
334
480
  ja: {
335
481
  header: '📦 リポジトリホスティング',
@@ -347,7 +493,7 @@ function getRepoStrings(language) {
347
493
  other: 'その他(GitLabなど - 近日公開)',
348
494
  adoCloneQuestion: 'クローンするリポ名パターンを入力(例: sw-* または空白でスキップ):',
349
495
  adoCloneSkip: 'クローンをスキップ - 後で設定可能',
350
- // New unified selection strings
496
+ // Unified selection strings
351
497
  adoSelectStrategy: 'クローンするリポジトリをどのように選択しますか?',
352
498
  adoAllRepos: 'すべて',
353
499
  adoAllReposDesc: 'プロジェクトのすべてのリポジトリをクローン',
@@ -375,6 +521,13 @@ function getRepoStrings(language) {
375
521
  adoFetchingProjects: 'プロジェクトを取得中...',
376
522
  adoNoProjects: 'この組織にプロジェクトが見つかりません',
377
523
  adoConnectionFailed: 'Azure DevOps への接続に失敗しました',
524
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
525
+ githubMultiRepoHeader: '📁 GitHub マルチリポジトリ選択',
526
+ githubMultiRepoDesc: 'GitHub組織からリポジトリを選択してください。',
527
+ githubSelectStrategy: 'GitHubリポジトリをどのように選択しますか?',
528
+ bitbucketMultiRepoHeader: '📁 Bitbucket マルチリポジトリ選択',
529
+ bitbucketMultiRepoDesc: 'Bitbucketワークスペースからリポジトリを選択してください。',
530
+ bitbucketSelectStrategy: 'Bitbucketリポジトリをどのように選択しますか?',
378
531
  },
379
532
  ko: {
380
533
  header: '📦 저장소 호스팅',
@@ -392,7 +545,7 @@ function getRepoStrings(language) {
392
545
  other: '기타 (GitLab 등 - 곧 출시)',
393
546
  adoCloneQuestion: '복제할 저장소 이름 패턴 입력 (예: sw-* 또는 비워두기):',
394
547
  adoCloneSkip: '복제 건너뜀 - 나중에 설정 가능',
395
- // New unified selection strings
548
+ // Unified selection strings
396
549
  adoSelectStrategy: '복제할 저장소를 어떻게 선택하시겠습니까?',
397
550
  adoAllRepos: '모두',
398
551
  adoAllReposDesc: '프로젝트의 모든 저장소 복제',
@@ -420,6 +573,13 @@ function getRepoStrings(language) {
420
573
  adoFetchingProjects: '프로젝트 가져오는 중...',
421
574
  adoNoProjects: '이 조직에서 프로젝트를 찾을 수 없습니다',
422
575
  adoConnectionFailed: 'Azure DevOps 연결 실패',
576
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
577
+ githubMultiRepoHeader: '📁 GitHub 다중 저장소 선택',
578
+ githubMultiRepoDesc: 'GitHub 조직에서 저장소를 선택하세요.',
579
+ githubSelectStrategy: 'GitHub 저장소를 어떻게 선택하시겠습니까?',
580
+ bitbucketMultiRepoHeader: '📁 Bitbucket 다중 저장소 선택',
581
+ bitbucketMultiRepoDesc: 'Bitbucket 워크스페이스에서 저장소를 선택하세요.',
582
+ bitbucketSelectStrategy: 'Bitbucket 저장소를 어떻게 선택하시겠습니까?',
423
583
  },
424
584
  pt: {
425
585
  header: '📦 Hospedagem do repositório',
@@ -437,7 +597,7 @@ function getRepoStrings(language) {
437
597
  other: 'Outro (GitLab, etc - em breve)',
438
598
  adoCloneQuestion: 'Digite padrão de nome de repo para clonar (ex. sw-* ou deixe vazio):',
439
599
  adoCloneSkip: 'Clonagem ignorada - configurável depois',
440
- // New unified selection strings
600
+ // Unified selection strings
441
601
  adoSelectStrategy: 'Como você quer selecionar repositórios para clonar?',
442
602
  adoAllRepos: 'Todos',
443
603
  adoAllReposDesc: 'Clonar todos os repositórios do projeto',
@@ -465,113 +625,112 @@ function getRepoStrings(language) {
465
625
  adoFetchingProjects: 'Buscando projetos...',
466
626
  adoNoProjects: 'Nenhum projeto encontrado nesta organização',
467
627
  adoConnectionFailed: 'Falha ao conectar ao Azure DevOps',
628
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
629
+ githubMultiRepoHeader: '📁 Seleção de múltiplos repositórios GitHub',
630
+ githubMultiRepoDesc: 'Selecione os repositórios da sua organização GitHub.',
631
+ githubSelectStrategy: 'Como você quer selecionar os repositórios do GitHub?',
632
+ bitbucketMultiRepoHeader: '📁 Seleção de múltiplos repositórios Bitbucket',
633
+ bitbucketMultiRepoDesc: 'Selecione os repositórios do seu workspace Bitbucket.',
634
+ bitbucketSelectStrategy: 'Como você quer selecionar os repositórios do Bitbucket?',
468
635
  },
469
636
  };
470
637
  return strings[language] || strings.en;
471
638
  }
472
639
  /**
473
- * Fetch all accessible projects for an ADO organization
640
+ * Prompt for multi-repo pattern selection (unified for GitHub, Bitbucket, ADO)
641
+ *
642
+ * @param provider - The git provider ('github' | 'bitbucket' | 'ado')
643
+ * @param strings - Localized strings
644
+ * @returns Selected pattern result
474
645
  */
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)}`);
646
+ async function promptMultiRepoPatternSelection(provider, strings) {
647
+ // Display provider-specific header
648
+ if (provider === 'github') {
649
+ console.log(chalk.blue(`\n${strings.githubMultiRepoHeader}\n`));
650
+ console.log(chalk.gray(` ${strings.githubMultiRepoDesc}\n`));
496
651
  }
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;
652
+ else if (provider === 'bitbucket') {
653
+ console.log(chalk.blue(`\n${strings.bitbucketMultiRepoHeader}\n`));
654
+ console.log(chalk.gray(` ${strings.bitbucketMultiRepoDesc}\n`));
514
655
  }
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
- }
656
+ else {
657
+ console.log(chalk.blue('\n📁 Azure DevOps Repository Selection\n'));
658
+ console.log(chalk.gray(' Select which repositories to clone from your ADO project.\n'));
522
659
  }
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
- }
660
+ // Get provider-specific strategy message
661
+ const strategyMessage = provider === 'github'
662
+ ? strings.githubSelectStrategy
663
+ : provider === 'bitbucket'
664
+ ? strings.bitbucketSelectStrategy
665
+ : strings.adoSelectStrategy;
666
+ const strategyChoices = [
667
+ {
668
+ name: `${chalk.green('✓')} ${strings.adoAllRepos} ${chalk.gray(`- ${strings.adoAllReposDesc}`)}`,
669
+ value: 'all',
670
+ },
671
+ {
672
+ name: `${strings.adoPatternGlob} ${chalk.gray(`- ${strings.adoPatternGlobDesc}`)}`,
673
+ value: 'pattern-glob',
674
+ },
675
+ {
676
+ name: `${strings.adoPatternRegex} ${chalk.gray(`- ${strings.adoPatternRegexDesc}`)}`,
677
+ value: 'pattern-regex',
678
+ },
679
+ {
680
+ name: `${strings.adoSkipOption} ${chalk.gray(`- ${strings.adoSkipDesc}`)}`,
681
+ value: 'skip',
682
+ },
683
+ ];
684
+ const strategy = await select({
685
+ message: strategyMessage,
686
+ choices: strategyChoices,
687
+ default: 'all',
532
688
  });
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;
689
+ switch (strategy) {
690
+ case 'all': {
691
+ console.log(chalk.green(` ✓ ${strings.adoSelectedAll}`));
692
+ return { strategy: 'all', pattern: '*' };
693
+ }
694
+ case 'pattern-glob': {
695
+ console.log(chalk.gray(`\n 💡 ${strings.adoPatternHint}`));
696
+ console.log(chalk.gray(` 💡 ${strings.adoShortcutsHint}\n`));
697
+ const pattern = await input({
698
+ message: strings.adoPatternPrompt,
699
+ validate: (value) => {
700
+ if (!value.trim())
701
+ return strings.adoPatternRequired;
702
+ return true;
703
+ },
704
+ });
705
+ // Parse shortcuts (starts:, ends:, contains:)
706
+ const parsedPattern = parsePatternShortcut(pattern.trim());
707
+ const message = strings.adoSelectedPattern.replace('{pattern}', parsedPattern);
708
+ console.log(chalk.green(` ✓ ${message}`));
709
+ return { strategy: 'pattern-glob', pattern: parsedPattern };
710
+ }
711
+ case 'pattern-regex': {
712
+ console.log(chalk.gray(`\n 💡 ${strings.adoRegexHint}\n`));
713
+ const pattern = await input({
714
+ message: strings.adoRegexPrompt,
715
+ validate: (value) => {
716
+ if (!value.trim())
717
+ return strings.adoPatternRequired;
718
+ const validation = validateRegex(value.trim());
719
+ if (validation !== true) {
720
+ return `${strings.adoInvalidRegex}: ${validation}`;
721
+ }
722
+ return true;
723
+ },
724
+ });
725
+ const message = strings.adoSelectedRegex.replace('{pattern}', pattern.trim());
726
+ console.log(chalk.green(` ✓ ${message}`));
727
+ return { strategy: 'pattern-regex', pattern: pattern.trim(), isRegex: true };
728
+ }
729
+ case 'skip': {
730
+ console.log(chalk.gray(` → ${strings.adoCloneSkip}`));
731
+ return { strategy: 'skip' };
541
732
  }
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
733
  }
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
734
  }
576
735
  /**
577
736
  * Prompt user for repository hosting configuration
@@ -643,95 +802,40 @@ export async function setupRepositoryHosting(options) {
643
802
  else {
644
803
  repositoryHosting = `${provider}-${structure}`;
645
804
  }
646
- // Step 3: For ADO multi-repo, prompt for project selection and clone pattern
805
+ // Step 3: For multi-repo setups, prompt for pattern selection
806
+ // Now unified for ADO, GitHub, and Bitbucket (v0.32.6+)
647
807
  let adoClonePattern;
648
808
  let adoClonePatternResult;
649
809
  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;
688
- }
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;
728
- }
729
- case 'skip': {
730
- adoClonePatternResult = { strategy: 'skip' };
731
- console.log(chalk.gray(` → ${strings.adoCloneSkip}`));
732
- break;
810
+ if (isMultiRepo && (provider === 'ado' || provider === 'github' || provider === 'bitbucket')) {
811
+ // Step 3a: For ADO only - prompt for organization and project selection
812
+ if (provider === 'ado') {
813
+ console.log(chalk.blue('\n📁 Azure DevOps Project Selection\n'));
814
+ console.log(chalk.gray(' Select which project(s) to clone repositories from.\n'));
815
+ const projectSelection = await promptAdoProjectSelection(options.targetDir, strings);
816
+ if (projectSelection) {
817
+ adoProjectSelection = projectSelection;
733
818
  }
734
819
  }
820
+ // Step 3b: Show unified strategy selection (ALL providers)
821
+ const patternResult = await promptMultiRepoPatternSelection(provider, strings);
822
+ // Map to ADO-style result for backward compatibility
823
+ adoClonePatternResult = {
824
+ strategy: patternResult.strategy,
825
+ pattern: patternResult.pattern,
826
+ isRegex: patternResult.isRegex,
827
+ };
828
+ // Set the clone pattern string
829
+ if (patternResult.strategy === 'all') {
830
+ adoClonePattern = '*';
831
+ }
832
+ else if (patternResult.strategy === 'pattern-glob') {
833
+ adoClonePattern = patternResult.pattern;
834
+ }
835
+ else if (patternResult.strategy === 'pattern-regex') {
836
+ adoClonePattern = `regex:${patternResult.pattern}`;
837
+ }
838
+ // For 'skip', adoClonePattern remains undefined
735
839
  }
736
840
  return { hosting: repositoryHosting, isMultiRepo, adoClonePattern, adoClonePatternResult, adoProjectSelection };
737
841
  }