specweave 1.0.15 → 1.0.17
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/src/cli/helpers/init/external-import.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/external-import.js +108 -0
- package/dist/src/cli/helpers/init/external-import.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/github-multi-repo.d.ts +1 -1
- package/dist/src/cli/helpers/issue-tracker/github-multi-repo.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js +3 -12
- package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/github.d.ts +2 -5
- package/dist/src/cli/helpers/issue-tracker/github.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/github.js +9 -81
- package/dist/src/cli/helpers/issue-tracker/github.js.map +1 -1
- package/dist/src/core/repo-structure/multi-repo-configurator.d.ts +4 -3
- package/dist/src/core/repo-structure/multi-repo-configurator.d.ts.map +1 -1
- package/dist/src/core/repo-structure/multi-repo-configurator.js +103 -434
- package/dist/src/core/repo-structure/multi-repo-configurator.js.map +1 -1
- package/dist/src/core/repo-structure/prompt-consolidator.d.ts +1 -1
- package/dist/src/core/repo-structure/prompt-consolidator.d.ts.map +1 -1
- package/dist/src/core/repo-structure/prompt-consolidator.js +14 -18
- package/dist/src/core/repo-structure/prompt-consolidator.js.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts +6 -2
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.js +13 -39
- package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
- package/dist/src/core/repo-structure/setup-state-manager.d.ts +4 -0
- package/dist/src/core/repo-structure/setup-state-manager.d.ts.map +1 -1
- package/dist/src/core/repo-structure/setup-state-manager.js.map +1 -1
- package/dist/src/importers/github-importer.d.ts.map +1 -1
- package/dist/src/importers/github-importer.js +14 -1
- package/dist/src/importers/github-importer.js.map +1 -1
- package/dist/src/importers/import-coordinator.d.ts.map +1 -1
- package/dist/src/importers/import-coordinator.js +7 -0
- package/dist/src/importers/import-coordinator.js.map +1 -1
- package/package.json +1 -1
- package/dist/src/cli/commands/validate-parent-repo.d.ts +0 -8
- package/dist/src/cli/commands/validate-parent-repo.d.ts.map +0 -1
- package/dist/src/cli/commands/validate-parent-repo.js +0 -15
- package/dist/src/cli/commands/validate-parent-repo.js.map +0 -1
- package/dist/src/core/cicd/parent-repo-validator.d.ts +0 -42
- package/dist/src/core/cicd/parent-repo-validator.d.ts.map +0 -1
- package/dist/src/core/cicd/parent-repo-validator.js +0 -201
- package/dist/src/core/cicd/parent-repo-validator.js.map +0 -1
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
* Multi-Repository Configuration
|
|
3
3
|
*
|
|
4
4
|
* Extracted from repo-structure-manager.ts to reduce file size and prevent crashes.
|
|
5
|
-
* Handles configuration of multi-repository setups
|
|
5
|
+
* Handles configuration of multi-repository setups with repository discovery.
|
|
6
|
+
*
|
|
7
|
+
* v1.0.13: REMOVED parent repo concept - all repos are equal, first is default.
|
|
6
8
|
*/
|
|
7
9
|
import path from 'path';
|
|
8
10
|
import chalk from 'chalk';
|
|
9
11
|
import { select, input, confirm, number } from '@inquirer/prompts';
|
|
10
12
|
import { Octokit } from '@octokit/rest';
|
|
11
13
|
import { normalizeRepoName, suggestRepoName } from './repo-id-generator.js';
|
|
12
|
-
import {
|
|
14
|
+
import { getVisibilityPrompt, } from './prompt-consolidator.js';
|
|
13
15
|
import { detectRepositoryHints } from './folder-detector.js';
|
|
14
16
|
import { discoverRepositories } from './repo-bulk-discovery.js';
|
|
15
17
|
/**
|
|
@@ -25,19 +27,16 @@ async function saveSetupState(stateManager, state) {
|
|
|
25
27
|
}
|
|
26
28
|
/**
|
|
27
29
|
* Configure multi-repository architecture
|
|
30
|
+
* v1.0.13: All repos are equal - no parent concept. First repo is default.
|
|
28
31
|
* @param options - Configuration options
|
|
29
32
|
*/
|
|
30
33
|
export async function configureMultiRepo(options) {
|
|
31
|
-
const { projectPath, githubToken, stateManager,
|
|
34
|
+
const { projectPath, githubToken, stateManager, urlType = 'ssh', platform = 'github', provider } = options;
|
|
32
35
|
console.log(chalk.cyan('\n Multi-Repository Configuration\n'));
|
|
33
36
|
console.log(chalk.gray('This creates separate repositories for each service/component.\n'));
|
|
34
|
-
|
|
35
|
-
if (useParent) {
|
|
36
|
-
console.log(chalk.blue(getParentRepoBenefits()));
|
|
37
|
-
console.log('');
|
|
38
|
-
}
|
|
37
|
+
console.log(chalk.gray('All repositories are equal - first one will be the default for issues.\n'));
|
|
39
38
|
const config = {
|
|
40
|
-
architecture:
|
|
39
|
+
architecture: 'multi-repo',
|
|
41
40
|
urlType,
|
|
42
41
|
platform,
|
|
43
42
|
provider,
|
|
@@ -46,19 +45,17 @@ export async function configureMultiRepo(options) {
|
|
|
46
45
|
// Save state: architecture selected
|
|
47
46
|
await saveSetupState(stateManager, {
|
|
48
47
|
version: '1.0.0',
|
|
49
|
-
architecture:
|
|
50
|
-
parentRepo: undefined,
|
|
48
|
+
architecture: 'multi-repo',
|
|
51
49
|
repos: [],
|
|
52
50
|
currentStep: 'architecture-selected',
|
|
53
51
|
timestamp: new Date().toISOString(),
|
|
54
52
|
envCreated: false
|
|
55
53
|
});
|
|
56
|
-
//
|
|
57
|
-
// Step 1: Ask how to configure implementation repositories (BEFORE parent questions)
|
|
54
|
+
// Ask discovery strategy
|
|
58
55
|
let discoveryStrategy = 'manual';
|
|
59
56
|
let discoveredRepos = [];
|
|
60
57
|
let owner = '';
|
|
61
|
-
if (
|
|
58
|
+
if (githubToken) {
|
|
62
59
|
console.log(chalk.cyan('\n Repository Discovery\n'));
|
|
63
60
|
console.log(chalk.gray('You\'re setting up multiple repositories. We can discover them automatically!\n'));
|
|
64
61
|
const configMethod = await select({
|
|
@@ -68,8 +65,7 @@ export async function configureMultiRepo(options) {
|
|
|
68
65
|
name: [
|
|
69
66
|
chalk.bold.green(' Bulk Discovery (RECOMMENDED)'),
|
|
70
67
|
chalk.gray(' Automatically discover repos from ' + provider.config.name),
|
|
71
|
-
chalk.gray(' -
|
|
72
|
-
chalk.gray(' - Auto-configure implementation repos'),
|
|
68
|
+
chalk.gray(' - Auto-configure all repos'),
|
|
73
69
|
chalk.gray(' - Supports: all, pattern, regex filtering'),
|
|
74
70
|
''
|
|
75
71
|
].join('\n'),
|
|
@@ -90,9 +86,9 @@ export async function configureMultiRepo(options) {
|
|
|
90
86
|
});
|
|
91
87
|
discoveryStrategy = configMethod;
|
|
92
88
|
}
|
|
93
|
-
//
|
|
89
|
+
// Bulk discovery flow
|
|
94
90
|
if (discoveryStrategy === 'bulk-discovery') {
|
|
95
|
-
// Get owner
|
|
91
|
+
// Get owner first (needed for discovery)
|
|
96
92
|
console.log(chalk.cyan('\n Repository Owner\n'));
|
|
97
93
|
owner = await input({
|
|
98
94
|
message: `${provider.config.name} owner/organization:`,
|
|
@@ -122,424 +118,108 @@ export async function configureMultiRepo(options) {
|
|
|
122
118
|
}
|
|
123
119
|
if (discoveryResult && discoveryResult.strategy !== 'manual') {
|
|
124
120
|
discoveredRepos = discoveryResult.repositories;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
];
|
|
140
|
-
const parentSelection = await select({
|
|
141
|
-
message: 'Which repository is the parent?',
|
|
142
|
-
choices: parentChoices,
|
|
143
|
-
pageSize: 15
|
|
144
|
-
});
|
|
145
|
-
if (parentSelection === 'manual') {
|
|
146
|
-
// User wants to enter parent manually - fall back to old flow
|
|
147
|
-
discoveryStrategy = 'manual';
|
|
148
|
-
discoveredRepos = []; // Clear discovered repos
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
// User selected a parent from discovered list
|
|
152
|
-
const parentIndex = parseInt(parentSelection);
|
|
153
|
-
const selectedParent = discoveredRepos[parentIndex];
|
|
154
|
-
// Fetch full repo details from GitHub API
|
|
155
|
-
let description = selectedParent.description || 'SpecWeave parent repository - specs, docs, and architecture';
|
|
156
|
-
let existingVisibility = selectedParent.private ? 'private' : 'public';
|
|
157
|
-
try {
|
|
158
|
-
const response = await fetch(`https://api.github.com/repos/${owner}/${selectedParent.name}`, {
|
|
159
|
-
headers: {
|
|
160
|
-
'Authorization': `Bearer ${githubToken}`,
|
|
161
|
-
'Accept': 'application/vnd.github+json'
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
if (response.ok) {
|
|
165
|
-
const data = await response.json();
|
|
166
|
-
description = data.description || description;
|
|
167
|
-
existingVisibility = data.private ? 'private' : 'public';
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
catch {
|
|
171
|
-
// Use defaults if fetch fails
|
|
172
|
-
}
|
|
173
|
-
// Set parent config
|
|
174
|
-
config.parentRepo = {
|
|
175
|
-
name: selectedParent.name,
|
|
176
|
-
owner: owner,
|
|
177
|
-
description: description,
|
|
178
|
-
visibility: existingVisibility,
|
|
179
|
-
createOnGitHub: false // Already exists!
|
|
180
|
-
};
|
|
181
|
-
// Remove parent from discovered repos (implementation repos = discovered - parent)
|
|
182
|
-
discoveredRepos.splice(parentIndex, 1);
|
|
183
|
-
console.log(chalk.green(`\n Using existing repository: ${owner}/${selectedParent.name}\n`));
|
|
184
|
-
console.log(chalk.gray(` Implementation repositories: ${discoveredRepos.length}\n`));
|
|
185
|
-
// Save state: parent repo configured
|
|
186
|
-
await saveSetupState(stateManager, {
|
|
187
|
-
version: '1.0.0',
|
|
188
|
-
architecture: useParent ? 'parent' : 'multi-repo',
|
|
189
|
-
parentRepo: config.parentRepo,
|
|
190
|
-
repos: [],
|
|
191
|
-
currentStep: 'parent-repo-configured',
|
|
192
|
-
timestamp: new Date().toISOString(),
|
|
193
|
-
envCreated: false
|
|
121
|
+
console.log(chalk.green(`\n Discovered ${discoveredRepos.length} repositories\n`));
|
|
122
|
+
// Configure all discovered repos - first is default
|
|
123
|
+
for (let i = 0; i < discoveredRepos.length; i++) {
|
|
124
|
+
const repo = discoveredRepos[i];
|
|
125
|
+
const repoId = normalizeRepoName(repo.name);
|
|
126
|
+
config.repositories.push({
|
|
127
|
+
id: repoId,
|
|
128
|
+
name: repo.name,
|
|
129
|
+
owner: repo.owner,
|
|
130
|
+
description: repo.description || `${repo.name} service`,
|
|
131
|
+
path: repo.name,
|
|
132
|
+
visibility: repo.private ? 'private' : 'public',
|
|
133
|
+
createOnGitHub: false,
|
|
134
|
+
isNested: false
|
|
194
135
|
});
|
|
195
|
-
|
|
196
|
-
// We'll continue below with discoveredRepos
|
|
136
|
+
console.log(chalk.green(` ${i === 0 ? '(default) ' : ''}${repo.name} ${chalk.gray(`(id: ${repoId})`)}`));
|
|
197
137
|
}
|
|
138
|
+
// Save state
|
|
139
|
+
await saveSetupState(stateManager, {
|
|
140
|
+
version: '1.0.0',
|
|
141
|
+
architecture: 'multi-repo',
|
|
142
|
+
repos: config.repositories.map(r => ({
|
|
143
|
+
id: r.id,
|
|
144
|
+
repo: r.name,
|
|
145
|
+
owner: r.owner,
|
|
146
|
+
path: r.path,
|
|
147
|
+
visibility: r.visibility,
|
|
148
|
+
displayName: r.name,
|
|
149
|
+
created: false
|
|
150
|
+
})),
|
|
151
|
+
currentStep: 'repos-configured',
|
|
152
|
+
timestamp: new Date().toISOString(),
|
|
153
|
+
envCreated: false
|
|
154
|
+
});
|
|
155
|
+
return config;
|
|
198
156
|
}
|
|
199
157
|
else {
|
|
200
|
-
// User selected manual - fall back to
|
|
158
|
+
// User selected manual - fall back to manual flow
|
|
201
159
|
discoveryStrategy = 'manual';
|
|
202
160
|
}
|
|
203
161
|
}
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
validate: (val) => {
|
|
215
|
-
if (!val.trim())
|
|
216
|
-
return 'Folder name is required';
|
|
217
|
-
return true;
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
const ownerForLocalAnswer = await input({
|
|
221
|
-
message: `${provider.config.name} owner/organization for IMPLEMENTATION repos:`,
|
|
222
|
-
validate: async (val) => {
|
|
223
|
-
if (!val.trim())
|
|
224
|
-
return 'Owner is required';
|
|
225
|
-
// Validate owner exists on the platform
|
|
226
|
-
if (githubToken) {
|
|
227
|
-
const result = await provider.validateOwner(val, githubToken);
|
|
228
|
-
if (!result.valid) {
|
|
229
|
-
return result.error || `Invalid ${provider.config.name} owner`;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return true;
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
parentAnswers = {
|
|
236
|
-
parentName: parentNameAnswer,
|
|
237
|
-
owner: ownerForLocalAnswer
|
|
238
|
-
};
|
|
239
|
-
// Set defaults for local parent
|
|
240
|
-
parentAnswers.description = 'Local parent folder (not synced to GitHub)';
|
|
241
|
-
parentAnswers.createOnGitHub = false; // Never create GitHub repo for local parent
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
// GitHub parent: First ask if using existing or creating new
|
|
245
|
-
const parentChoice = await select({
|
|
246
|
-
message: 'Parent repository setup:',
|
|
247
|
-
choices: [
|
|
248
|
-
{
|
|
249
|
-
name: `${chalk.green('Use existing parent repository')}\n${chalk.gray('Connect to an existing GitHub repo that already has .specweave/ structure')}`,
|
|
250
|
-
value: 'existing'
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
name: `${chalk.blue('Create new parent repository')}\n${chalk.gray('Create a new GitHub repo for specs, docs, and architecture')}`,
|
|
254
|
-
value: 'new'
|
|
255
|
-
}
|
|
256
|
-
],
|
|
257
|
-
default: 'new'
|
|
258
|
-
});
|
|
259
|
-
if (parentChoice === 'existing') {
|
|
260
|
-
// Using existing parent repository
|
|
261
|
-
console.log(chalk.cyan('\n Existing Parent Repository\n'));
|
|
262
|
-
// Ask for owner first
|
|
263
|
-
const existingOwner = await input({
|
|
264
|
-
message: `${provider.config.name} owner/organization:`,
|
|
265
|
-
validate: async (val) => {
|
|
266
|
-
if (!val.trim())
|
|
267
|
-
return 'Owner is required';
|
|
268
|
-
// Validate owner exists on the platform
|
|
269
|
-
if (githubToken) {
|
|
270
|
-
const result = await provider.validateOwner(val, githubToken);
|
|
271
|
-
if (!result.valid) {
|
|
272
|
-
return result.error || `Invalid ${provider.config.name} owner`;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
return true;
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
const ownerPrompt = { owner: existingOwner };
|
|
279
|
-
// Ask for existing repo name
|
|
280
|
-
const existingParentName = await input({
|
|
281
|
-
message: 'Existing parent repository name:',
|
|
282
|
-
default: `${path.basename(projectPath)}-parent`,
|
|
283
|
-
validate: async (val) => {
|
|
284
|
-
if (!val.trim())
|
|
285
|
-
return 'Repository name is required';
|
|
286
|
-
// Validate repository EXISTS on the platform
|
|
287
|
-
if (githubToken && ownerPrompt.owner) {
|
|
288
|
-
const result = await provider.validateRepository(ownerPrompt.owner, val, githubToken);
|
|
289
|
-
if (!result.exists) {
|
|
290
|
-
return `Repository ${ownerPrompt.owner}/${val} not found on ${provider.config.name}. Please check the name or choose 'Create new'.`;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
return true;
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
const repoPrompt = { parentName: existingParentName };
|
|
297
|
-
// Fetch description and visibility from GitHub API (or use defaults)
|
|
298
|
-
let description = 'SpecWeave parent repository - specs, docs, and architecture';
|
|
299
|
-
let existingVisibility = 'private';
|
|
162
|
+
// Manual flow
|
|
163
|
+
// Ask for owner
|
|
164
|
+
if (!owner) {
|
|
165
|
+
console.log(chalk.cyan('\n Repository Owner\n'));
|
|
166
|
+
owner = await input({
|
|
167
|
+
message: `${provider.config.name} owner/organization:`,
|
|
168
|
+
validate: async (val) => {
|
|
169
|
+
if (!val.trim())
|
|
170
|
+
return 'Owner is required';
|
|
171
|
+
// Validate owner exists on the platform
|
|
300
172
|
if (githubToken) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
'Authorization': `Bearer ${githubToken}`,
|
|
305
|
-
'Accept': 'application/vnd.github+json'
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
if (response.ok) {
|
|
309
|
-
const data = await response.json();
|
|
310
|
-
description = data.description || description;
|
|
311
|
-
existingVisibility = data.private ? 'private' : 'public';
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
catch {
|
|
315
|
-
// Use defaults if fetch fails
|
|
173
|
+
const result = await provider.validateOwner(val, githubToken);
|
|
174
|
+
if (!result.valid) {
|
|
175
|
+
return result.error || `Invalid ${provider.config.name} owner`;
|
|
316
176
|
}
|
|
317
177
|
}
|
|
318
|
-
|
|
319
|
-
owner: ownerPrompt.owner,
|
|
320
|
-
parentName: repoPrompt.parentName,
|
|
321
|
-
description: description,
|
|
322
|
-
createOnGitHub: false, // Don't create, it already exists!
|
|
323
|
-
visibility: existingVisibility // Use existing visibility from GitHub
|
|
324
|
-
};
|
|
325
|
-
console.log(chalk.green(`\n Using existing repository: ${ownerPrompt.owner}/${repoPrompt.parentName}\n`));
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
// Creating new parent repository
|
|
329
|
-
console.log(chalk.cyan('\n New Parent Repository\n'));
|
|
330
|
-
// Ask for owner (separate prompt to avoid validator issues)
|
|
331
|
-
const newOwner = await input({
|
|
332
|
-
message: `${provider.config.name} owner/organization for ALL repos:`,
|
|
333
|
-
validate: async (val) => {
|
|
334
|
-
if (!val.trim())
|
|
335
|
-
return 'Owner is required';
|
|
336
|
-
// Validate owner exists on the platform
|
|
337
|
-
if (githubToken) {
|
|
338
|
-
const result = await provider.validateOwner(val, githubToken);
|
|
339
|
-
if (!result.valid) {
|
|
340
|
-
return result.error || `Invalid ${provider.config.name} owner`;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
return true;
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
const ownerPrompt = { owner: newOwner };
|
|
347
|
-
// Now ask remaining questions, using the owner value
|
|
348
|
-
const newParentName = await input({
|
|
349
|
-
message: 'Parent repository name:',
|
|
350
|
-
default: `${path.basename(projectPath)}-parent`,
|
|
351
|
-
validate: async (val) => {
|
|
352
|
-
if (!val.trim())
|
|
353
|
-
return 'Repository name is required';
|
|
354
|
-
// Validate repository DOESN'T exist
|
|
355
|
-
if (githubToken && ownerPrompt.owner) {
|
|
356
|
-
const result = await provider.validateRepository(ownerPrompt.owner, val, githubToken);
|
|
357
|
-
if (result.exists) {
|
|
358
|
-
return `Repository ${ownerPrompt.owner}/${val} already exists at ${result.url}. Please choose 'Use existing' or pick a different name.`;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
return true;
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
const newParentDesc = await input({
|
|
365
|
-
message: 'Parent repository description:',
|
|
366
|
-
default: 'SpecWeave parent repository - specs, docs, and architecture'
|
|
367
|
-
});
|
|
368
|
-
const createParentOnGitHub = await confirm({
|
|
369
|
-
message: 'Create parent repository on GitHub?',
|
|
370
|
-
default: true
|
|
371
|
-
});
|
|
372
|
-
// Merge the answers
|
|
373
|
-
parentAnswers = {
|
|
374
|
-
...ownerPrompt,
|
|
375
|
-
parentName: newParentName,
|
|
376
|
-
description: newParentDesc,
|
|
377
|
-
createOnGitHub: createParentOnGitHub
|
|
378
|
-
};
|
|
178
|
+
return true;
|
|
379
179
|
}
|
|
380
|
-
}
|
|
381
|
-
// Ask about visibility for parent repo (only if creating NEW repo on GitHub)
|
|
382
|
-
let parentVisibility = 'private';
|
|
383
|
-
if (!isLocalParent && parentAnswers.createOnGitHub) {
|
|
384
|
-
// Only prompt for visibility when creating a NEW repository
|
|
385
|
-
const parentVisibilityPrompt = getVisibilityPrompt(parentAnswers.parentName);
|
|
386
|
-
parentVisibility = await select({
|
|
387
|
-
message: parentVisibilityPrompt.question,
|
|
388
|
-
choices: parentVisibilityPrompt.options.map(opt => ({
|
|
389
|
-
name: `${opt.label}\n${chalk.gray(opt.description)}`,
|
|
390
|
-
value: opt.value
|
|
391
|
-
})),
|
|
392
|
-
default: parentVisibilityPrompt.default
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
else if (!isLocalParent && parentAnswers.visibility) {
|
|
396
|
-
// Use existing repository's visibility (fetched from GitHub API)
|
|
397
|
-
parentVisibility = parentAnswers.visibility;
|
|
398
|
-
}
|
|
399
|
-
config.parentRepo = {
|
|
400
|
-
name: parentAnswers.parentName,
|
|
401
|
-
owner: parentAnswers.owner,
|
|
402
|
-
description: parentAnswers.description,
|
|
403
|
-
visibility: parentVisibility,
|
|
404
|
-
createOnGitHub: parentAnswers.createOnGitHub
|
|
405
|
-
};
|
|
406
|
-
// Save state: parent repo configured
|
|
407
|
-
await saveSetupState(stateManager, {
|
|
408
|
-
version: '1.0.0',
|
|
409
|
-
architecture: useParent ? 'parent' : 'multi-repo',
|
|
410
|
-
parentRepo: config.parentRepo,
|
|
411
|
-
repos: [],
|
|
412
|
-
currentStep: 'parent-repo-configured',
|
|
413
|
-
timestamp: new Date().toISOString(),
|
|
414
|
-
envCreated: false
|
|
415
180
|
});
|
|
416
181
|
}
|
|
417
|
-
//
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
console.log(chalk.green(`\n Total repositories: ${repoCount + 1} (1 parent + ${repoCount} implementation)\n`));
|
|
182
|
+
// Auto-detect existing folders
|
|
183
|
+
const hints = await detectRepositoryHints(projectPath);
|
|
184
|
+
if (hints.detectedFolders.length > 0) {
|
|
185
|
+
console.log(chalk.green(`\n Detected ${hints.detectedFolders.length} service folder(s):`));
|
|
186
|
+
hints.detectedFolders.forEach(f => console.log(chalk.gray(` - ${f}`)));
|
|
187
|
+
console.log('');
|
|
424
188
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (useParent && config.parentRepo) {
|
|
436
|
-
console.log(chalk.cyan('\n Repository Count\n'));
|
|
437
|
-
console.log(chalk.gray('You will create:'));
|
|
438
|
-
if (isLocalParent) {
|
|
439
|
-
console.log(chalk.white(' - 1 parent FOLDER (local only, .specweave/ gitignored)'));
|
|
440
|
-
console.log(chalk.white(' - N implementation repositories (your services/apps on GitHub)'));
|
|
441
|
-
}
|
|
442
|
-
else {
|
|
443
|
-
console.log(chalk.white(' - 1 parent repository (specs, docs, increments)'));
|
|
444
|
-
console.log(chalk.white(' - N implementation repositories (your services/apps)'));
|
|
445
|
-
}
|
|
446
|
-
console.log(chalk.gray('\nNext question asks for: IMPLEMENTATION repositories ONLY (not counting parent)\n'));
|
|
189
|
+
// Ask how many repositories
|
|
190
|
+
const repoCountAnswer = await number({
|
|
191
|
+
message: 'How many repositories?',
|
|
192
|
+
default: hints.suggestedCount >= 2 ? hints.suggestedCount : 2,
|
|
193
|
+
validate: (val) => {
|
|
194
|
+
if (val === undefined || val < 2)
|
|
195
|
+
return 'Need at least 2 repositories';
|
|
196
|
+
if (val > 10)
|
|
197
|
+
return 'Maximum 10 repositories supported';
|
|
198
|
+
return true;
|
|
447
199
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
message: useParent
|
|
451
|
-
? ' How many IMPLEMENTATION repositories? (not counting parent)'
|
|
452
|
-
: 'How many repositories?',
|
|
453
|
-
default: hints.suggestedCount, // Use auto-detected count
|
|
454
|
-
validate: (val) => {
|
|
455
|
-
if (val === undefined || val < 1)
|
|
456
|
-
return useParent
|
|
457
|
-
? 'Need at least 1 implementation repository'
|
|
458
|
-
: 'Need at least 2 repositories';
|
|
459
|
-
if (val > 10)
|
|
460
|
-
return 'Maximum 10 repositories supported';
|
|
461
|
-
return true;
|
|
462
|
-
}
|
|
463
|
-
});
|
|
464
|
-
repoCount = repoCountAnswer ?? hints.suggestedCount;
|
|
465
|
-
// Show summary AFTER for confirmation
|
|
466
|
-
if (useParent && config.parentRepo) {
|
|
467
|
-
if (isLocalParent) {
|
|
468
|
-
console.log(chalk.green(`\n Total repositories to create: ${repoCount} implementation repos`));
|
|
469
|
-
console.log(chalk.gray(` (Parent folder is local only, not counted)\n`));
|
|
470
|
-
}
|
|
471
|
-
else {
|
|
472
|
-
const totalRepos = 1 + repoCount;
|
|
473
|
-
console.log(chalk.green(`\n Total repositories to create: ${totalRepos} (1 parent + ${repoCount} implementation)\n`));
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
// Bulk repository discovery for manual flow (old behavior)
|
|
477
|
-
if (githubToken && config.parentRepo && discoveryStrategy === 'manual') {
|
|
478
|
-
const octokit = new Octokit({ auth: githubToken });
|
|
479
|
-
const discoveryOwner = config.parentRepo.owner;
|
|
480
|
-
const isOrg = await provider.isOrganization(discoveryOwner, githubToken);
|
|
481
|
-
// Retry loop for pattern adjustment
|
|
482
|
-
let discoveryResult = null;
|
|
483
|
-
while (discoveryResult === null) {
|
|
484
|
-
discoveryResult = await discoverRepositories(octokit, discoveryOwner, isOrg, repoCount);
|
|
485
|
-
// If null, user selected "go back and adjust pattern", loop will retry
|
|
486
|
-
// If user selected "manual", discoveryResult will be { repositories: [], strategy: 'manual' }
|
|
487
|
-
}
|
|
488
|
-
if (discoveryResult) {
|
|
489
|
-
bulkDiscoveryStrategy = discoveryResult.strategy;
|
|
490
|
-
if (discoveryResult.strategy !== 'manual') {
|
|
491
|
-
discoveredRepos = discoveryResult.repositories;
|
|
492
|
-
// Update repoCount to match discovered repos
|
|
493
|
-
if (discoveredRepos.length !== repoCount) {
|
|
494
|
-
console.log(chalk.yellow(`\n Adjusting repository count from ${repoCount} to ${discoveredRepos.length} (based on discovery)\n`));
|
|
495
|
-
repoCount = discoveredRepos.length;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
200
|
+
});
|
|
201
|
+
const repoCount = repoCountAnswer ?? 2;
|
|
501
202
|
// Configure each repository
|
|
502
203
|
console.log(chalk.cyan('\n Configure Each Repository:\n'));
|
|
204
|
+
console.log(chalk.gray('First repository will be the default for issue tracking.\n'));
|
|
503
205
|
const usedIds = new Set();
|
|
504
|
-
const configuredRepoNames = []; // Track configured repo names for smart ID generation
|
|
505
206
|
for (let i = 0; i < repoCount; i++) {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
console.log(chalk.white(`\nRepository ${i + 1} of ${repoCount}:`));
|
|
509
|
-
// Smart suggestion for ALL repos (not just first one!)
|
|
207
|
+
console.log(chalk.white(`\nRepository ${i + 1} of ${repoCount}${i === 0 ? chalk.yellow(' (default)') : ''}:`));
|
|
208
|
+
// Smart suggestion
|
|
510
209
|
const projectName = path.basename(projectPath);
|
|
511
|
-
const suggestedName =
|
|
512
|
-
//
|
|
513
|
-
if (isDiscovered) {
|
|
514
|
-
// Use normalized repo name as ID (repo names are unique in GitHub)
|
|
515
|
-
const repoId = normalizeRepoName(discoveredRepo.name);
|
|
516
|
-
console.log(chalk.green(` Using: ${chalk.bold(discoveredRepo.name)} ${chalk.gray(`(id: ${repoId})`)}`));
|
|
517
|
-
usedIds.add(repoId);
|
|
518
|
-
configuredRepoNames.push(discoveredRepo.name);
|
|
519
|
-
config.repositories.push({
|
|
520
|
-
id: repoId,
|
|
521
|
-
name: discoveredRepo.name,
|
|
522
|
-
owner: discoveredRepo.owner,
|
|
523
|
-
description: discoveredRepo.description || `${discoveredRepo.name} service`,
|
|
524
|
-
path: discoveredRepo.name,
|
|
525
|
-
visibility: discoveredRepo.private ? 'private' : 'public',
|
|
526
|
-
createOnGitHub: false,
|
|
527
|
-
isNested: useParent
|
|
528
|
-
});
|
|
529
|
-
continue;
|
|
530
|
-
}
|
|
531
|
-
// Manual entry (original behavior)
|
|
210
|
+
const suggestedName = suggestRepoName(projectName, i, repoCount);
|
|
211
|
+
// Manual entry
|
|
532
212
|
const repoName = await input({
|
|
533
213
|
message: 'Repository name:',
|
|
534
214
|
default: suggestedName,
|
|
535
215
|
validate: async (val) => {
|
|
536
216
|
if (!val.trim())
|
|
537
217
|
return 'Repository name is required';
|
|
538
|
-
// Validate repository doesn't exist
|
|
539
|
-
if (
|
|
540
|
-
const result = await provider.validateRepository(
|
|
218
|
+
// Validate repository doesn't exist
|
|
219
|
+
if (githubToken) {
|
|
220
|
+
const result = await provider.validateRepository(owner, val, githubToken);
|
|
541
221
|
if (result.exists) {
|
|
542
|
-
return `Repository ${
|
|
222
|
+
return `Repository ${owner}/${val} already exists at ${result.url}`;
|
|
543
223
|
}
|
|
544
224
|
}
|
|
545
225
|
return true;
|
|
@@ -549,28 +229,18 @@ export async function configureMultiRepo(options) {
|
|
|
549
229
|
message: 'Repository description:',
|
|
550
230
|
default: `${path.basename(repoName)} service`
|
|
551
231
|
});
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
message: 'Create this repository on GitHub?',
|
|
557
|
-
default: true
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
const repoAnswers = {
|
|
561
|
-
name: repoName,
|
|
562
|
-
description: repoDescription,
|
|
563
|
-
createOnGitHub: repoCreateOnGitHub
|
|
564
|
-
};
|
|
232
|
+
const repoCreateOnGitHub = await confirm({
|
|
233
|
+
message: 'Create this repository on GitHub?',
|
|
234
|
+
default: true
|
|
235
|
+
});
|
|
565
236
|
// Use normalized repo name as ID (repo names are unique)
|
|
566
|
-
const id = normalizeRepoName(
|
|
237
|
+
const id = normalizeRepoName(repoName);
|
|
567
238
|
console.log(chalk.green(` Repository ID: ${chalk.bold(id)}`));
|
|
568
239
|
usedIds.add(id);
|
|
569
|
-
configuredRepoNames.push(repoAnswers.name);
|
|
570
240
|
// Ask about visibility only if creating a new repository
|
|
571
241
|
let visibility = 'private';
|
|
572
|
-
if (
|
|
573
|
-
const visibilityPrompt = getVisibilityPrompt(
|
|
242
|
+
if (repoCreateOnGitHub) {
|
|
243
|
+
const visibilityPrompt = getVisibilityPrompt(repoName);
|
|
574
244
|
visibility = await select({
|
|
575
245
|
message: visibilityPrompt.question,
|
|
576
246
|
choices: visibilityPrompt.options.map(opt => ({
|
|
@@ -582,19 +252,18 @@ export async function configureMultiRepo(options) {
|
|
|
582
252
|
}
|
|
583
253
|
config.repositories.push({
|
|
584
254
|
id: id,
|
|
585
|
-
name:
|
|
586
|
-
owner:
|
|
587
|
-
description:
|
|
588
|
-
path:
|
|
255
|
+
name: repoName,
|
|
256
|
+
owner: owner,
|
|
257
|
+
description: repoDescription,
|
|
258
|
+
path: repoName,
|
|
589
259
|
visibility: visibility,
|
|
590
|
-
createOnGitHub:
|
|
591
|
-
isNested:
|
|
260
|
+
createOnGitHub: repoCreateOnGitHub,
|
|
261
|
+
isNested: false
|
|
592
262
|
});
|
|
593
263
|
// Save state after each repo
|
|
594
264
|
await saveSetupState(stateManager, {
|
|
595
265
|
version: '1.0.0',
|
|
596
|
-
architecture:
|
|
597
|
-
parentRepo: config.parentRepo,
|
|
266
|
+
architecture: 'multi-repo',
|
|
598
267
|
repos: config.repositories.map(r => ({
|
|
599
268
|
id: r.id,
|
|
600
269
|
repo: r.name,
|