specweave 0.24.0 ā 0.24.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.
- package/.claude-plugin/marketplace.json +55 -0
- package/CLAUDE.md +42 -0
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +80 -41
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/types.d.ts +1 -1
- package/dist/src/cli/helpers/issue-tracker/types.d.ts.map +1 -1
- package/dist/src/config/types.d.ts +24 -24
- package/dist/src/core/config/types.d.ts +25 -0
- package/dist/src/core/config/types.d.ts.map +1 -1
- package/dist/src/core/config/types.js +6 -0
- package/dist/src/core/config/types.js.map +1 -1
- package/dist/src/core/repo-structure/repo-bulk-discovery.d.ts +33 -0
- package/dist/src/core/repo-structure/repo-bulk-discovery.d.ts.map +1 -0
- package/dist/src/core/repo-structure/repo-bulk-discovery.js +275 -0
- package/dist/src/core/repo-structure/repo-bulk-discovery.js.map +1 -0
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts +9 -0
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.js +255 -87
- package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
- package/dist/src/init/architecture/types.d.ts +6 -6
- package/dist/src/utils/plugin-validator.d.ts.map +1 -1
- package/dist/src/utils/plugin-validator.js +15 -14
- package/dist/src/utils/plugin-validator.js.map +1 -1
- package/package.json +4 -4
- package/plugins/specweave/.claude-plugin/plugin.json +4 -4
- package/plugins/specweave/agents/pm/AGENT.md +2 -0
- package/plugins/specweave/commands/specweave-do.md +0 -47
- package/plugins/specweave/commands/specweave-increment.md +0 -82
- package/plugins/specweave/commands/specweave-next.md +0 -47
- package/plugins/specweave/hooks/post-task-completion.sh +67 -6
- package/plugins/specweave/hooks/pre-edit-spec.sh +11 -0
- package/plugins/specweave/hooks/pre-task-completion.sh +69 -2
- package/plugins/specweave/hooks/pre-write-spec.sh +11 -0
- package/plugins/specweave/skills/increment-planner/SKILL.md +124 -4
- package/plugins/specweave-frontend/agents/frontend-architect/AGENT.md +21 -0
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +150 -0
- package/plugins/specweave-payments/commands/stripe-setup.md +931 -0
- package/plugins/specweave-payments/commands/subscription-flow.md +1193 -0
- package/plugins/specweave-payments/commands/subscription-manage.md +386 -0
- package/plugins/specweave-payments/commands/webhook-setup.md +295 -0
- package/plugins/specweave-testing/agents/qa-engineer/AGENT.md +21 -0
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - Organize specs per project/team
|
|
15
15
|
* - Split tasks between repositories
|
|
16
16
|
*/
|
|
17
|
-
import
|
|
17
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
18
18
|
import path from 'path';
|
|
19
19
|
import chalk from 'chalk';
|
|
20
20
|
import inquirer from 'inquirer';
|
|
@@ -28,6 +28,8 @@ import { generateEnvFile } from '../../utils/env-file-generator.js';
|
|
|
28
28
|
import { generateSetupSummary } from './setup-summary.js';
|
|
29
29
|
import { getArchitecturePrompt, getParentRepoBenefits, getVisibilityPrompt } from './prompt-consolidator.js';
|
|
30
30
|
import { detectRepositoryHints } from './folder-detector.js';
|
|
31
|
+
import { discoverRepositories } from './repo-bulk-discovery.js';
|
|
32
|
+
import { Octokit } from '@octokit/rest';
|
|
31
33
|
export class RepoStructureManager {
|
|
32
34
|
constructor(projectPath, githubToken) {
|
|
33
35
|
this.projectPath = projectPath;
|
|
@@ -127,7 +129,7 @@ export class RepoStructureManager {
|
|
|
127
129
|
async configureSingleRepo() {
|
|
128
130
|
console.log(chalk.cyan('\nš¦ Single Repository Configuration\n'));
|
|
129
131
|
// Check if repo already exists
|
|
130
|
-
const hasGit =
|
|
132
|
+
const hasGit = existsSync(path.join(this.projectPath, '.git'));
|
|
131
133
|
if (hasGit) {
|
|
132
134
|
// Try to detect existing remote
|
|
133
135
|
try {
|
|
@@ -147,15 +149,36 @@ export class RepoStructureManager {
|
|
|
147
149
|
default: true
|
|
148
150
|
}]);
|
|
149
151
|
if (useExisting) {
|
|
152
|
+
// Fetch description and visibility from GitHub API
|
|
153
|
+
let description = `${repo} - SpecWeave project`;
|
|
154
|
+
let visibility = 'private';
|
|
155
|
+
if (this.githubToken) {
|
|
156
|
+
try {
|
|
157
|
+
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
|
|
158
|
+
headers: {
|
|
159
|
+
'Authorization': `Bearer ${this.githubToken}`,
|
|
160
|
+
'Accept': 'application/vnd.github+json'
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
if (response.ok) {
|
|
164
|
+
const data = await response.json();
|
|
165
|
+
description = data.description || description;
|
|
166
|
+
visibility = data.private ? 'private' : 'public';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// Use defaults if fetch fails
|
|
171
|
+
}
|
|
172
|
+
}
|
|
150
173
|
return {
|
|
151
174
|
architecture: 'single',
|
|
152
175
|
repositories: [{
|
|
153
176
|
id: 'main',
|
|
154
177
|
name: repo,
|
|
155
178
|
owner: owner,
|
|
156
|
-
description:
|
|
179
|
+
description: description,
|
|
157
180
|
path: '.',
|
|
158
|
-
visibility:
|
|
181
|
+
visibility: visibility, // Fetched from GitHub API
|
|
159
182
|
createOnGitHub: false,
|
|
160
183
|
isNested: false
|
|
161
184
|
}]
|
|
@@ -195,19 +218,23 @@ export class RepoStructureManager {
|
|
|
195
218
|
default: !hasGit
|
|
196
219
|
}
|
|
197
220
|
]);
|
|
198
|
-
// Ask about visibility
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
221
|
+
// Ask about visibility only if creating a new repository
|
|
222
|
+
let visibility = 'private';
|
|
223
|
+
if (answers.createOnGitHub) {
|
|
224
|
+
const visibilityPrompt = getVisibilityPrompt(answers.repo);
|
|
225
|
+
const result = await inquirer.prompt([{
|
|
226
|
+
type: 'list',
|
|
227
|
+
name: 'visibility',
|
|
228
|
+
message: visibilityPrompt.question,
|
|
229
|
+
choices: visibilityPrompt.options.map(opt => ({
|
|
230
|
+
name: `${opt.label}\n${chalk.gray(opt.description)}`,
|
|
231
|
+
value: opt.value,
|
|
232
|
+
short: opt.label
|
|
233
|
+
})),
|
|
234
|
+
default: visibilityPrompt.default
|
|
235
|
+
}]);
|
|
236
|
+
visibility = result.visibility;
|
|
237
|
+
}
|
|
211
238
|
return {
|
|
212
239
|
architecture: 'single',
|
|
213
240
|
repositories: [{
|
|
@@ -360,8 +387,9 @@ export class RepoStructureManager {
|
|
|
360
387
|
}
|
|
361
388
|
}
|
|
362
389
|
]);
|
|
363
|
-
// Fetch description from GitHub API (or use
|
|
390
|
+
// Fetch description and visibility from GitHub API (or use defaults)
|
|
364
391
|
let description = 'SpecWeave parent repository - specs, docs, and architecture';
|
|
392
|
+
let existingVisibility = 'private';
|
|
365
393
|
if (this.githubToken) {
|
|
366
394
|
try {
|
|
367
395
|
const response = await fetch(`https://api.github.com/repos/${ownerPrompt.owner}/${repoPrompt.parentName}`, {
|
|
@@ -373,17 +401,19 @@ export class RepoStructureManager {
|
|
|
373
401
|
if (response.ok) {
|
|
374
402
|
const data = await response.json();
|
|
375
403
|
description = data.description || description;
|
|
404
|
+
existingVisibility = data.private ? 'private' : 'public';
|
|
376
405
|
}
|
|
377
406
|
}
|
|
378
407
|
catch {
|
|
379
|
-
// Use
|
|
408
|
+
// Use defaults if fetch fails
|
|
380
409
|
}
|
|
381
410
|
}
|
|
382
411
|
parentAnswers = {
|
|
383
412
|
owner: ownerPrompt.owner,
|
|
384
413
|
parentName: repoPrompt.parentName,
|
|
385
414
|
description: description,
|
|
386
|
-
createOnGitHub: false // Don't create, it already exists!
|
|
415
|
+
createOnGitHub: false, // Don't create, it already exists!
|
|
416
|
+
visibility: existingVisibility // Use existing visibility from GitHub
|
|
387
417
|
};
|
|
388
418
|
console.log(chalk.green(`\nā Using existing repository: ${ownerPrompt.owner}/${repoPrompt.parentName}\n`));
|
|
389
419
|
}
|
|
@@ -447,9 +477,10 @@ export class RepoStructureManager {
|
|
|
447
477
|
parentAnswers = { ...ownerPrompt, ...remainingAnswers };
|
|
448
478
|
}
|
|
449
479
|
}
|
|
450
|
-
// Ask about visibility for parent repo (only if creating on GitHub)
|
|
480
|
+
// Ask about visibility for parent repo (only if creating NEW repo on GitHub)
|
|
451
481
|
let parentVisibility = 'private';
|
|
452
|
-
if (!isLocalParent) {
|
|
482
|
+
if (!isLocalParent && parentAnswers.createOnGitHub) {
|
|
483
|
+
// Only prompt for visibility when creating a NEW repository
|
|
453
484
|
const parentVisibilityPrompt = getVisibilityPrompt(parentAnswers.parentName);
|
|
454
485
|
const result = await inquirer.prompt([{
|
|
455
486
|
type: 'list',
|
|
@@ -464,6 +495,10 @@ export class RepoStructureManager {
|
|
|
464
495
|
}]);
|
|
465
496
|
parentVisibility = result.parentVisibility;
|
|
466
497
|
}
|
|
498
|
+
else if (!isLocalParent && parentAnswers.visibility) {
|
|
499
|
+
// Use existing repository's visibility (fetched from GitHub API)
|
|
500
|
+
parentVisibility = parentAnswers.visibility;
|
|
501
|
+
}
|
|
467
502
|
config.parentRepo = {
|
|
468
503
|
name: parentAnswers.parentName,
|
|
469
504
|
owner: parentAnswers.owner,
|
|
@@ -532,26 +567,85 @@ export class RepoStructureManager {
|
|
|
532
567
|
console.log(chalk.green(`\nā Total repositories to create: ${totalRepos} (1 parent + ${repoCount} implementation)\n`));
|
|
533
568
|
}
|
|
534
569
|
}
|
|
570
|
+
// Bulk repository discovery (optimization for many repos)
|
|
571
|
+
let discoveredRepos = [];
|
|
572
|
+
let bulkDiscoveryStrategy = 'manual';
|
|
573
|
+
if (this.githubToken && config.parentRepo) {
|
|
574
|
+
const octokit = new Octokit({ auth: this.githubToken });
|
|
575
|
+
const owner = config.parentRepo.owner;
|
|
576
|
+
const isOrg = await this.isGitHubOrganization(owner);
|
|
577
|
+
const discoveryResult = await discoverRepositories(octokit, owner, isOrg, repoCount);
|
|
578
|
+
if (discoveryResult) {
|
|
579
|
+
bulkDiscoveryStrategy = discoveryResult.strategy;
|
|
580
|
+
if (discoveryResult.strategy !== 'manual') {
|
|
581
|
+
discoveredRepos = discoveryResult.repositories;
|
|
582
|
+
// Update repoCount to match discovered repos
|
|
583
|
+
if (discoveredRepos.length !== repoCount) {
|
|
584
|
+
console.log(chalk.yellow(`\nš Adjusting repository count from ${repoCount} to ${discoveredRepos.length} (based on discovery)\n`));
|
|
585
|
+
// Override repoCount with discovered count
|
|
586
|
+
repoCount = discoveredRepos.length;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
535
591
|
// Configure each repository
|
|
536
592
|
console.log(chalk.cyan('\nš¦ Configure Each Repository:\n'));
|
|
537
593
|
const usedIds = new Set();
|
|
538
594
|
const configuredRepoNames = []; // Track configured repo names for smart ID generation
|
|
539
595
|
for (let i = 0; i < repoCount; i++) {
|
|
596
|
+
const discoveredRepo = discoveredRepos[i]; // May be undefined if manual
|
|
597
|
+
const isDiscovered = bulkDiscoveryStrategy !== 'manual' && discoveredRepo;
|
|
540
598
|
console.log(chalk.white(`\nRepository ${i + 1} of ${repoCount}:`));
|
|
541
599
|
// Smart suggestion for ALL repos (not just first one!)
|
|
542
600
|
const projectName = path.basename(this.projectPath);
|
|
543
|
-
const suggestedName = suggestRepoName(projectName, i, repoCount);
|
|
601
|
+
const suggestedName = isDiscovered ? discoveredRepo.name : suggestRepoName(projectName, i, repoCount);
|
|
602
|
+
// If discovered, show preview and skip prompts (or allow confirmation)
|
|
603
|
+
if (isDiscovered) {
|
|
604
|
+
console.log(chalk.green(` ā Discovered: ${chalk.bold(discoveredRepo.name)}`));
|
|
605
|
+
console.log(chalk.gray(` Description: ${discoveredRepo.description || '(none)'}`));
|
|
606
|
+
console.log(chalk.gray(` Visibility: ${discoveredRepo.private ? 'Private' : 'Public'}`));
|
|
607
|
+
const { confirmDiscovered } = await inquirer.prompt([{
|
|
608
|
+
type: 'confirm',
|
|
609
|
+
name: 'confirmDiscovered',
|
|
610
|
+
message: 'Use this repository configuration?',
|
|
611
|
+
default: true
|
|
612
|
+
}]);
|
|
613
|
+
if (!confirmDiscovered) {
|
|
614
|
+
// Allow manual override
|
|
615
|
+
console.log(chalk.yellow(` ā Switching to manual entry for this repository\n`));
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
// Use discovered repo configuration directly
|
|
619
|
+
const smartId = generateRepoIdSmart(discoveredRepo.name, configuredRepoNames);
|
|
620
|
+
const { id: suggestedId } = ensureUniqueId(smartId, usedIds);
|
|
621
|
+
console.log(chalk.green(` ā Repository ID: ${chalk.bold(suggestedId)} ${chalk.gray('(auto-generated)')}`));
|
|
622
|
+
usedIds.add(suggestedId);
|
|
623
|
+
configuredRepoNames.push(discoveredRepo.name);
|
|
624
|
+
config.repositories.push({
|
|
625
|
+
id: suggestedId,
|
|
626
|
+
name: discoveredRepo.name,
|
|
627
|
+
owner: discoveredRepo.owner,
|
|
628
|
+
description: discoveredRepo.description || `${discoveredRepo.name} service`,
|
|
629
|
+
path: useParent ? suggestedId : suggestedId,
|
|
630
|
+
visibility: discoveredRepo.private ? 'private' : 'public',
|
|
631
|
+
createOnGitHub: false, // Already exists!
|
|
632
|
+
isNested: useParent
|
|
633
|
+
});
|
|
634
|
+
continue; // Skip manual prompts
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
// Manual entry (original behavior)
|
|
544
638
|
const repoAnswers = await inquirer.prompt([
|
|
545
639
|
{
|
|
546
640
|
type: 'input',
|
|
547
641
|
name: 'name',
|
|
548
642
|
message: 'Repository name:',
|
|
549
|
-
default: suggestedName,
|
|
643
|
+
default: suggestedName,
|
|
550
644
|
validate: async (input) => {
|
|
551
645
|
if (!input.trim())
|
|
552
646
|
return 'Repository name is required';
|
|
553
|
-
// Validate repository doesn't exist
|
|
554
|
-
if (this.githubToken && config.parentRepo) {
|
|
647
|
+
// Validate repository doesn't exist (skip for discovered repos)
|
|
648
|
+
if (!isDiscovered && this.githubToken && config.parentRepo) {
|
|
555
649
|
const result = await validateRepository(config.parentRepo.owner, input, this.githubToken);
|
|
556
650
|
if (result.exists) {
|
|
557
651
|
return `Repository ${config.parentRepo.owner}/${input} already exists at ${result.url}`;
|
|
@@ -570,7 +664,7 @@ export class RepoStructureManager {
|
|
|
570
664
|
type: 'confirm',
|
|
571
665
|
name: 'createOnGitHub',
|
|
572
666
|
message: 'Create this repository on GitHub?',
|
|
573
|
-
default: true
|
|
667
|
+
default: !isDiscovered // Default to true for new repos, false for discovered
|
|
574
668
|
}
|
|
575
669
|
]);
|
|
576
670
|
// Smart auto-generate ID from repository name (context-aware)
|
|
@@ -616,19 +710,23 @@ export class RepoStructureManager {
|
|
|
616
710
|
}
|
|
617
711
|
usedIds.add(id);
|
|
618
712
|
configuredRepoNames.push(repoAnswers.name);
|
|
619
|
-
// Ask about visibility
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
713
|
+
// Ask about visibility only if creating a new repository
|
|
714
|
+
let visibility = 'private';
|
|
715
|
+
if (repoAnswers.createOnGitHub) {
|
|
716
|
+
const visibilityPrompt = getVisibilityPrompt(repoAnswers.name);
|
|
717
|
+
const result = await inquirer.prompt([{
|
|
718
|
+
type: 'list',
|
|
719
|
+
name: 'visibility',
|
|
720
|
+
message: visibilityPrompt.question,
|
|
721
|
+
choices: visibilityPrompt.options.map(opt => ({
|
|
722
|
+
name: `${opt.label}\n${chalk.gray(opt.description)}`,
|
|
723
|
+
value: opt.value,
|
|
724
|
+
short: opt.label
|
|
725
|
+
})),
|
|
726
|
+
default: visibilityPrompt.default
|
|
727
|
+
}]);
|
|
728
|
+
visibility = result.visibility;
|
|
729
|
+
}
|
|
632
730
|
config.repositories.push({
|
|
633
731
|
id: id,
|
|
634
732
|
name: repoAnswers.name,
|
|
@@ -713,22 +811,26 @@ export class RepoStructureManager {
|
|
|
713
811
|
type: 'confirm',
|
|
714
812
|
name: 'createOnGitHub',
|
|
715
813
|
message: 'Create repository on GitHub?',
|
|
716
|
-
default: !
|
|
814
|
+
default: !existsSync(path.join(this.projectPath, '.git'))
|
|
717
815
|
}
|
|
718
816
|
]);
|
|
719
|
-
// Ask about visibility
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
817
|
+
// Ask about visibility only if creating a new repository
|
|
818
|
+
let visibility = 'private';
|
|
819
|
+
if (answers.createOnGitHub) {
|
|
820
|
+
const visibilityPrompt = getVisibilityPrompt(answers.repo);
|
|
821
|
+
const result = await inquirer.prompt([{
|
|
822
|
+
type: 'list',
|
|
823
|
+
name: 'visibility',
|
|
824
|
+
message: visibilityPrompt.question,
|
|
825
|
+
choices: visibilityPrompt.options.map(opt => ({
|
|
826
|
+
name: `${opt.label}\n${chalk.gray(opt.description)}`,
|
|
827
|
+
value: opt.value,
|
|
828
|
+
short: opt.label
|
|
829
|
+
})),
|
|
830
|
+
default: visibilityPrompt.default
|
|
831
|
+
}]);
|
|
832
|
+
visibility = result.visibility;
|
|
833
|
+
}
|
|
732
834
|
const projects = answers.projects.split(',').map((p) => p.trim());
|
|
733
835
|
return {
|
|
734
836
|
architecture: 'monorepo',
|
|
@@ -942,6 +1044,81 @@ export class RepoStructureManager {
|
|
|
942
1044
|
}
|
|
943
1045
|
return false;
|
|
944
1046
|
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Check if a repository exists on GitHub
|
|
1049
|
+
*/
|
|
1050
|
+
async repositoryExistsOnGitHub(owner, repo) {
|
|
1051
|
+
if (!this.githubToken) {
|
|
1052
|
+
return false;
|
|
1053
|
+
}
|
|
1054
|
+
try {
|
|
1055
|
+
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
|
|
1056
|
+
headers: {
|
|
1057
|
+
'Authorization': `Bearer ${this.githubToken}`,
|
|
1058
|
+
'Accept': 'application/vnd.github+json'
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
return response.ok;
|
|
1062
|
+
}
|
|
1063
|
+
catch {
|
|
1064
|
+
return false;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Clone or initialize a repository
|
|
1069
|
+
* If the repo exists on GitHub, clone it; otherwise, init + add remote
|
|
1070
|
+
*/
|
|
1071
|
+
async cloneOrInitRepository(repoPath, owner, name, createOnGitHub) {
|
|
1072
|
+
// If .git already exists, skip
|
|
1073
|
+
if (existsSync(path.join(repoPath, '.git'))) {
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
const remoteUrl = `https://github.com/${owner}/${name}.git`;
|
|
1077
|
+
// Check if repository exists on GitHub
|
|
1078
|
+
const repoExists = await this.repositoryExistsOnGitHub(owner, name);
|
|
1079
|
+
if (repoExists) {
|
|
1080
|
+
// Repository exists - clone it
|
|
1081
|
+
console.log(chalk.gray(` ā Cloning existing repository from GitHub...`));
|
|
1082
|
+
try {
|
|
1083
|
+
// Remove directory if it exists and is empty
|
|
1084
|
+
if (existsSync(repoPath)) {
|
|
1085
|
+
const files = require('fs').readdirSync(repoPath);
|
|
1086
|
+
if (files.length === 0) {
|
|
1087
|
+
require('fs').rmdirSync(repoPath);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
// Clone the repository
|
|
1091
|
+
const parentDir = path.dirname(repoPath);
|
|
1092
|
+
const repoName = path.basename(repoPath);
|
|
1093
|
+
execFileNoThrowSync('git', ['clone', remoteUrl, repoName], { cwd: parentDir });
|
|
1094
|
+
console.log(chalk.green(` ā Cloned ${owner}/${name}`));
|
|
1095
|
+
}
|
|
1096
|
+
catch (error) {
|
|
1097
|
+
console.log(chalk.yellow(` ā ļø Clone failed: ${error.message}`));
|
|
1098
|
+
console.log(chalk.gray(` ā Falling back to init + remote`));
|
|
1099
|
+
// Fallback: init + add remote
|
|
1100
|
+
if (!existsSync(repoPath)) {
|
|
1101
|
+
mkdirSync(repoPath, { recursive: true });
|
|
1102
|
+
}
|
|
1103
|
+
execFileNoThrowSync('git', ['init'], { cwd: repoPath });
|
|
1104
|
+
execFileNoThrowSync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: repoPath });
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
else {
|
|
1108
|
+
// Repository doesn't exist - init + add remote
|
|
1109
|
+
if (!createOnGitHub) {
|
|
1110
|
+
console.log(chalk.gray(` ā Repository will be created later (skipping for now)`));
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
console.log(chalk.gray(` ā Initializing empty git repository...`));
|
|
1114
|
+
if (!existsSync(repoPath)) {
|
|
1115
|
+
mkdirSync(repoPath, { recursive: true });
|
|
1116
|
+
}
|
|
1117
|
+
execFileNoThrowSync('git', ['init'], { cwd: repoPath });
|
|
1118
|
+
execFileNoThrowSync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: repoPath });
|
|
1119
|
+
console.log(chalk.green(` ā Initialized ${owner}/${name}`));
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
945
1122
|
/**
|
|
946
1123
|
* Initialize local git repositories
|
|
947
1124
|
*/
|
|
@@ -951,7 +1128,7 @@ export class RepoStructureManager {
|
|
|
951
1128
|
if (config.architecture === 'parent') {
|
|
952
1129
|
// Parent repo approach: ROOT-LEVEL cloning (not services/!)
|
|
953
1130
|
// Initialize parent repo at root
|
|
954
|
-
if (!
|
|
1131
|
+
if (!existsSync(path.join(this.projectPath, '.git'))) {
|
|
955
1132
|
execFileNoThrowSync('git', ['init'], { cwd: this.projectPath });
|
|
956
1133
|
if (config.parentRepo) {
|
|
957
1134
|
const remoteUrl = `https://github.com/${config.parentRepo.owner}/${config.parentRepo.name}.git`;
|
|
@@ -961,38 +1138,29 @@ export class RepoStructureManager {
|
|
|
961
1138
|
// Initialize implementation repos at ROOT LEVEL
|
|
962
1139
|
for (const repo of config.repositories) {
|
|
963
1140
|
const repoPath = path.join(this.projectPath, repo.path);
|
|
964
|
-
//
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
if (!fs.existsSync(path.join(repoPath, '.git'))) {
|
|
970
|
-
execFileNoThrowSync('git', ['init'], { cwd: repoPath });
|
|
971
|
-
const remoteUrl = `https://github.com/${repo.owner}/${repo.name}.git`;
|
|
972
|
-
execFileNoThrowSync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: repoPath });
|
|
1141
|
+
// Clone or initialize repository
|
|
1142
|
+
await this.cloneOrInitRepository(repoPath, repo.owner, repo.name, repo.createOnGitHub);
|
|
1143
|
+
// Create basic structure (only if repo was just initialized, not cloned)
|
|
1144
|
+
if (!repo.createOnGitHub || !await this.repositoryExistsOnGitHub(repo.owner, repo.name)) {
|
|
1145
|
+
this.createBasicRepoStructure(repoPath, repo.id);
|
|
973
1146
|
}
|
|
974
|
-
// Create basic structure
|
|
975
|
-
this.createBasicRepoStructure(repoPath, repo.id);
|
|
976
1147
|
}
|
|
977
1148
|
}
|
|
978
1149
|
else if (config.architecture === 'multi-repo') {
|
|
979
1150
|
// Standard multi-repo: repos as subdirectories
|
|
980
1151
|
for (const repo of config.repositories) {
|
|
981
1152
|
const repoPath = path.join(this.projectPath, repo.path);
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
if (!
|
|
986
|
-
|
|
987
|
-
const remoteUrl = `https://github.com/${repo.owner}/${repo.name}.git`;
|
|
988
|
-
execFileNoThrowSync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: repoPath });
|
|
1153
|
+
// Clone or initialize repository
|
|
1154
|
+
await this.cloneOrInitRepository(repoPath, repo.owner, repo.name, repo.createOnGitHub);
|
|
1155
|
+
// Create basic structure (only if repo was just initialized, not cloned)
|
|
1156
|
+
if (!repo.createOnGitHub || !await this.repositoryExistsOnGitHub(repo.owner, repo.name)) {
|
|
1157
|
+
this.createBasicRepoStructure(repoPath, repo.id);
|
|
989
1158
|
}
|
|
990
|
-
this.createBasicRepoStructure(repoPath, repo.id);
|
|
991
1159
|
}
|
|
992
1160
|
}
|
|
993
1161
|
else {
|
|
994
1162
|
// Single repo or monorepo
|
|
995
|
-
if (!
|
|
1163
|
+
if (!existsSync(path.join(this.projectPath, '.git'))) {
|
|
996
1164
|
execFileNoThrowSync('git', ['init'], { cwd: this.projectPath });
|
|
997
1165
|
const repo = config.repositories[0];
|
|
998
1166
|
if (repo) {
|
|
@@ -1004,8 +1172,8 @@ export class RepoStructureManager {
|
|
|
1004
1172
|
if (config.architecture === 'monorepo' && config.monorepoProjects) {
|
|
1005
1173
|
for (const project of config.monorepoProjects) {
|
|
1006
1174
|
const projectPath = path.join(this.projectPath, 'packages', project);
|
|
1007
|
-
if (!
|
|
1008
|
-
|
|
1175
|
+
if (!existsSync(projectPath)) {
|
|
1176
|
+
mkdirSync(projectPath, { recursive: true });
|
|
1009
1177
|
}
|
|
1010
1178
|
this.createBasicRepoStructure(projectPath, project);
|
|
1011
1179
|
}
|
|
@@ -1024,21 +1192,21 @@ export class RepoStructureManager {
|
|
|
1024
1192
|
const dirs = ['src', 'tests'];
|
|
1025
1193
|
for (const dir of dirs) {
|
|
1026
1194
|
const dirPath = path.join(repoPath, dir);
|
|
1027
|
-
if (!
|
|
1028
|
-
|
|
1195
|
+
if (!existsSync(dirPath)) {
|
|
1196
|
+
mkdirSync(dirPath, { recursive: true });
|
|
1029
1197
|
}
|
|
1030
1198
|
}
|
|
1031
1199
|
// Create README.md
|
|
1032
1200
|
const readmePath = path.join(repoPath, 'README.md');
|
|
1033
|
-
if (!
|
|
1201
|
+
if (!existsSync(readmePath)) {
|
|
1034
1202
|
const readmeContent = `# ${projectId}\n\n${projectId} service/component.\n\nPart of SpecWeave multi-repository project.\n`;
|
|
1035
|
-
|
|
1203
|
+
writeFileSync(readmePath, readmeContent);
|
|
1036
1204
|
}
|
|
1037
1205
|
// Create .gitignore
|
|
1038
1206
|
const gitignorePath = path.join(repoPath, '.gitignore');
|
|
1039
|
-
if (!
|
|
1207
|
+
if (!existsSync(gitignorePath)) {
|
|
1040
1208
|
const gitignoreContent = `node_modules/\ndist/\n.env\n.DS_Store\n*.log\n`;
|
|
1041
|
-
|
|
1209
|
+
writeFileSync(gitignorePath, gitignoreContent);
|
|
1042
1210
|
}
|
|
1043
1211
|
}
|
|
1044
1212
|
/**
|
|
@@ -1054,8 +1222,8 @@ export class RepoStructureManager {
|
|
|
1054
1222
|
// Monorepo: create specs folder for each project
|
|
1055
1223
|
for (const project of config.monorepoProjects) {
|
|
1056
1224
|
const projectSpecPath = path.join(specweavePath, 'docs', 'internal', 'specs', project.toLowerCase());
|
|
1057
|
-
if (!
|
|
1058
|
-
|
|
1225
|
+
if (!existsSync(projectSpecPath)) {
|
|
1226
|
+
mkdirSync(projectSpecPath, { recursive: true });
|
|
1059
1227
|
}
|
|
1060
1228
|
console.log(chalk.gray(` ā Created project structure: ${project}`));
|
|
1061
1229
|
}
|
|
@@ -1064,8 +1232,8 @@ export class RepoStructureManager {
|
|
|
1064
1232
|
// Multi-repo: create specs folder for each repository
|
|
1065
1233
|
for (const repo of config.repositories) {
|
|
1066
1234
|
const projectSpecPath = path.join(specweavePath, 'docs', 'internal', 'specs', repo.id);
|
|
1067
|
-
if (!
|
|
1068
|
-
|
|
1235
|
+
if (!existsSync(projectSpecPath)) {
|
|
1236
|
+
mkdirSync(projectSpecPath, { recursive: true });
|
|
1069
1237
|
}
|
|
1070
1238
|
console.log(chalk.gray(` ā Created project structure: ${repo.id}`));
|
|
1071
1239
|
}
|