@girardmedia/bootspring 2.5.0 → 2.5.2
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/README.md +9 -403
- package/bin/bootspring.js +1 -96
- package/dist/cli/index.js +65134 -0
- package/dist/cli-launcher.js +92 -0
- package/dist/core/index.d.ts +2110 -5582
- package/dist/core/index.js +2 -0
- package/dist/core.js +21123 -5413
- package/dist/mcp/index.d.ts +357 -1
- package/dist/mcp/index.js +2 -0
- package/dist/mcp-server.js +51948 -1976
- package/package.json +27 -63
- package/scripts/postinstall.cjs +144 -0
- package/LICENSE +0 -29
- package/dist/cli/index.cjs +0 -20776
- package/generators/api-docs.js +0 -827
- package/generators/decisions.js +0 -655
- package/generators/generate.js +0 -595
- package/generators/health.js +0 -942
- package/generators/index.ts +0 -82
- package/generators/presets/full.js +0 -28
- package/generators/presets/index.js +0 -12
- package/generators/presets/minimal.js +0 -29
- package/generators/presets/standard.js +0 -28
- package/generators/questionnaire.js +0 -414
- package/generators/sections/advanced.js +0 -136
- package/generators/sections/ai.js +0 -106
- package/generators/sections/auth.js +0 -89
- package/generators/sections/backend.js +0 -146
- package/generators/sections/business.js +0 -118
- package/generators/sections/content.js +0 -300
- package/generators/sections/deployment.js +0 -139
- package/generators/sections/features.js +0 -122
- package/generators/sections/frontend.js +0 -118
- package/generators/sections/identity.js +0 -76
- package/generators/sections/index.js +0 -40
- package/generators/sections/instructions.js +0 -146
- package/generators/sections/payments.js +0 -104
- package/generators/sections/plugins.js +0 -142
- package/generators/sections/pre-build.js +0 -130
- package/generators/sections/security.js +0 -127
- package/generators/sections/technical.js +0 -171
- package/generators/sections/testing.js +0 -125
- package/generators/sections/workflow.js +0 -104
- package/generators/sprint.js +0 -675
- package/generators/templates/agents.template.js +0 -199
- package/generators/templates/assistant-context.template.js +0 -83
- package/generators/templates/build-planning.template.js +0 -708
- package/generators/templates/claude.template.js +0 -379
- package/generators/templates/content.template.js +0 -819
- package/generators/templates/index.js +0 -16
- package/generators/templates/planning.template.js +0 -515
- package/generators/templates/seed.template.js +0 -109
- package/generators/visual-doc-generator.js +0 -910
- package/scripts/postinstall.js +0 -197
- /package/{claude-commands → assets/claude-commands}/agent.md +0 -0
- /package/{claude-commands → assets/claude-commands}/bs.md +0 -0
- /package/{claude-commands → assets/claude-commands}/build.md +0 -0
- /package/{claude-commands → assets/claude-commands}/skill.md +0 -0
- /package/{claude-commands → assets/claude-commands}/todo.md +0 -0
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Security Section
|
|
3
|
-
*
|
|
4
|
-
* Security requirements, compliance, and best practices.
|
|
5
|
-
*
|
|
6
|
-
* @package bootspring
|
|
7
|
-
* @module generators/sections/security
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const title = 'Security Requirements';
|
|
11
|
-
const description = 'Security and compliance setup';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Run the security section
|
|
15
|
-
*/
|
|
16
|
-
async function run(rl, previousAnswers, helpers) {
|
|
17
|
-
const { askChoice, askMultiSelect, askYesNo } = helpers;
|
|
18
|
-
const answers = {};
|
|
19
|
-
|
|
20
|
-
// Security Level
|
|
21
|
-
const levelOptions = [
|
|
22
|
-
{ label: 'Basic', value: 'basic', description: 'Standard security practices' },
|
|
23
|
-
{ label: 'Standard', value: 'standard', description: 'Industry best practices' },
|
|
24
|
-
{ label: 'High', value: 'high', description: 'Enhanced security measures' },
|
|
25
|
-
{ label: 'Enterprise', value: 'enterprise', description: 'Maximum security' }
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
answers.securityLevel = await askChoice(
|
|
29
|
-
rl,
|
|
30
|
-
'Security level:',
|
|
31
|
-
levelOptions,
|
|
32
|
-
1
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
// Security Features
|
|
36
|
-
const featureOptions = [
|
|
37
|
-
{ label: 'HTTPS enforcement', value: 'https' },
|
|
38
|
-
{ label: 'CSP headers', value: 'csp' },
|
|
39
|
-
{ label: 'Rate limiting', value: 'ratelimit' },
|
|
40
|
-
{ label: 'Input sanitization', value: 'sanitization' },
|
|
41
|
-
{ label: 'SQL injection prevention', value: 'sqli' },
|
|
42
|
-
{ label: 'XSS prevention', value: 'xss' },
|
|
43
|
-
{ label: 'CSRF protection', value: 'csrf' },
|
|
44
|
-
{ label: 'Secret scanning', value: 'secrets' }
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
// Default based on security level
|
|
48
|
-
const defaultFeatures = answers.securityLevel === 'basic'
|
|
49
|
-
? ['https', 'sanitization']
|
|
50
|
-
: answers.securityLevel === 'standard'
|
|
51
|
-
? ['https', 'csp', 'ratelimit', 'sanitization', 'xss']
|
|
52
|
-
: ['https', 'csp', 'ratelimit', 'sanitization', 'sqli', 'xss', 'csrf', 'secrets'];
|
|
53
|
-
|
|
54
|
-
answers.securityFeatures = await askMultiSelect(
|
|
55
|
-
rl,
|
|
56
|
-
'Security features:',
|
|
57
|
-
featureOptions,
|
|
58
|
-
defaultFeatures
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
// Compliance Requirements
|
|
62
|
-
const complianceOptions = [
|
|
63
|
-
{ label: 'None', value: 'none' },
|
|
64
|
-
{ label: 'GDPR', value: 'gdpr' },
|
|
65
|
-
{ label: 'SOC 2', value: 'soc2' },
|
|
66
|
-
{ label: 'HIPAA', value: 'hipaa' },
|
|
67
|
-
{ label: 'PCI DSS', value: 'pci' },
|
|
68
|
-
{ label: 'CCPA', value: 'ccpa' }
|
|
69
|
-
];
|
|
70
|
-
|
|
71
|
-
answers.compliance = await askMultiSelect(
|
|
72
|
-
rl,
|
|
73
|
-
'Compliance requirements:',
|
|
74
|
-
complianceOptions,
|
|
75
|
-
[]
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
// Data handling
|
|
79
|
-
if (answers.compliance.length > 0 && !answers.compliance.includes('none')) {
|
|
80
|
-
const dataOptions = [
|
|
81
|
-
{ label: 'Data encryption at rest', value: 'encryption-rest' },
|
|
82
|
-
{ label: 'Data encryption in transit', value: 'encryption-transit' },
|
|
83
|
-
{ label: 'Data retention policies', value: 'retention' },
|
|
84
|
-
{ label: 'Data deletion (right to forget)', value: 'deletion' },
|
|
85
|
-
{ label: 'Data export', value: 'export' },
|
|
86
|
-
{ label: 'Audit logging', value: 'audit' }
|
|
87
|
-
];
|
|
88
|
-
|
|
89
|
-
answers.dataHandling = await askMultiSelect(
|
|
90
|
-
rl,
|
|
91
|
-
'Data handling requirements:',
|
|
92
|
-
dataOptions,
|
|
93
|
-
['encryption-rest', 'encryption-transit']
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Vulnerability Scanning
|
|
98
|
-
answers.enableSecurityScanning = await askYesNo(
|
|
99
|
-
rl,
|
|
100
|
-
'Enable automated security scanning?',
|
|
101
|
-
true
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
if (answers.enableSecurityScanning) {
|
|
105
|
-
const scanOptions = [
|
|
106
|
-
{ label: 'Dependabot', value: 'dependabot', description: 'GitHub dependency alerts' },
|
|
107
|
-
{ label: 'Snyk', value: 'snyk', description: 'Security monitoring' },
|
|
108
|
-
{ label: 'CodeQL', value: 'codeql', description: 'GitHub code scanning' },
|
|
109
|
-
{ label: 'SonarQube', value: 'sonar', description: 'Code quality & security' }
|
|
110
|
-
];
|
|
111
|
-
|
|
112
|
-
answers.securityScanner = await askChoice(
|
|
113
|
-
rl,
|
|
114
|
-
'Security scanner:',
|
|
115
|
-
scanOptions,
|
|
116
|
-
0
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return answers;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
module.exports = {
|
|
124
|
-
title,
|
|
125
|
-
description,
|
|
126
|
-
run
|
|
127
|
-
};
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Technical Stack Section
|
|
3
|
-
*
|
|
4
|
-
* Collects framework, language, database, and hosting choices.
|
|
5
|
-
*
|
|
6
|
-
* @package bootspring
|
|
7
|
-
* @module generators/sections/technical
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
const utils = require('../../core/utils');
|
|
13
|
-
|
|
14
|
-
const title = 'Technical Stack';
|
|
15
|
-
const description = 'Your technology choices';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Detect tech stack from existing files
|
|
19
|
-
*/
|
|
20
|
-
function detectStack(projectRoot) {
|
|
21
|
-
const detected = {
|
|
22
|
-
framework: null,
|
|
23
|
-
language: null,
|
|
24
|
-
database: null,
|
|
25
|
-
orm: null
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const pkg = utils.getPackageJson(projectRoot);
|
|
29
|
-
if (!pkg) return detected;
|
|
30
|
-
|
|
31
|
-
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
32
|
-
|
|
33
|
-
// Detect framework
|
|
34
|
-
if (deps.next) detected.framework = 'nextjs';
|
|
35
|
-
else if (deps['@remix-run/react']) detected.framework = 'remix';
|
|
36
|
-
else if (deps.nuxt) detected.framework = 'nuxt';
|
|
37
|
-
else if (deps['@sveltejs/kit']) detected.framework = 'sveltekit';
|
|
38
|
-
else if (deps.express) detected.framework = 'express';
|
|
39
|
-
else if (deps.fastify) detected.framework = 'fastify';
|
|
40
|
-
else if (deps.hono) detected.framework = 'hono';
|
|
41
|
-
|
|
42
|
-
// Detect language
|
|
43
|
-
detected.language = fs.existsSync(path.join(projectRoot, 'tsconfig.json'))
|
|
44
|
-
? 'typescript'
|
|
45
|
-
: 'javascript';
|
|
46
|
-
|
|
47
|
-
// Detect ORM/Database
|
|
48
|
-
if (deps['@prisma/client']) {
|
|
49
|
-
detected.orm = 'prisma';
|
|
50
|
-
detected.database = 'postgresql'; // Default assumption
|
|
51
|
-
} else if (deps.drizzle) {
|
|
52
|
-
detected.orm = 'drizzle';
|
|
53
|
-
} else if (deps.mongoose) {
|
|
54
|
-
detected.orm = 'mongoose';
|
|
55
|
-
detected.database = 'mongodb';
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return detected;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Run the technical section
|
|
63
|
-
*/
|
|
64
|
-
async function run(rl, previousAnswers, helpers) {
|
|
65
|
-
const { askChoice } = helpers;
|
|
66
|
-
const detected = detectStack(process.cwd());
|
|
67
|
-
|
|
68
|
-
if (detected.framework) {
|
|
69
|
-
console.log(`\nDetected: ${detected.framework} + ${detected.language}`);
|
|
70
|
-
if (detected.orm) console.log(` ${detected.orm}`);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const answers = {};
|
|
74
|
-
|
|
75
|
-
// Framework
|
|
76
|
-
const frameworkOptions = [
|
|
77
|
-
{ label: 'Next.js', value: 'nextjs', description: 'React framework with SSR' },
|
|
78
|
-
{ label: 'Remix', value: 'remix', description: 'Full-stack React framework' },
|
|
79
|
-
{ label: 'Nuxt', value: 'nuxt', description: 'Vue.js framework' },
|
|
80
|
-
{ label: 'SvelteKit', value: 'sveltekit', description: 'Svelte framework' },
|
|
81
|
-
{ label: 'Express', value: 'express', description: 'Node.js backend' },
|
|
82
|
-
{ label: 'Fastify', value: 'fastify', description: 'Fast Node.js backend' },
|
|
83
|
-
{ label: 'Hono', value: 'hono', description: 'Lightweight edge runtime' },
|
|
84
|
-
{ label: 'Other', value: 'other', description: 'Something else' }
|
|
85
|
-
];
|
|
86
|
-
const detectedFrameworkIndex = frameworkOptions.findIndex(f => f.value === detected.framework);
|
|
87
|
-
|
|
88
|
-
answers.framework = await askChoice(
|
|
89
|
-
rl,
|
|
90
|
-
'Framework:',
|
|
91
|
-
frameworkOptions,
|
|
92
|
-
detectedFrameworkIndex >= 0 ? detectedFrameworkIndex : 0
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
// Language
|
|
96
|
-
answers.language = await askChoice(
|
|
97
|
-
rl,
|
|
98
|
-
'Language:',
|
|
99
|
-
[
|
|
100
|
-
{ label: 'TypeScript', value: 'typescript', description: 'Recommended for type safety' },
|
|
101
|
-
{ label: 'JavaScript', value: 'javascript', description: 'Classic JS' }
|
|
102
|
-
],
|
|
103
|
-
detected.language === 'typescript' ? 0 : 1
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
// Database
|
|
107
|
-
const databaseOptions = [
|
|
108
|
-
{ label: 'PostgreSQL', value: 'postgresql', description: 'Powerful relational DB' },
|
|
109
|
-
{ label: 'MySQL', value: 'mysql', description: 'Popular relational DB' },
|
|
110
|
-
{ label: 'MongoDB', value: 'mongodb', description: 'Document database' },
|
|
111
|
-
{ label: 'SQLite', value: 'sqlite', description: 'File-based, great for dev' },
|
|
112
|
-
{ label: 'None', value: 'none', description: 'No database needed' }
|
|
113
|
-
];
|
|
114
|
-
const detectedDbIndex = databaseOptions.findIndex(d => d.value === detected.database);
|
|
115
|
-
|
|
116
|
-
answers.database = await askChoice(
|
|
117
|
-
rl,
|
|
118
|
-
'Database:',
|
|
119
|
-
databaseOptions,
|
|
120
|
-
detectedDbIndex >= 0 ? detectedDbIndex : 0
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
// ORM (if database selected)
|
|
124
|
-
if (answers.database !== 'none') {
|
|
125
|
-
const ormOptions = answers.database === 'mongodb'
|
|
126
|
-
? [
|
|
127
|
-
{ label: 'Mongoose', value: 'mongoose', description: 'Popular MongoDB ODM' },
|
|
128
|
-
{ label: 'Native driver', value: 'native', description: 'Use MongoDB driver directly' }
|
|
129
|
-
]
|
|
130
|
-
: [
|
|
131
|
-
{ label: 'Prisma', value: 'prisma', description: 'Type-safe ORM (Recommended)' },
|
|
132
|
-
{ label: 'Drizzle', value: 'drizzle', description: 'Lightweight TypeScript ORM' },
|
|
133
|
-
{ label: 'TypeORM', value: 'typeorm', description: 'Decorator-based ORM' },
|
|
134
|
-
{ label: 'Raw SQL', value: 'raw', description: 'Use SQL directly' }
|
|
135
|
-
];
|
|
136
|
-
|
|
137
|
-
const detectedOrmIndex = ormOptions.findIndex(o => o.value === detected.orm);
|
|
138
|
-
|
|
139
|
-
answers.orm = await askChoice(
|
|
140
|
-
rl,
|
|
141
|
-
'ORM/Database client:',
|
|
142
|
-
ormOptions,
|
|
143
|
-
detectedOrmIndex >= 0 ? detectedOrmIndex : 0
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Hosting
|
|
148
|
-
const hostingOptions = [
|
|
149
|
-
{ label: 'Vercel', value: 'vercel', description: 'Best for Next.js' },
|
|
150
|
-
{ label: 'Netlify', value: 'netlify', description: 'Great for JAMstack' },
|
|
151
|
-
{ label: 'Railway', value: 'railway', description: 'Full-stack hosting' },
|
|
152
|
-
{ label: 'Fly.io', value: 'fly', description: 'Edge deployment' },
|
|
153
|
-
{ label: 'AWS', value: 'aws', description: 'Full AWS stack' },
|
|
154
|
-
{ label: 'Self-hosted', value: 'self-hosted', description: 'Your own servers' }
|
|
155
|
-
];
|
|
156
|
-
|
|
157
|
-
answers.hosting = await askChoice(
|
|
158
|
-
rl,
|
|
159
|
-
'Hosting platform:',
|
|
160
|
-
hostingOptions,
|
|
161
|
-
0
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
return answers;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
module.exports = {
|
|
168
|
-
title,
|
|
169
|
-
description,
|
|
170
|
-
run
|
|
171
|
-
};
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Testing Section
|
|
3
|
-
*
|
|
4
|
-
* Testing strategy, frameworks, and coverage.
|
|
5
|
-
*
|
|
6
|
-
* @package bootspring
|
|
7
|
-
* @module generators/sections/testing
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const title = 'Testing Strategy';
|
|
11
|
-
const description = 'Testing setup and approach';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Run the testing section
|
|
15
|
-
*/
|
|
16
|
-
async function run(rl, previousAnswers, helpers) {
|
|
17
|
-
const { askChoice, askMultiSelect, askYesNo } = helpers;
|
|
18
|
-
const answers = {};
|
|
19
|
-
|
|
20
|
-
// Enable testing
|
|
21
|
-
answers.enableTesting = await askYesNo(
|
|
22
|
-
rl,
|
|
23
|
-
'Set up testing infrastructure?',
|
|
24
|
-
true
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
if (!answers.enableTesting) {
|
|
28
|
-
return answers;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Test Runner
|
|
32
|
-
const runnerOptions = [
|
|
33
|
-
{ label: 'Vitest', value: 'vitest', description: 'Fast, Vite-native (Recommended)' },
|
|
34
|
-
{ label: 'Jest', value: 'jest', description: 'Popular, full-featured' },
|
|
35
|
-
{ label: 'Bun test', value: 'bun', description: 'Fast, Bun runtime' },
|
|
36
|
-
{ label: 'Node test runner', value: 'node', description: 'Built-in Node.js' }
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
answers.testRunner = await askChoice(
|
|
40
|
-
rl,
|
|
41
|
-
'Test runner:',
|
|
42
|
-
runnerOptions,
|
|
43
|
-
0
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
// Testing Types
|
|
47
|
-
const typeOptions = [
|
|
48
|
-
{ label: 'Unit tests', value: 'unit' },
|
|
49
|
-
{ label: 'Integration tests', value: 'integration' },
|
|
50
|
-
{ label: 'E2E tests', value: 'e2e' },
|
|
51
|
-
{ label: 'Component tests', value: 'component' },
|
|
52
|
-
{ label: 'API tests', value: 'api' },
|
|
53
|
-
{ label: 'Visual regression', value: 'visual' }
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
answers.testTypes = await askMultiSelect(
|
|
57
|
-
rl,
|
|
58
|
-
'Testing types:',
|
|
59
|
-
typeOptions,
|
|
60
|
-
['unit', 'integration']
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
// E2E Framework (if selected)
|
|
64
|
-
if (answers.testTypes.includes('e2e')) {
|
|
65
|
-
const e2eOptions = [
|
|
66
|
-
{ label: 'Playwright', value: 'playwright', description: 'Modern E2E (Recommended)' },
|
|
67
|
-
{ label: 'Cypress', value: 'cypress', description: 'Popular E2E' },
|
|
68
|
-
{ label: 'Puppeteer', value: 'puppeteer', description: 'Chrome automation' }
|
|
69
|
-
];
|
|
70
|
-
|
|
71
|
-
answers.e2eFramework = await askChoice(
|
|
72
|
-
rl,
|
|
73
|
-
'E2E framework:',
|
|
74
|
-
e2eOptions,
|
|
75
|
-
0
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Component Testing (if React-based)
|
|
80
|
-
if (answers.testTypes.includes('component')) {
|
|
81
|
-
const componentOptions = [
|
|
82
|
-
{ label: 'Testing Library', value: 'testing-library', description: 'User-centric testing' },
|
|
83
|
-
{ label: 'Storybook', value: 'storybook', description: 'Component explorer' },
|
|
84
|
-
{ label: 'Both', value: 'both', description: 'Testing Library + Storybook' }
|
|
85
|
-
];
|
|
86
|
-
|
|
87
|
-
answers.componentTesting = await askChoice(
|
|
88
|
-
rl,
|
|
89
|
-
'Component testing:',
|
|
90
|
-
componentOptions,
|
|
91
|
-
0
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Coverage
|
|
96
|
-
const coverageOptions = [
|
|
97
|
-
{ label: 'No coverage', value: 'none' },
|
|
98
|
-
{ label: 'Basic (50%+)', value: 'basic' },
|
|
99
|
-
{ label: 'Standard (70%+)', value: 'standard' },
|
|
100
|
-
{ label: 'High (85%+)', value: 'high' },
|
|
101
|
-
{ label: 'Strict (95%+)', value: 'strict' }
|
|
102
|
-
];
|
|
103
|
-
|
|
104
|
-
answers.coverageTarget = await askChoice(
|
|
105
|
-
rl,
|
|
106
|
-
'Coverage target:',
|
|
107
|
-
coverageOptions,
|
|
108
|
-
2
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
// CI Integration
|
|
112
|
-
answers.testInCI = await askYesNo(
|
|
113
|
-
rl,
|
|
114
|
-
'Run tests in CI pipeline?',
|
|
115
|
-
true
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
return answers;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
module.exports = {
|
|
122
|
-
title,
|
|
123
|
-
description,
|
|
124
|
-
run
|
|
125
|
-
};
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Development Workflow Section
|
|
3
|
-
*
|
|
4
|
-
* Collects git strategy, team size, and commit conventions.
|
|
5
|
-
*
|
|
6
|
-
* @package bootspring
|
|
7
|
-
* @module generators/sections/workflow
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const title = 'Development Workflow';
|
|
11
|
-
const description = 'How you work';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Run the workflow section
|
|
15
|
-
*/
|
|
16
|
-
async function run(rl, previousAnswers, helpers) {
|
|
17
|
-
const { askChoice, askYesNo } = helpers;
|
|
18
|
-
|
|
19
|
-
const answers = {};
|
|
20
|
-
|
|
21
|
-
// Team size
|
|
22
|
-
answers.teamSize = await askChoice(
|
|
23
|
-
rl,
|
|
24
|
-
'Team size:',
|
|
25
|
-
[
|
|
26
|
-
{ label: 'Solo', value: 'solo', description: 'Just me' },
|
|
27
|
-
{ label: 'Small (2-5)', value: 'small', description: 'Small team' },
|
|
28
|
-
{ label: 'Medium (6-15)', value: 'medium', description: 'Growing team' },
|
|
29
|
-
{ label: 'Large (15+)', value: 'large', description: 'Large organization' }
|
|
30
|
-
],
|
|
31
|
-
0
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
// Git strategy
|
|
35
|
-
answers.gitStrategy = await askChoice(
|
|
36
|
-
rl,
|
|
37
|
-
'Git workflow:',
|
|
38
|
-
[
|
|
39
|
-
{ label: 'Feature Branch', value: 'feature-branch', description: 'Branch per feature, merge to main' },
|
|
40
|
-
{ label: 'Trunk-Based', value: 'trunk', description: 'Direct commits to main with flags' },
|
|
41
|
-
{ label: 'GitFlow', value: 'gitflow', description: 'develop, release, hotfix branches' },
|
|
42
|
-
{ label: 'GitHub Flow', value: 'github-flow', description: 'Simple branch + PR' }
|
|
43
|
-
],
|
|
44
|
-
answers.teamSize === 'solo' ? 1 : 0
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
// Commit style
|
|
48
|
-
answers.commitStyle = await askChoice(
|
|
49
|
-
rl,
|
|
50
|
-
'Commit message style:',
|
|
51
|
-
[
|
|
52
|
-
{ label: 'Conventional', value: 'conventional', description: 'feat:, fix:, docs: etc.' },
|
|
53
|
-
{ label: 'Gitmoji', value: 'gitmoji', description: 'Emoji prefixes' },
|
|
54
|
-
{ label: 'Free-form', value: 'freeform', description: 'No strict format' }
|
|
55
|
-
],
|
|
56
|
-
0
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
// Code review
|
|
60
|
-
if (answers.teamSize !== 'solo') {
|
|
61
|
-
answers.requireReview = await askYesNo(
|
|
62
|
-
rl,
|
|
63
|
-
'Require code review for PRs?',
|
|
64
|
-
true
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
answers.minReviewers = answers.requireReview
|
|
68
|
-
? await askChoice(rl, 'Minimum reviewers:', [
|
|
69
|
-
{ label: '1', value: 1 },
|
|
70
|
-
{ label: '2', value: 2 },
|
|
71
|
-
{ label: '3+', value: 3 }
|
|
72
|
-
], 0)
|
|
73
|
-
: 0;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// CI/CD
|
|
77
|
-
answers.enableCI = await askYesNo(
|
|
78
|
-
rl,
|
|
79
|
-
'Set up CI/CD pipeline?',
|
|
80
|
-
true
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
if (answers.enableCI) {
|
|
84
|
-
answers.ciProvider = await askChoice(
|
|
85
|
-
rl,
|
|
86
|
-
'CI/CD provider:',
|
|
87
|
-
[
|
|
88
|
-
{ label: 'GitHub Actions', value: 'github-actions', description: 'Built into GitHub' },
|
|
89
|
-
{ label: 'Vercel', value: 'vercel', description: 'Automatic with Vercel' },
|
|
90
|
-
{ label: 'GitLab CI', value: 'gitlab', description: 'GitLab pipelines' },
|
|
91
|
-
{ label: 'CircleCI', value: 'circleci', description: 'Dedicated CI service' }
|
|
92
|
-
],
|
|
93
|
-
0
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return answers;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
module.exports = {
|
|
101
|
-
title,
|
|
102
|
-
description,
|
|
103
|
-
run
|
|
104
|
-
};
|