specweave 0.28.19 → 0.28.22
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/plugins/specweave-ado/lib/ado-spec-sync.d.ts +16 -0
- package/dist/plugins/specweave-ado/lib/ado-spec-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-sync.js +63 -3
- package/dist/plugins/specweave-ado/lib/ado-spec-sync.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts +12 -3
- package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-status-sync.js +37 -3
- package/dist/plugins/specweave-ado/lib/ado-status-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-increment-sync-cli.d.ts +8 -6
- package/dist/plugins/specweave-github/lib/github-increment-sync-cli.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-increment-sync-cli.js +230 -165
- package/dist/plugins/specweave-github/lib/github-increment-sync-cli.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-status-sync.d.ts +10 -0
- package/dist/plugins/specweave-github/lib/github-status-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-status-sync.js +40 -2
- package/dist/plugins/specweave-github/lib/github-status-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/increment-issue-builder.d.ts +2 -0
- package/dist/plugins/specweave-github/lib/increment-issue-builder.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/increment-issue-builder.js +25 -5
- package/dist/plugins/specweave-github/lib/increment-issue-builder.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts +12 -0
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.js +57 -5
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts +5 -1
- package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-status-sync.js +12 -4
- package/dist/plugins/specweave-jira/lib/jira-status-sync.js.map +1 -1
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +7 -3
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/helpers/init/external-import.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/external-import.js +193 -23
- package/dist/src/cli/helpers/init/external-import.js.map +1 -1
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.d.ts +115 -0
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.js +590 -0
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.js.map +1 -0
- package/dist/src/cli/helpers/init/language-selection.js +1 -1
- package/dist/src/cli/helpers/init/language-selection.js.map +1 -1
- package/dist/src/cli/helpers/init/repository-setup.js +1 -1
- package/dist/src/cli/helpers/init/repository-setup.js.map +1 -1
- package/dist/src/config/types.d.ts +6 -6
- package/dist/src/core/background/index.d.ts +11 -0
- package/dist/src/core/background/index.d.ts.map +1 -0
- package/dist/src/core/background/index.js +11 -0
- package/dist/src/core/background/index.js.map +1 -0
- package/dist/src/core/background/job-manager.d.ts +65 -0
- package/dist/src/core/background/job-manager.d.ts.map +1 -0
- package/dist/src/core/background/job-manager.js +192 -0
- package/dist/src/core/background/job-manager.js.map +1 -0
- package/dist/src/core/background/types.d.ts +59 -0
- package/dist/src/core/background/types.d.ts.map +1 -0
- package/dist/src/core/background/types.js +8 -0
- package/dist/src/core/background/types.js.map +1 -0
- package/dist/src/core/repo-structure/multi-repo-configurator.d.ts +25 -0
- package/dist/src/core/repo-structure/multi-repo-configurator.d.ts.map +1 -0
- package/dist/src/core/repo-structure/multi-repo-configurator.js +614 -0
- package/dist/src/core/repo-structure/multi-repo-configurator.js.map +1 -0
- package/dist/src/core/repo-structure/prompt-consolidator.d.ts.map +1 -1
- package/dist/src/core/repo-structure/prompt-consolidator.js +6 -36
- package/dist/src/core/repo-structure/prompt-consolidator.js.map +1 -1
- package/dist/src/core/repo-structure/repo-initializer.d.ts +40 -0
- package/dist/src/core/repo-structure/repo-initializer.d.ts.map +1 -0
- package/dist/src/core/repo-structure/repo-initializer.js +260 -0
- package/dist/src/core/repo-structure/repo-initializer.js.map +1 -0
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts +3 -37
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.js +23 -803
- package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
- package/dist/src/core/types/spec-metadata.d.ts +2 -0
- package/dist/src/core/types/spec-metadata.d.ts.map +1 -1
- package/dist/src/importers/import-coordinator.d.ts +20 -0
- package/dist/src/importers/import-coordinator.d.ts.map +1 -1
- package/dist/src/importers/import-coordinator.js.map +1 -1
- package/dist/src/init/architecture/types.d.ts +2 -2
- package/dist/src/init/compliance/types.d.ts +1 -1
- package/package.json +1 -1
- package/plugins/specweave/commands/specweave-jobs.md +160 -0
- package/plugins/specweave-ado/lib/ado-spec-sync.js +59 -3
- package/plugins/specweave-ado/lib/ado-spec-sync.ts +72 -3
- package/plugins/specweave-ado/lib/ado-status-sync.js +35 -3
- package/plugins/specweave-ado/lib/ado-status-sync.ts +48 -4
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +6 -0
- package/plugins/specweave-github/lib/github-increment-sync-cli.js +268 -155
- package/plugins/specweave-github/lib/github-increment-sync-cli.ts +313 -209
- package/plugins/specweave-github/lib/github-status-sync.js +37 -1
- package/plugins/specweave-github/lib/github-status-sync.ts +60 -4
- package/plugins/specweave-github/lib/increment-issue-builder.js +26 -5
- package/plugins/specweave-github/lib/increment-issue-builder.ts +36 -5
- package/plugins/specweave-jira/lib/jira-spec-sync.js +53 -5
- package/plugins/specweave-jira/lib/jira-spec-sync.ts +87 -7
- package/plugins/specweave-jira/lib/jira-status-sync.js +9 -3
- package/plugins/specweave-jira/lib/jira-status-sync.ts +15 -6
- package/plugins/specweave-release/commands/specweave-release-npm.md +187 -8
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +9 -0
|
@@ -14,24 +14,22 @@
|
|
|
14
14
|
* - Organize specs per project/team
|
|
15
15
|
* - Split tasks between repositories
|
|
16
16
|
*/
|
|
17
|
-
import { existsSync
|
|
17
|
+
import { existsSync } from 'fs';
|
|
18
18
|
import path from 'path';
|
|
19
19
|
import chalk from 'chalk';
|
|
20
|
-
import { select, input, confirm
|
|
20
|
+
import { select, input, confirm } from '@inquirer/prompts';
|
|
21
21
|
import ora from 'ora';
|
|
22
22
|
import { execSync } from 'child_process';
|
|
23
|
-
import { execFileNoThrowSync } from '../../utils/execFileNoThrow.js';
|
|
24
|
-
import { normalizeRepoName, suggestRepoName } from './repo-id-generator.js';
|
|
25
23
|
import { SetupStateManager } from './setup-state-manager.js';
|
|
26
24
|
import { generateEnvFile } from '../../utils/env-file-generator.js';
|
|
27
25
|
import { generateSetupSummary } from './setup-summary.js';
|
|
28
|
-
import { getArchitecturePrompt,
|
|
29
|
-
import { detectRepositoryHints } from './folder-detector.js';
|
|
30
|
-
import { discoverRepositories } from './repo-bulk-discovery.js';
|
|
31
|
-
import { Octokit } from '@octokit/rest';
|
|
26
|
+
import { getArchitecturePrompt, getVisibilityPrompt, getUrlTypePrompt } from './prompt-consolidator.js';
|
|
32
27
|
import { initializeProviders } from './providers/index.js';
|
|
33
28
|
import { getPlatformRegistry } from './platform-registry.js';
|
|
34
29
|
import { getPlatformSelectionPrompt } from './prompt-consolidator.js';
|
|
30
|
+
// Extracted modules to reduce file size and prevent crashes
|
|
31
|
+
import { configureMultiRepo } from './multi-repo-configurator.js';
|
|
32
|
+
import { initializeLocalRepos as initLocalRepos, createSpecWeaveStructure as createSpecWeaveStruct } from './repo-initializer.js';
|
|
35
33
|
export class RepoStructureManager {
|
|
36
34
|
constructor(projectPath, githubToken) {
|
|
37
35
|
this.projectPath = projectPath;
|
|
@@ -125,7 +123,16 @@ export class RepoStructureManager {
|
|
|
125
123
|
return this.configureSingleRepo(urlType, platform, provider);
|
|
126
124
|
case 'parent':
|
|
127
125
|
// GitHub parent repo (pushed to GitHub)
|
|
128
|
-
return
|
|
126
|
+
return configureMultiRepo({
|
|
127
|
+
projectPath: this.projectPath,
|
|
128
|
+
githubToken: this.githubToken,
|
|
129
|
+
stateManager: this.stateManager,
|
|
130
|
+
useParent: true,
|
|
131
|
+
isLocalParent: false,
|
|
132
|
+
urlType,
|
|
133
|
+
platform: platform,
|
|
134
|
+
provider
|
|
135
|
+
});
|
|
129
136
|
default:
|
|
130
137
|
throw new Error(`Unknown architecture: ${architecture}`);
|
|
131
138
|
}
|
|
@@ -313,612 +320,6 @@ export class RepoStructureManager {
|
|
|
313
320
|
}]
|
|
314
321
|
};
|
|
315
322
|
}
|
|
316
|
-
/**
|
|
317
|
-
* Configure multi-repository architecture
|
|
318
|
-
* @param useParent - Whether to use parent repository/folder
|
|
319
|
-
* @param isLocalParent - If true, parent folder is local only (NOT pushed to GitHub)
|
|
320
|
-
* @param urlType - Git remote URL format (ssh or https)
|
|
321
|
-
* @param platform - Git hosting platform type
|
|
322
|
-
* @param provider - Git provider instance for API operations
|
|
323
|
-
*
|
|
324
|
-
* NOTE: This is primarily user-facing output (console.log/console.error).
|
|
325
|
-
* All console.* calls in this method are legitimate user-facing exceptions
|
|
326
|
-
* as defined in CONTRIBUTING.md (colored messages, confirmations, formatted output).
|
|
327
|
-
*/
|
|
328
|
-
async configureMultiRepo(useParent = true, isLocalParent = false, urlType = 'ssh', platform = 'github', provider) {
|
|
329
|
-
console.log(chalk.cyan('\n🎯 Multi-Repository Configuration\n'));
|
|
330
|
-
console.log(chalk.gray('This creates separate repositories for each service/component.\n'));
|
|
331
|
-
// Show parent repo benefits if using parent approach
|
|
332
|
-
if (useParent) {
|
|
333
|
-
console.log(chalk.blue(getParentRepoBenefits()));
|
|
334
|
-
console.log('');
|
|
335
|
-
}
|
|
336
|
-
const config = {
|
|
337
|
-
architecture: useParent ? 'parent' : 'multi-repo',
|
|
338
|
-
urlType,
|
|
339
|
-
platform,
|
|
340
|
-
provider,
|
|
341
|
-
repositories: []
|
|
342
|
-
};
|
|
343
|
-
// Save state: architecture selected
|
|
344
|
-
await this.saveSetupState({
|
|
345
|
-
version: '1.0.0',
|
|
346
|
-
architecture: useParent ? 'parent' : 'multi-repo',
|
|
347
|
-
parentRepo: undefined,
|
|
348
|
-
repos: [],
|
|
349
|
-
currentStep: 'architecture-selected',
|
|
350
|
-
timestamp: new Date().toISOString(),
|
|
351
|
-
envCreated: false
|
|
352
|
-
});
|
|
353
|
-
// ========== NEW FLOW: Ask discovery strategy FIRST (before parent questions!) ==========
|
|
354
|
-
// Step 1: Ask how to configure implementation repositories (BEFORE parent questions)
|
|
355
|
-
let discoveryStrategy = 'manual';
|
|
356
|
-
let discoveredRepos = [];
|
|
357
|
-
let owner = '';
|
|
358
|
-
if (!isLocalParent && this.githubToken && useParent) {
|
|
359
|
-
console.log(chalk.cyan('\n🚀 Repository Discovery\n'));
|
|
360
|
-
console.log(chalk.gray('You\'re setting up multiple repositories. We can discover them automatically!\n'));
|
|
361
|
-
const configMethod = await select({
|
|
362
|
-
message: 'How do you want to configure repositories?',
|
|
363
|
-
choices: [
|
|
364
|
-
{
|
|
365
|
-
name: [
|
|
366
|
-
chalk.bold.green('🎯 Bulk Discovery (RECOMMENDED)'),
|
|
367
|
-
chalk.gray(' Automatically discover repos from ' + provider.config.name),
|
|
368
|
-
chalk.gray(' • Select parent from discovered list'),
|
|
369
|
-
chalk.gray(' • Auto-configure implementation repos'),
|
|
370
|
-
chalk.gray(' • Supports: all, pattern, regex filtering'),
|
|
371
|
-
''
|
|
372
|
-
].join('\n'),
|
|
373
|
-
value: 'bulk-discovery'
|
|
374
|
-
},
|
|
375
|
-
{
|
|
376
|
-
name: [
|
|
377
|
-
chalk.bold('✏️ Manual Entry'),
|
|
378
|
-
chalk.gray(' Enter each repository manually'),
|
|
379
|
-
chalk.gray(' • Full control over settings'),
|
|
380
|
-
chalk.gray(' • Best for new repos or custom setup'),
|
|
381
|
-
''
|
|
382
|
-
].join('\n'),
|
|
383
|
-
value: 'manual'
|
|
384
|
-
}
|
|
385
|
-
],
|
|
386
|
-
default: 'bulk-discovery'
|
|
387
|
-
});
|
|
388
|
-
discoveryStrategy = configMethod;
|
|
389
|
-
}
|
|
390
|
-
// Step 2: If bulk discovery, discover repos FIRST, then ask which is parent
|
|
391
|
-
if (discoveryStrategy === 'bulk-discovery') {
|
|
392
|
-
// Get owner FIRST (needed for discovery)
|
|
393
|
-
console.log(chalk.cyan('\n👤 Repository Owner\n'));
|
|
394
|
-
owner = await input({
|
|
395
|
-
message: `${provider.config.name} owner/organization:`,
|
|
396
|
-
validate: async (val) => {
|
|
397
|
-
if (!val.trim())
|
|
398
|
-
return 'Owner is required';
|
|
399
|
-
// Validate owner exists on the platform
|
|
400
|
-
if (this.githubToken) {
|
|
401
|
-
const result = await provider.validateOwner(val, this.githubToken);
|
|
402
|
-
if (!result.valid) {
|
|
403
|
-
return result.error || `Invalid ${provider.config.name} owner`;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
return true;
|
|
407
|
-
}
|
|
408
|
-
});
|
|
409
|
-
// Discover repositories via pattern matching
|
|
410
|
-
const octokit = new Octokit({ auth: this.githubToken });
|
|
411
|
-
const isOrg = await provider.isOrganization(owner, this.githubToken);
|
|
412
|
-
// Retry loop for pattern adjustment
|
|
413
|
-
let discoveryResult = null;
|
|
414
|
-
while (discoveryResult === null) {
|
|
415
|
-
// Discovery-first flow: skip count validation since user discovers THEN selects
|
|
416
|
-
discoveryResult = await discoverRepositories(octokit, owner, isOrg, 0, { skipValidation: true });
|
|
417
|
-
// If null, user selected "go back and adjust pattern", loop will retry
|
|
418
|
-
// If user selected "manual", discoveryResult will be { repositories: [], strategy: 'manual' }
|
|
419
|
-
}
|
|
420
|
-
if (discoveryResult && discoveryResult.strategy !== 'manual') {
|
|
421
|
-
discoveredRepos = discoveryResult.repositories;
|
|
422
|
-
// Now ask: Which repo is the parent?
|
|
423
|
-
console.log(chalk.cyan('\n🏠 Select Parent Repository\n'));
|
|
424
|
-
console.log(chalk.gray('Choose which repository will be the parent (contains .specweave/ structure)\n'));
|
|
425
|
-
const parentChoices = [
|
|
426
|
-
...discoveredRepos.map((repo, index) => ({
|
|
427
|
-
name: `${chalk.bold(repo.name)}\n${chalk.gray(repo.description || '(no description)')}`,
|
|
428
|
-
value: index.toString(),
|
|
429
|
-
short: repo.name
|
|
430
|
-
})),
|
|
431
|
-
{
|
|
432
|
-
name: `${chalk.yellow('✏️ Enter parent manually')} ${chalk.gray('(not in discovered list)')}`,
|
|
433
|
-
value: 'manual',
|
|
434
|
-
short: 'Enter manually'
|
|
435
|
-
}
|
|
436
|
-
];
|
|
437
|
-
const parentSelection = await select({
|
|
438
|
-
message: 'Which repository is the parent?',
|
|
439
|
-
choices: parentChoices,
|
|
440
|
-
pageSize: 15
|
|
441
|
-
});
|
|
442
|
-
if (parentSelection === 'manual') {
|
|
443
|
-
// User wants to enter parent manually - fall back to old flow
|
|
444
|
-
discoveryStrategy = 'manual';
|
|
445
|
-
discoveredRepos = []; // Clear discovered repos
|
|
446
|
-
}
|
|
447
|
-
else {
|
|
448
|
-
// User selected a parent from discovered list
|
|
449
|
-
const parentIndex = parseInt(parentSelection);
|
|
450
|
-
const selectedParent = discoveredRepos[parentIndex];
|
|
451
|
-
// Fetch full repo details from GitHub API
|
|
452
|
-
let description = selectedParent.description || 'SpecWeave parent repository - specs, docs, and architecture';
|
|
453
|
-
let existingVisibility = selectedParent.private ? 'private' : 'public';
|
|
454
|
-
try {
|
|
455
|
-
const response = await fetch(`https://api.github.com/repos/${owner}/${selectedParent.name}`, {
|
|
456
|
-
headers: {
|
|
457
|
-
'Authorization': `Bearer ${this.githubToken}`,
|
|
458
|
-
'Accept': 'application/vnd.github+json'
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
if (response.ok) {
|
|
462
|
-
const data = await response.json();
|
|
463
|
-
description = data.description || description;
|
|
464
|
-
existingVisibility = data.private ? 'private' : 'public';
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
catch {
|
|
468
|
-
// Use defaults if fetch fails
|
|
469
|
-
}
|
|
470
|
-
// Set parent config
|
|
471
|
-
config.parentRepo = {
|
|
472
|
-
name: selectedParent.name,
|
|
473
|
-
owner: owner,
|
|
474
|
-
description: description,
|
|
475
|
-
visibility: existingVisibility,
|
|
476
|
-
createOnGitHub: false // Already exists!
|
|
477
|
-
};
|
|
478
|
-
// Remove parent from discovered repos (implementation repos = discovered - parent)
|
|
479
|
-
discoveredRepos.splice(parentIndex, 1);
|
|
480
|
-
console.log(chalk.green(`\n✓ Using existing repository: ${owner}/${selectedParent.name}\n`));
|
|
481
|
-
console.log(chalk.gray(`✓ Implementation repositories: ${discoveredRepos.length}\n`));
|
|
482
|
-
// Save state: parent repo configured
|
|
483
|
-
await this.saveSetupState({
|
|
484
|
-
version: '1.0.0',
|
|
485
|
-
architecture: useParent ? 'parent' : 'multi-repo',
|
|
486
|
-
parentRepo: config.parentRepo,
|
|
487
|
-
repos: [],
|
|
488
|
-
currentStep: 'parent-repo-configured',
|
|
489
|
-
timestamp: new Date().toISOString(),
|
|
490
|
-
envCreated: false
|
|
491
|
-
});
|
|
492
|
-
// Skip to repository configuration (lines 794+)
|
|
493
|
-
// We'll continue below with discoveredRepos
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
else {
|
|
497
|
-
// User selected manual - fall back to old flow
|
|
498
|
-
discoveryStrategy = 'manual';
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
// Step 3: Manual flow (existing logic) - only runs if discoveryStrategy === 'manual'
|
|
502
|
-
if (discoveryStrategy === 'manual' && useParent) {
|
|
503
|
-
let parentAnswers;
|
|
504
|
-
if (isLocalParent) {
|
|
505
|
-
// Local parent: Skip GitHub questions, just ask for folder name
|
|
506
|
-
console.log(chalk.blue('\n💡 Local Parent Folder Setup'));
|
|
507
|
-
console.log(chalk.gray('This folder will contain .specweave/ but will NOT be pushed to GitHub.\n'));
|
|
508
|
-
const parentNameAnswer = await input({
|
|
509
|
-
message: 'Parent folder name:',
|
|
510
|
-
default: `${path.basename(this.projectPath)}`,
|
|
511
|
-
validate: (val) => {
|
|
512
|
-
if (!val.trim())
|
|
513
|
-
return 'Folder name is required';
|
|
514
|
-
return true;
|
|
515
|
-
}
|
|
516
|
-
});
|
|
517
|
-
const ownerForLocalAnswer = await input({
|
|
518
|
-
message: `${provider.config.name} owner/organization for IMPLEMENTATION repos:`,
|
|
519
|
-
validate: async (val) => {
|
|
520
|
-
if (!val.trim())
|
|
521
|
-
return 'Owner is required';
|
|
522
|
-
// Validate owner exists on the platform
|
|
523
|
-
if (this.githubToken) {
|
|
524
|
-
const result = await provider.validateOwner(val, this.githubToken);
|
|
525
|
-
if (!result.valid) {
|
|
526
|
-
return result.error || `Invalid ${provider.config.name} owner`;
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
return true;
|
|
530
|
-
}
|
|
531
|
-
});
|
|
532
|
-
parentAnswers = {
|
|
533
|
-
parentName: parentNameAnswer,
|
|
534
|
-
owner: ownerForLocalAnswer
|
|
535
|
-
};
|
|
536
|
-
// Set defaults for local parent
|
|
537
|
-
parentAnswers.description = 'Local parent folder (not synced to GitHub)';
|
|
538
|
-
parentAnswers.createOnGitHub = false; // Never create GitHub repo for local parent
|
|
539
|
-
}
|
|
540
|
-
else {
|
|
541
|
-
// GitHub parent: First ask if using existing or creating new
|
|
542
|
-
const parentChoice = await select({
|
|
543
|
-
message: 'Parent repository setup:',
|
|
544
|
-
choices: [
|
|
545
|
-
{
|
|
546
|
-
name: `${chalk.green('Use existing parent repository')}\n${chalk.gray('Connect to an existing GitHub repo that already has .specweave/ structure')}`,
|
|
547
|
-
value: 'existing'
|
|
548
|
-
},
|
|
549
|
-
{
|
|
550
|
-
name: `${chalk.blue('Create new parent repository')}\n${chalk.gray('Create a new GitHub repo for specs, docs, and architecture')}`,
|
|
551
|
-
value: 'new'
|
|
552
|
-
}
|
|
553
|
-
],
|
|
554
|
-
default: 'new'
|
|
555
|
-
});
|
|
556
|
-
if (parentChoice === 'existing') {
|
|
557
|
-
// Using existing parent repository
|
|
558
|
-
console.log(chalk.cyan('\n📋 Existing Parent Repository\n'));
|
|
559
|
-
// Ask for owner first
|
|
560
|
-
const existingOwner = await input({
|
|
561
|
-
message: `${provider.config.name} owner/organization:`,
|
|
562
|
-
validate: async (val) => {
|
|
563
|
-
if (!val.trim())
|
|
564
|
-
return 'Owner is required';
|
|
565
|
-
// Validate owner exists on the platform
|
|
566
|
-
if (this.githubToken) {
|
|
567
|
-
const result = await provider.validateOwner(val, this.githubToken);
|
|
568
|
-
if (!result.valid) {
|
|
569
|
-
return result.error || `Invalid ${provider.config.name} owner`;
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
return true;
|
|
573
|
-
}
|
|
574
|
-
});
|
|
575
|
-
const ownerPrompt = { owner: existingOwner };
|
|
576
|
-
// Ask for existing repo name
|
|
577
|
-
const existingParentName = await input({
|
|
578
|
-
message: 'Existing parent repository name:',
|
|
579
|
-
default: `${path.basename(this.projectPath)}-parent`,
|
|
580
|
-
validate: async (val) => {
|
|
581
|
-
if (!val.trim())
|
|
582
|
-
return 'Repository name is required';
|
|
583
|
-
// Validate repository EXISTS on the platform
|
|
584
|
-
if (this.githubToken && ownerPrompt.owner) {
|
|
585
|
-
const result = await provider.validateRepository(ownerPrompt.owner, val, this.githubToken);
|
|
586
|
-
if (!result.exists) {
|
|
587
|
-
return `Repository ${ownerPrompt.owner}/${val} not found on ${provider.config.name}. Please check the name or choose 'Create new'.`;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
return true;
|
|
591
|
-
}
|
|
592
|
-
});
|
|
593
|
-
const repoPrompt = { parentName: existingParentName };
|
|
594
|
-
// Fetch description and visibility from GitHub API (or use defaults)
|
|
595
|
-
let description = 'SpecWeave parent repository - specs, docs, and architecture';
|
|
596
|
-
let existingVisibility = 'private';
|
|
597
|
-
if (this.githubToken) {
|
|
598
|
-
try {
|
|
599
|
-
const response = await fetch(`https://api.github.com/repos/${ownerPrompt.owner}/${repoPrompt.parentName}`, {
|
|
600
|
-
headers: {
|
|
601
|
-
'Authorization': `Bearer ${this.githubToken}`,
|
|
602
|
-
'Accept': 'application/vnd.github+json'
|
|
603
|
-
}
|
|
604
|
-
});
|
|
605
|
-
if (response.ok) {
|
|
606
|
-
const data = await response.json();
|
|
607
|
-
description = data.description || description;
|
|
608
|
-
existingVisibility = data.private ? 'private' : 'public';
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
catch {
|
|
612
|
-
// Use defaults if fetch fails
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
parentAnswers = {
|
|
616
|
-
owner: ownerPrompt.owner,
|
|
617
|
-
parentName: repoPrompt.parentName,
|
|
618
|
-
description: description,
|
|
619
|
-
createOnGitHub: false, // Don't create, it already exists!
|
|
620
|
-
visibility: existingVisibility // Use existing visibility from GitHub
|
|
621
|
-
};
|
|
622
|
-
console.log(chalk.green(`\n✓ Using existing repository: ${ownerPrompt.owner}/${repoPrompt.parentName}\n`));
|
|
623
|
-
}
|
|
624
|
-
else {
|
|
625
|
-
// Creating new parent repository
|
|
626
|
-
console.log(chalk.cyan('\n✨ New Parent Repository\n'));
|
|
627
|
-
// Ask for owner (separate prompt to avoid validator issues)
|
|
628
|
-
const newOwner = await input({
|
|
629
|
-
message: `${provider.config.name} owner/organization for ALL repos:`,
|
|
630
|
-
validate: async (val) => {
|
|
631
|
-
if (!val.trim())
|
|
632
|
-
return 'Owner is required';
|
|
633
|
-
// Validate owner exists on the platform
|
|
634
|
-
if (this.githubToken) {
|
|
635
|
-
const result = await provider.validateOwner(val, this.githubToken);
|
|
636
|
-
if (!result.valid) {
|
|
637
|
-
return result.error || `Invalid ${provider.config.name} owner`;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
return true;
|
|
641
|
-
}
|
|
642
|
-
});
|
|
643
|
-
const ownerPrompt = { owner: newOwner };
|
|
644
|
-
// Now ask remaining questions, using the owner value
|
|
645
|
-
const newParentName = await input({
|
|
646
|
-
message: 'Parent repository name:',
|
|
647
|
-
default: `${path.basename(this.projectPath)}-parent`,
|
|
648
|
-
validate: async (val) => {
|
|
649
|
-
if (!val.trim())
|
|
650
|
-
return 'Repository name is required';
|
|
651
|
-
// Validate repository DOESN'T exist
|
|
652
|
-
if (this.githubToken && ownerPrompt.owner) {
|
|
653
|
-
const result = await provider.validateRepository(ownerPrompt.owner, val, this.githubToken);
|
|
654
|
-
if (result.exists) {
|
|
655
|
-
return `Repository ${ownerPrompt.owner}/${val} already exists at ${result.url}. Please choose 'Use existing' or pick a different name.`;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
return true;
|
|
659
|
-
}
|
|
660
|
-
});
|
|
661
|
-
const newParentDesc = await input({
|
|
662
|
-
message: 'Parent repository description:',
|
|
663
|
-
default: 'SpecWeave parent repository - specs, docs, and architecture'
|
|
664
|
-
});
|
|
665
|
-
const createParentOnGitHub = await confirm({
|
|
666
|
-
message: 'Create parent repository on GitHub?',
|
|
667
|
-
default: true
|
|
668
|
-
});
|
|
669
|
-
// Merge the answers
|
|
670
|
-
parentAnswers = {
|
|
671
|
-
...ownerPrompt,
|
|
672
|
-
parentName: newParentName,
|
|
673
|
-
description: newParentDesc,
|
|
674
|
-
createOnGitHub: createParentOnGitHub
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
// Ask about visibility for parent repo (only if creating NEW repo on GitHub)
|
|
679
|
-
let parentVisibility = 'private';
|
|
680
|
-
if (!isLocalParent && parentAnswers.createOnGitHub) {
|
|
681
|
-
// Only prompt for visibility when creating a NEW repository
|
|
682
|
-
const parentVisibilityPrompt = getVisibilityPrompt(parentAnswers.parentName);
|
|
683
|
-
parentVisibility = await select({
|
|
684
|
-
message: parentVisibilityPrompt.question,
|
|
685
|
-
choices: parentVisibilityPrompt.options.map(opt => ({
|
|
686
|
-
name: `${opt.label}\n${chalk.gray(opt.description)}`,
|
|
687
|
-
value: opt.value
|
|
688
|
-
})),
|
|
689
|
-
default: parentVisibilityPrompt.default
|
|
690
|
-
});
|
|
691
|
-
}
|
|
692
|
-
else if (!isLocalParent && parentAnswers.visibility) {
|
|
693
|
-
// Use existing repository's visibility (fetched from GitHub API)
|
|
694
|
-
parentVisibility = parentAnswers.visibility;
|
|
695
|
-
}
|
|
696
|
-
config.parentRepo = {
|
|
697
|
-
name: parentAnswers.parentName,
|
|
698
|
-
owner: parentAnswers.owner,
|
|
699
|
-
description: parentAnswers.description,
|
|
700
|
-
visibility: parentVisibility,
|
|
701
|
-
createOnGitHub: parentAnswers.createOnGitHub
|
|
702
|
-
};
|
|
703
|
-
// Save state: parent repo configured
|
|
704
|
-
await this.saveSetupState({
|
|
705
|
-
version: '1.0.0',
|
|
706
|
-
architecture: useParent ? 'parent' : 'multi-repo',
|
|
707
|
-
parentRepo: config.parentRepo,
|
|
708
|
-
repos: [],
|
|
709
|
-
currentStep: 'parent-repo-configured',
|
|
710
|
-
timestamp: new Date().toISOString(),
|
|
711
|
-
envCreated: false
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
// Step 4: Repository count and discovery (skip count question if using bulk discovery)
|
|
715
|
-
let repoCount;
|
|
716
|
-
let bulkDiscoveryStrategy = discoveryStrategy === 'bulk-discovery' ? 'pattern' : 'manual';
|
|
717
|
-
if (discoveryStrategy === 'bulk-discovery' && discoveredRepos.length > 0) {
|
|
718
|
-
// Bulk discovery: repos already discovered, skip count question
|
|
719
|
-
repoCount = discoveredRepos.length;
|
|
720
|
-
console.log(chalk.green(`\n✓ Total repositories: ${repoCount + 1} (1 parent + ${repoCount} implementation)\n`));
|
|
721
|
-
}
|
|
722
|
-
else {
|
|
723
|
-
// Manual entry: ask for count
|
|
724
|
-
// Auto-detect existing folders
|
|
725
|
-
const hints = await detectRepositoryHints(this.projectPath);
|
|
726
|
-
if (hints.detectedFolders.length > 0) {
|
|
727
|
-
console.log(chalk.green(`\n✓ Detected ${hints.detectedFolders.length} service folder(s):`));
|
|
728
|
-
hints.detectedFolders.forEach(f => console.log(chalk.gray(` • ${f}`)));
|
|
729
|
-
console.log('');
|
|
730
|
-
}
|
|
731
|
-
// Show repository count clarification BEFORE asking
|
|
732
|
-
if (useParent && config.parentRepo) {
|
|
733
|
-
console.log(chalk.cyan('\n📊 Repository Count\n'));
|
|
734
|
-
console.log(chalk.gray('You will create:'));
|
|
735
|
-
if (isLocalParent) {
|
|
736
|
-
console.log(chalk.white(' • 1 parent FOLDER (local only, .specweave/ gitignored)'));
|
|
737
|
-
console.log(chalk.white(' • N implementation repositories (your services/apps on GitHub)'));
|
|
738
|
-
}
|
|
739
|
-
else {
|
|
740
|
-
console.log(chalk.white(' • 1 parent repository (specs, docs, increments)'));
|
|
741
|
-
console.log(chalk.white(' • N implementation repositories (your services/apps)'));
|
|
742
|
-
}
|
|
743
|
-
console.log(chalk.gray('\nNext question asks for: IMPLEMENTATION repositories ONLY (not counting parent)\n'));
|
|
744
|
-
}
|
|
745
|
-
// Ask how many implementation repositories
|
|
746
|
-
const repoCountAnswer = await number({
|
|
747
|
-
message: useParent
|
|
748
|
-
? '📦 How many IMPLEMENTATION repositories? (not counting parent)'
|
|
749
|
-
: 'How many repositories?',
|
|
750
|
-
default: hints.suggestedCount, // Use auto-detected count
|
|
751
|
-
validate: (val) => {
|
|
752
|
-
if (val === undefined || val < 1)
|
|
753
|
-
return useParent
|
|
754
|
-
? 'Need at least 1 implementation repository'
|
|
755
|
-
: 'Need at least 2 repositories';
|
|
756
|
-
if (val > 10)
|
|
757
|
-
return 'Maximum 10 repositories supported';
|
|
758
|
-
return true;
|
|
759
|
-
}
|
|
760
|
-
});
|
|
761
|
-
repoCount = repoCountAnswer ?? hints.suggestedCount;
|
|
762
|
-
// Show summary AFTER for confirmation
|
|
763
|
-
if (useParent && config.parentRepo) {
|
|
764
|
-
if (isLocalParent) {
|
|
765
|
-
console.log(chalk.green(`\n✓ Total repositories to create: ${repoCount} implementation repos`));
|
|
766
|
-
console.log(chalk.gray(` (Parent folder is local only, not counted)\n`));
|
|
767
|
-
}
|
|
768
|
-
else {
|
|
769
|
-
const totalRepos = 1 + repoCount;
|
|
770
|
-
console.log(chalk.green(`\n✓ Total repositories to create: ${totalRepos} (1 parent + ${repoCount} implementation)\n`));
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
// Bulk repository discovery for manual flow (old behavior)
|
|
774
|
-
if (this.githubToken && config.parentRepo && discoveryStrategy === 'manual') {
|
|
775
|
-
const octokit = new Octokit({ auth: this.githubToken });
|
|
776
|
-
const owner = config.parentRepo.owner;
|
|
777
|
-
const isOrg = await provider.isOrganization(owner, this.githubToken);
|
|
778
|
-
// Retry loop for pattern adjustment
|
|
779
|
-
let discoveryResult = null;
|
|
780
|
-
while (discoveryResult === null) {
|
|
781
|
-
discoveryResult = await discoverRepositories(octokit, owner, isOrg, repoCount);
|
|
782
|
-
// If null, user selected "go back and adjust pattern", loop will retry
|
|
783
|
-
// If user selected "manual", discoveryResult will be { repositories: [], strategy: 'manual' }
|
|
784
|
-
}
|
|
785
|
-
if (discoveryResult) {
|
|
786
|
-
bulkDiscoveryStrategy = discoveryResult.strategy;
|
|
787
|
-
if (discoveryResult.strategy !== 'manual') {
|
|
788
|
-
discoveredRepos = discoveryResult.repositories;
|
|
789
|
-
// Update repoCount to match discovered repos
|
|
790
|
-
if (discoveredRepos.length !== repoCount) {
|
|
791
|
-
console.log(chalk.yellow(`\n📝 Adjusting repository count from ${repoCount} to ${discoveredRepos.length} (based on discovery)\n`));
|
|
792
|
-
repoCount = discoveredRepos.length;
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
// Configure each repository
|
|
799
|
-
console.log(chalk.cyan('\n📦 Configure Each Repository:\n'));
|
|
800
|
-
const usedIds = new Set();
|
|
801
|
-
const configuredRepoNames = []; // Track configured repo names for smart ID generation
|
|
802
|
-
for (let i = 0; i < repoCount; i++) {
|
|
803
|
-
const discoveredRepo = discoveredRepos[i]; // May be undefined if manual
|
|
804
|
-
const isDiscovered = bulkDiscoveryStrategy !== 'manual' && discoveredRepo;
|
|
805
|
-
console.log(chalk.white(`\nRepository ${i + 1} of ${repoCount}:`));
|
|
806
|
-
// Smart suggestion for ALL repos (not just first one!)
|
|
807
|
-
const projectName = path.basename(this.projectPath);
|
|
808
|
-
const suggestedName = isDiscovered ? discoveredRepo.name : suggestRepoName(projectName, i, repoCount);
|
|
809
|
-
// If discovered, auto-use without confirmation (repos exist on GitHub)
|
|
810
|
-
if (isDiscovered) {
|
|
811
|
-
// Use normalized repo name as ID (repo names are unique in GitHub)
|
|
812
|
-
const repoId = normalizeRepoName(discoveredRepo.name);
|
|
813
|
-
console.log(chalk.green(` ✓ Using: ${chalk.bold(discoveredRepo.name)} ${chalk.gray(`(id: ${repoId})`)}`));
|
|
814
|
-
usedIds.add(repoId);
|
|
815
|
-
configuredRepoNames.push(discoveredRepo.name);
|
|
816
|
-
config.repositories.push({
|
|
817
|
-
id: repoId,
|
|
818
|
-
name: discoveredRepo.name,
|
|
819
|
-
owner: discoveredRepo.owner,
|
|
820
|
-
description: discoveredRepo.description || `${discoveredRepo.name} service`,
|
|
821
|
-
path: discoveredRepo.name,
|
|
822
|
-
visibility: discoveredRepo.private ? 'private' : 'public',
|
|
823
|
-
createOnGitHub: false,
|
|
824
|
-
isNested: useParent
|
|
825
|
-
});
|
|
826
|
-
continue;
|
|
827
|
-
}
|
|
828
|
-
// Manual entry (original behavior)
|
|
829
|
-
const repoName = await input({
|
|
830
|
-
message: 'Repository name:',
|
|
831
|
-
default: suggestedName,
|
|
832
|
-
validate: async (val) => {
|
|
833
|
-
if (!val.trim())
|
|
834
|
-
return 'Repository name is required';
|
|
835
|
-
// Validate repository doesn't exist (skip for discovered repos)
|
|
836
|
-
if (!isDiscovered && this.githubToken && config.parentRepo) {
|
|
837
|
-
const result = await provider.validateRepository(config.parentRepo.owner, val, this.githubToken);
|
|
838
|
-
if (result.exists) {
|
|
839
|
-
return `Repository ${config.parentRepo.owner}/${val} already exists at ${result.url}`;
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
return true;
|
|
843
|
-
}
|
|
844
|
-
});
|
|
845
|
-
const repoDescription = await input({
|
|
846
|
-
message: 'Repository description:',
|
|
847
|
-
default: `${path.basename(repoName)} service`
|
|
848
|
-
});
|
|
849
|
-
// Skip "Create on GitHub?" for discovered repos - they already exist!
|
|
850
|
-
let repoCreateOnGitHub = false;
|
|
851
|
-
if (!isDiscovered) {
|
|
852
|
-
repoCreateOnGitHub = await confirm({
|
|
853
|
-
message: 'Create this repository on GitHub?',
|
|
854
|
-
default: true
|
|
855
|
-
});
|
|
856
|
-
}
|
|
857
|
-
const repoAnswers = {
|
|
858
|
-
name: repoName,
|
|
859
|
-
description: repoDescription,
|
|
860
|
-
createOnGitHub: repoCreateOnGitHub
|
|
861
|
-
};
|
|
862
|
-
// Use normalized repo name as ID (repo names are unique)
|
|
863
|
-
const id = normalizeRepoName(repoAnswers.name);
|
|
864
|
-
console.log(chalk.green(` ✓ Repository ID: ${chalk.bold(id)}`));
|
|
865
|
-
usedIds.add(id);
|
|
866
|
-
configuredRepoNames.push(repoAnswers.name);
|
|
867
|
-
// Ask about visibility only if creating a new repository
|
|
868
|
-
let visibility = 'private';
|
|
869
|
-
if (repoAnswers.createOnGitHub) {
|
|
870
|
-
const visibilityPrompt = getVisibilityPrompt(repoAnswers.name);
|
|
871
|
-
visibility = await select({
|
|
872
|
-
message: visibilityPrompt.question,
|
|
873
|
-
choices: visibilityPrompt.options.map(opt => ({
|
|
874
|
-
name: `${opt.label}\n${chalk.gray(opt.description)}`,
|
|
875
|
-
value: opt.value
|
|
876
|
-
})),
|
|
877
|
-
default: visibilityPrompt.default
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
config.repositories.push({
|
|
881
|
-
id: id,
|
|
882
|
-
name: repoAnswers.name,
|
|
883
|
-
owner: config.parentRepo?.owner || '',
|
|
884
|
-
description: repoAnswers.description,
|
|
885
|
-
path: repoAnswers.name, // Use full repo name as folder (not short ID)
|
|
886
|
-
visibility: visibility,
|
|
887
|
-
createOnGitHub: repoAnswers.createOnGitHub,
|
|
888
|
-
isNested: useParent
|
|
889
|
-
});
|
|
890
|
-
// Save state after each repo
|
|
891
|
-
await this.saveSetupState({
|
|
892
|
-
version: '1.0.0',
|
|
893
|
-
architecture: useParent ? 'parent' : 'multi-repo',
|
|
894
|
-
parentRepo: config.parentRepo,
|
|
895
|
-
repos: config.repositories.map(r => ({
|
|
896
|
-
id: r.id,
|
|
897
|
-
repo: r.name,
|
|
898
|
-
owner: r.owner,
|
|
899
|
-
path: r.path,
|
|
900
|
-
visibility: r.visibility,
|
|
901
|
-
displayName: r.name,
|
|
902
|
-
created: false
|
|
903
|
-
})),
|
|
904
|
-
currentStep: `repo-${i + 1}-configured`,
|
|
905
|
-
timestamp: new Date().toISOString(),
|
|
906
|
-
envCreated: false
|
|
907
|
-
});
|
|
908
|
-
}
|
|
909
|
-
return config;
|
|
910
|
-
}
|
|
911
|
-
/**
|
|
912
|
-
* Save setup state for Ctrl+C recovery
|
|
913
|
-
*/
|
|
914
|
-
async saveSetupState(state) {
|
|
915
|
-
try {
|
|
916
|
-
await this.stateManager.saveState(state);
|
|
917
|
-
}
|
|
918
|
-
catch (error) {
|
|
919
|
-
console.warn(chalk.yellow(`⚠️ Failed to save setup state: ${error.message}`));
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
323
|
/**
|
|
923
324
|
* Configure monorepo
|
|
924
325
|
*/
|
|
@@ -1015,7 +416,7 @@ export class RepoStructureManager {
|
|
|
1015
416
|
}, this.githubToken);
|
|
1016
417
|
created.push(`${config.parentRepo.owner}/${config.parentRepo.name}`);
|
|
1017
418
|
// Save state: parent repo created
|
|
1018
|
-
await this.
|
|
419
|
+
await this.stateManager.saveState({
|
|
1019
420
|
version: '1.0.0',
|
|
1020
421
|
architecture: config.architecture,
|
|
1021
422
|
parentRepo: { ...config.parentRepo, url: config.provider.getRemoteUrl(config.parentRepo.owner, config.parentRepo.name, config.urlType) },
|
|
@@ -1083,7 +484,7 @@ export class RepoStructureManager {
|
|
|
1083
484
|
console.log(chalk.gray(' TIP: gh CLI auth is recommended over tokens'));
|
|
1084
485
|
console.log(chalk.yellow(' ⚠️ DO NOT commit .env to git (contains secrets!)'));
|
|
1085
486
|
// Save state: env created
|
|
1086
|
-
await this.
|
|
487
|
+
await this.stateManager.saveState({
|
|
1087
488
|
version: '1.0.0',
|
|
1088
489
|
architecture: config.architecture,
|
|
1089
490
|
parentRepo: config.parentRepo,
|
|
@@ -1192,200 +593,19 @@ export class RepoStructureManager {
|
|
|
1192
593
|
}
|
|
1193
594
|
return false;
|
|
1194
595
|
}
|
|
1195
|
-
/**
|
|
1196
|
-
* Check if a repository exists on GitHub
|
|
1197
|
-
*/
|
|
1198
|
-
async repositoryExistsOnGitHub(owner, repo) {
|
|
1199
|
-
if (!this.githubToken) {
|
|
1200
|
-
return false;
|
|
1201
|
-
}
|
|
1202
|
-
try {
|
|
1203
|
-
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
|
|
1204
|
-
headers: {
|
|
1205
|
-
'Authorization': `Bearer ${this.githubToken}`,
|
|
1206
|
-
'Accept': 'application/vnd.github+json'
|
|
1207
|
-
}
|
|
1208
|
-
});
|
|
1209
|
-
return response.ok;
|
|
1210
|
-
}
|
|
1211
|
-
catch {
|
|
1212
|
-
return false;
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
/**
|
|
1216
|
-
* Clone or initialize a repository
|
|
1217
|
-
* If the repo exists on the platform, clone it; otherwise, init + add remote
|
|
1218
|
-
*/
|
|
1219
|
-
async cloneOrInitRepository(repoPath, owner, name, createOnGitHub, urlType = 'ssh', provider) {
|
|
1220
|
-
// If .git already exists, skip
|
|
1221
|
-
if (existsSync(path.join(repoPath, '.git'))) {
|
|
1222
|
-
return;
|
|
1223
|
-
}
|
|
1224
|
-
const remoteUrl = provider.getRemoteUrl(owner, name, urlType);
|
|
1225
|
-
// Check if repository exists on GitHub
|
|
1226
|
-
const repoExists = await this.repositoryExistsOnGitHub(owner, name);
|
|
1227
|
-
if (repoExists) {
|
|
1228
|
-
// Repository exists - clone it
|
|
1229
|
-
console.log(chalk.gray(` → Cloning existing repository from GitHub...`));
|
|
1230
|
-
try {
|
|
1231
|
-
// Remove directory if it exists and is empty
|
|
1232
|
-
if (existsSync(repoPath)) {
|
|
1233
|
-
const files = require('fs').readdirSync(repoPath);
|
|
1234
|
-
if (files.length === 0) {
|
|
1235
|
-
require('fs').rmdirSync(repoPath);
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
// Clone the repository
|
|
1239
|
-
const parentDir = path.dirname(repoPath);
|
|
1240
|
-
const repoName = path.basename(repoPath);
|
|
1241
|
-
execFileNoThrowSync('git', ['clone', remoteUrl, repoName], { cwd: parentDir });
|
|
1242
|
-
console.log(chalk.green(` ✓ Cloned ${owner}/${name}`));
|
|
1243
|
-
}
|
|
1244
|
-
catch (error) {
|
|
1245
|
-
console.log(chalk.yellow(` ⚠️ Clone failed: ${error.message}`));
|
|
1246
|
-
console.log(chalk.gray(` → Falling back to init + remote`));
|
|
1247
|
-
// Fallback: init + add remote
|
|
1248
|
-
if (!existsSync(repoPath)) {
|
|
1249
|
-
mkdirSync(repoPath, { recursive: true });
|
|
1250
|
-
}
|
|
1251
|
-
execFileNoThrowSync('git', ['init'], { cwd: repoPath });
|
|
1252
|
-
execFileNoThrowSync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: repoPath });
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
else {
|
|
1256
|
-
// Repository doesn't exist - init + add remote
|
|
1257
|
-
if (!createOnGitHub) {
|
|
1258
|
-
console.log(chalk.gray(` → Repository will be created later (skipping for now)`));
|
|
1259
|
-
return;
|
|
1260
|
-
}
|
|
1261
|
-
console.log(chalk.gray(` → Initializing empty git repository...`));
|
|
1262
|
-
if (!existsSync(repoPath)) {
|
|
1263
|
-
mkdirSync(repoPath, { recursive: true });
|
|
1264
|
-
}
|
|
1265
|
-
execFileNoThrowSync('git', ['init'], { cwd: repoPath });
|
|
1266
|
-
execFileNoThrowSync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: repoPath });
|
|
1267
|
-
console.log(chalk.green(` ✓ Initialized ${owner}/${name}`));
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
596
|
/**
|
|
1271
597
|
* Initialize local git repositories
|
|
598
|
+
* Delegates to extracted repo-initializer module
|
|
1272
599
|
*/
|
|
1273
600
|
async initializeLocalRepos(config) {
|
|
1274
|
-
|
|
1275
|
-
// Create directory structure based on architecture
|
|
1276
|
-
if (config.architecture === 'parent') {
|
|
1277
|
-
// Parent repo approach: ROOT-LEVEL cloning (not services/!)
|
|
1278
|
-
// Initialize parent repo at root
|
|
1279
|
-
if (!existsSync(path.join(this.projectPath, '.git'))) {
|
|
1280
|
-
execFileNoThrowSync('git', ['init'], { cwd: this.projectPath });
|
|
1281
|
-
if (config.parentRepo) {
|
|
1282
|
-
const remoteUrl = config.provider.getRemoteUrl(config.parentRepo.owner, config.parentRepo.name, config.urlType);
|
|
1283
|
-
execFileNoThrowSync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: this.projectPath });
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
// Initialize implementation repos at ROOT LEVEL
|
|
1287
|
-
for (const repo of config.repositories) {
|
|
1288
|
-
const repoPath = path.join(this.projectPath, repo.path);
|
|
1289
|
-
// Clone or initialize repository
|
|
1290
|
-
await this.cloneOrInitRepository(repoPath, repo.owner, repo.name, repo.createOnGitHub, config.urlType, config.provider);
|
|
1291
|
-
// Create basic structure (only if repo was just initialized, not cloned)
|
|
1292
|
-
if (!repo.createOnGitHub || !await this.repositoryExistsOnGitHub(repo.owner, repo.name)) {
|
|
1293
|
-
this.createBasicRepoStructure(repoPath, repo.id);
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
else if (config.architecture === 'multi-repo') {
|
|
1298
|
-
// Standard multi-repo: repos as subdirectories
|
|
1299
|
-
for (const repo of config.repositories) {
|
|
1300
|
-
const repoPath = path.join(this.projectPath, repo.path);
|
|
1301
|
-
// Clone or initialize repository
|
|
1302
|
-
await this.cloneOrInitRepository(repoPath, repo.owner, repo.name, repo.createOnGitHub, config.urlType, config.provider);
|
|
1303
|
-
// Create basic structure (only if repo was just initialized, not cloned)
|
|
1304
|
-
if (!repo.createOnGitHub || !await this.repositoryExistsOnGitHub(repo.owner, repo.name)) {
|
|
1305
|
-
this.createBasicRepoStructure(repoPath, repo.id);
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
else {
|
|
1310
|
-
// Single repo or monorepo
|
|
1311
|
-
if (!existsSync(path.join(this.projectPath, '.git'))) {
|
|
1312
|
-
execFileNoThrowSync('git', ['init'], { cwd: this.projectPath });
|
|
1313
|
-
const repo = config.repositories[0];
|
|
1314
|
-
if (repo) {
|
|
1315
|
-
const remoteUrl = config.provider.getRemoteUrl(repo.owner, repo.name, config.urlType);
|
|
1316
|
-
execFileNoThrowSync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: this.projectPath });
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
// For monorepo, create project directories
|
|
1320
|
-
if (config.architecture === 'monorepo' && config.monorepoProjects) {
|
|
1321
|
-
for (const project of config.monorepoProjects) {
|
|
1322
|
-
const projectPath = path.join(this.projectPath, 'packages', project);
|
|
1323
|
-
if (!existsSync(projectPath)) {
|
|
1324
|
-
mkdirSync(projectPath, { recursive: true });
|
|
1325
|
-
}
|
|
1326
|
-
this.createBasicRepoStructure(projectPath, project);
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
spinner.succeed('Local repositories initialized');
|
|
1331
|
-
}
|
|
1332
|
-
/**
|
|
1333
|
-
* Create basic structure for a repository/project
|
|
1334
|
-
*
|
|
1335
|
-
* NOTE: Only creates src/ and tests/ folders for nested projects.
|
|
1336
|
-
* Documentation lives in .specweave/docs/internal/specs/{project-id}/ (source of truth)
|
|
1337
|
-
*/
|
|
1338
|
-
createBasicRepoStructure(repoPath, projectId) {
|
|
1339
|
-
// Create basic directories (NO docs/ - documentation lives in .specweave/)
|
|
1340
|
-
const dirs = ['src', 'tests'];
|
|
1341
|
-
for (const dir of dirs) {
|
|
1342
|
-
const dirPath = path.join(repoPath, dir);
|
|
1343
|
-
if (!existsSync(dirPath)) {
|
|
1344
|
-
mkdirSync(dirPath, { recursive: true });
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
// Create README.md
|
|
1348
|
-
const readmePath = path.join(repoPath, 'README.md');
|
|
1349
|
-
if (!existsSync(readmePath)) {
|
|
1350
|
-
const readmeContent = `# ${projectId}\n\n${projectId} service/component.\n\nPart of SpecWeave multi-repository project.\n`;
|
|
1351
|
-
writeFileSync(readmePath, readmeContent);
|
|
1352
|
-
}
|
|
1353
|
-
// Create .gitignore
|
|
1354
|
-
const gitignorePath = path.join(repoPath, '.gitignore');
|
|
1355
|
-
if (!existsSync(gitignorePath)) {
|
|
1356
|
-
const gitignoreContent = `node_modules/\ndist/\n.env\n.DS_Store\n*.log\n`;
|
|
1357
|
-
writeFileSync(gitignorePath, gitignoreContent);
|
|
1358
|
-
}
|
|
601
|
+
return initLocalRepos(config, this.projectPath, this.githubToken);
|
|
1359
602
|
}
|
|
1360
603
|
/**
|
|
1361
|
-
* Create SpecWeave project structure
|
|
1362
|
-
*
|
|
1363
|
-
* NOTE: As of v0.X.X (increment 0026), we ONLY create specs/ folders.
|
|
1364
|
-
* No modules/, team/, architecture/, legacy/ folders.
|
|
604
|
+
* Create SpecWeave project structure
|
|
605
|
+
* Delegates to extracted repo-initializer module
|
|
1365
606
|
*/
|
|
1366
607
|
async createSpecWeaveStructure(config) {
|
|
1367
|
-
|
|
1368
|
-
// Create project-specific spec folders (ONLY specs/, no nested folders)
|
|
1369
|
-
if (config.architecture === 'monorepo' && config.monorepoProjects) {
|
|
1370
|
-
// Monorepo: create specs folder for each project
|
|
1371
|
-
for (const project of config.monorepoProjects) {
|
|
1372
|
-
const projectSpecPath = path.join(specweavePath, 'docs', 'internal', 'specs', project.toLowerCase());
|
|
1373
|
-
if (!existsSync(projectSpecPath)) {
|
|
1374
|
-
mkdirSync(projectSpecPath, { recursive: true });
|
|
1375
|
-
}
|
|
1376
|
-
console.log(chalk.gray(` ✓ Created project structure: ${project}`));
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
else if (config.architecture === 'multi-repo' || config.architecture === 'parent') {
|
|
1380
|
-
// Multi-repo: create specs folder for each repository
|
|
1381
|
-
for (const repo of config.repositories) {
|
|
1382
|
-
const projectSpecPath = path.join(specweavePath, 'docs', 'internal', 'specs', repo.id);
|
|
1383
|
-
if (!existsSync(projectSpecPath)) {
|
|
1384
|
-
mkdirSync(projectSpecPath, { recursive: true });
|
|
1385
|
-
}
|
|
1386
|
-
console.log(chalk.gray(` ✓ Created project structure: ${repo.path}`));
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
608
|
+
return createSpecWeaveStruct(config, this.projectPath);
|
|
1389
609
|
}
|
|
1390
610
|
}
|
|
1391
611
|
//# sourceMappingURL=repo-structure-manager.js.map
|