@girardmedia/bootspring 2.0.21 → 2.0.23
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/bin/bootspring.js +5 -0
- package/cli/org.js +474 -0
- package/cli/preseed/index.js +16 -0
- package/cli/preseed/interactive.js +143 -0
- package/cli/preseed/templates.js +227 -0
- package/cli/preseed.js +9 -301
- package/cli/seed/builders/ai-context-builder.js +85 -0
- package/cli/seed/builders/index.js +13 -0
- package/cli/seed/builders/seed-builder.js +272 -0
- package/cli/seed/extractors/content-extractors.js +383 -0
- package/cli/seed/extractors/index.js +47 -0
- package/cli/seed/extractors/metadata-extractors.js +167 -0
- package/cli/seed/extractors/section-extractor.js +54 -0
- package/cli/seed/extractors/stack-extractors.js +228 -0
- package/cli/seed/index.js +18 -0
- package/cli/seed/utils/folder-structure.js +84 -0
- package/cli/seed/utils/index.js +11 -0
- package/cli/seed.js +23 -1074
- package/core/api-client.js +77 -0
- package/core/entitlements.js +36 -0
- package/core/organizations.js +223 -0
- package/core/policies.js +51 -6
- package/core/policy-matrix.js +303 -0
- package/core/project-context.js +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +3220 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/context-McpJQa_2.d.ts +5710 -0
- package/dist/core/index.d.ts +635 -0
- package/dist/core/index.js +2593 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index-QqbeEiDm.d.ts +857 -0
- package/dist/index-UiYCgwiH.d.ts +174 -0
- package/dist/index.d.ts +453 -0
- package/dist/index.js +44228 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +41173 -0
- package/dist/mcp/index.js.map +1 -0
- package/generators/index.ts +82 -0
- package/intelligence/orchestrator/config/failure-signatures.js +48 -0
- package/intelligence/orchestrator/config/index.js +23 -0
- package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
- package/intelligence/orchestrator/config/phases.js +111 -0
- package/intelligence/orchestrator/config/remediation.js +150 -0
- package/intelligence/orchestrator/config/workflows.js +168 -0
- package/intelligence/orchestrator/core/index.js +16 -0
- package/intelligence/orchestrator/core/state-manager.js +88 -0
- package/intelligence/orchestrator/core/telemetry.js +24 -0
- package/intelligence/orchestrator/index.js +17 -0
- package/intelligence/orchestrator.js +17 -512
- package/mcp/contracts/mcp-contract.v1.json +1 -1
- package/package.json +16 -3
- package/src/cli/agent.ts +703 -0
- package/src/cli/analyze.ts +640 -0
- package/src/cli/audit.ts +707 -0
- package/src/cli/auth.ts +930 -0
- package/src/cli/billing.ts +364 -0
- package/src/cli/build.ts +1089 -0
- package/src/cli/business.ts +508 -0
- package/src/cli/checkpoint-utils.ts +236 -0
- package/src/cli/checkpoint.ts +757 -0
- package/src/cli/cloud-sync.ts +534 -0
- package/src/cli/content.ts +273 -0
- package/src/cli/context.ts +667 -0
- package/src/cli/dashboard.ts +133 -0
- package/src/cli/deploy.ts +704 -0
- package/src/cli/doctor.ts +480 -0
- package/src/cli/fundraise.ts +494 -0
- package/src/cli/generate.ts +346 -0
- package/src/cli/github-cmd.ts +566 -0
- package/src/cli/health.ts +599 -0
- package/src/cli/index.ts +113 -0
- package/src/cli/init.ts +838 -0
- package/src/cli/legal.ts +495 -0
- package/src/cli/log.ts +316 -0
- package/src/cli/loop.ts +1660 -0
- package/src/cli/manager.ts +878 -0
- package/src/cli/mcp.ts +275 -0
- package/src/cli/memory.ts +346 -0
- package/src/cli/metrics.ts +590 -0
- package/src/cli/monitor.ts +960 -0
- package/src/cli/mvp.ts +662 -0
- package/src/cli/onboard.ts +663 -0
- package/src/cli/orchestrator.ts +622 -0
- package/src/cli/plugin.ts +483 -0
- package/src/cli/prd.ts +671 -0
- package/src/cli/preseed-start.ts +1633 -0
- package/src/cli/preseed.ts +2434 -0
- package/src/cli/project.ts +526 -0
- package/src/cli/quality.ts +885 -0
- package/src/cli/security.ts +1079 -0
- package/src/cli/seed.ts +1224 -0
- package/src/cli/skill.ts +537 -0
- package/src/cli/suggest.ts +1225 -0
- package/src/cli/switch.ts +518 -0
- package/src/cli/task.ts +780 -0
- package/src/cli/telemetry.ts +172 -0
- package/src/cli/todo.ts +627 -0
- package/src/cli/types.ts +15 -0
- package/src/cli/update.ts +334 -0
- package/src/cli/visualize.ts +609 -0
- package/src/cli/watch.ts +895 -0
- package/src/cli/workspace.ts +709 -0
- package/src/core/action-recorder.ts +673 -0
- package/src/core/analyze-workflow.ts +1453 -0
- package/src/core/api-client.ts +1120 -0
- package/src/core/audit-workflow.ts +1681 -0
- package/src/core/auth.ts +471 -0
- package/src/core/build-orchestrator.ts +509 -0
- package/src/core/build-state.ts +621 -0
- package/src/core/checkpoint-engine.ts +482 -0
- package/src/core/config.ts +1285 -0
- package/src/core/context-loader.ts +694 -0
- package/src/core/context.ts +410 -0
- package/src/core/deploy-workflow.ts +1085 -0
- package/src/core/entitlements.ts +322 -0
- package/src/core/github-sync.ts +720 -0
- package/src/core/index.ts +981 -0
- package/src/core/ingest.ts +1186 -0
- package/src/core/metrics-engine.ts +886 -0
- package/src/core/mvp.ts +847 -0
- package/src/core/onboard-workflow.ts +1293 -0
- package/src/core/policies.ts +81 -0
- package/src/core/preseed-workflow.ts +1163 -0
- package/src/core/preseed.ts +1826 -0
- package/src/core/project-context.ts +380 -0
- package/src/core/project-state.ts +699 -0
- package/src/core/r2-sync.ts +691 -0
- package/src/core/scaffold.ts +1715 -0
- package/src/core/session.ts +286 -0
- package/src/core/task-extractor.ts +799 -0
- package/src/core/telemetry.ts +371 -0
- package/src/core/tier-enforcement.ts +737 -0
- package/src/core/utils.ts +437 -0
- package/src/index.ts +29 -0
- package/src/intelligence/agent-collab.ts +2376 -0
- package/src/intelligence/auto-suggest.ts +713 -0
- package/src/intelligence/content-gen.ts +1351 -0
- package/src/intelligence/cross-project.ts +1692 -0
- package/src/intelligence/git-memory.ts +529 -0
- package/src/intelligence/index.ts +318 -0
- package/src/intelligence/orchestrator.ts +534 -0
- package/src/intelligence/prd.ts +466 -0
- package/src/intelligence/recommendations.ts +982 -0
- package/src/intelligence/workflow-composer.ts +1472 -0
- package/src/mcp/capabilities.ts +233 -0
- package/src/mcp/index.ts +37 -0
- package/src/mcp/registry.ts +1268 -0
- package/src/mcp/response-formatter.ts +797 -0
- package/src/mcp/server.ts +240 -0
- package/src/types/agent.ts +69 -0
- package/src/types/config.ts +86 -0
- package/src/types/context.ts +77 -0
- package/src/types/index.ts +53 -0
- package/src/types/mcp.ts +91 -0
- package/src/types/skills.ts +47 -0
- package/src/types/workflow.ts +155 -0
- package/generators/index.js +0 -18
|
@@ -0,0 +1,1285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring Configuration
|
|
3
|
+
* Handles loading and validating bootspring.config.js
|
|
4
|
+
*
|
|
5
|
+
* @package bootspring
|
|
6
|
+
* @module core/config
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Zod Schemas for Configuration Validation
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
// -----------------------------------------------------------------------------
|
|
18
|
+
// Plugin Schemas
|
|
19
|
+
// -----------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Base plugin schema - all plugins share these common fields
|
|
23
|
+
*/
|
|
24
|
+
export const BasePluginSchema = z.object({
|
|
25
|
+
enabled: z.boolean().optional().default(false),
|
|
26
|
+
provider: z.string().optional(),
|
|
27
|
+
features: z.array(z.string()).optional().default([])
|
|
28
|
+
}).passthrough();
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Auth plugin schema with specific provider options
|
|
32
|
+
*/
|
|
33
|
+
export const AuthPluginSchema = z.object({
|
|
34
|
+
enabled: z.boolean().optional().default(false),
|
|
35
|
+
provider: z.enum(['clerk', 'nextauth', 'auth0', 'supabase', 'jwt', 'custom']).optional(),
|
|
36
|
+
features: z.array(z.enum([
|
|
37
|
+
'social_login', 'email_password', 'magic_link', 'sso', 'mfa', 'rbac', 'passwordless'
|
|
38
|
+
])).optional().default([])
|
|
39
|
+
}).passthrough();
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Payments plugin schema
|
|
43
|
+
*/
|
|
44
|
+
export const PaymentsPluginSchema = z.object({
|
|
45
|
+
enabled: z.boolean().optional().default(false),
|
|
46
|
+
provider: z.enum(['stripe', 'paddle', 'lemonsqueezy', 'paypal', 'custom']).optional(),
|
|
47
|
+
features: z.array(z.enum([
|
|
48
|
+
'checkout', 'subscriptions', 'invoices', 'usage_billing', 'trials', 'coupons'
|
|
49
|
+
])).optional().default([])
|
|
50
|
+
}).passthrough();
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Database plugin schema
|
|
54
|
+
*/
|
|
55
|
+
export const DatabasePluginSchema = z.object({
|
|
56
|
+
enabled: z.boolean().optional().default(true),
|
|
57
|
+
provider: z.enum(['prisma', 'drizzle', 'typeorm', 'kysely', 'custom']).optional(),
|
|
58
|
+
features: z.array(z.enum([
|
|
59
|
+
'migrations', 'transactions', 'seeding', 'multi_tenant', 'full_text_search', 'soft_delete'
|
|
60
|
+
])).optional().default([])
|
|
61
|
+
}).passthrough();
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Testing plugin schema
|
|
65
|
+
*/
|
|
66
|
+
export const TestingPluginSchema = z.object({
|
|
67
|
+
enabled: z.boolean().optional().default(true),
|
|
68
|
+
provider: z.enum(['vitest', 'jest', 'playwright', 'cypress', 'custom']).optional(),
|
|
69
|
+
features: z.array(z.enum([
|
|
70
|
+
'unit', 'integration', 'e2e', 'coverage', 'snapshot', 'mocking'
|
|
71
|
+
])).optional().default([])
|
|
72
|
+
}).passthrough();
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Security plugin schema
|
|
76
|
+
*/
|
|
77
|
+
export const SecurityPluginSchema = z.object({
|
|
78
|
+
enabled: z.boolean().optional().default(true),
|
|
79
|
+
provider: z.string().optional(),
|
|
80
|
+
features: z.array(z.enum([
|
|
81
|
+
'input_validation', 'rate_limiting', 'csrf', 'xss', 'sql_injection',
|
|
82
|
+
'audit', 'rbac', 'encryption', 'secrets_management'
|
|
83
|
+
])).optional().default([])
|
|
84
|
+
}).passthrough();
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* AI plugin schema
|
|
88
|
+
*/
|
|
89
|
+
export const AIPluginSchema = z.object({
|
|
90
|
+
enabled: z.boolean().optional().default(false),
|
|
91
|
+
provider: z.enum(['anthropic', 'openai', 'google', 'cohere', 'custom']).optional(),
|
|
92
|
+
features: z.array(z.enum([
|
|
93
|
+
'streaming', 'tool_use', 'embeddings', 'rag', 'agents', 'vision'
|
|
94
|
+
])).optional().default([])
|
|
95
|
+
}).passthrough();
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Email plugin schema
|
|
99
|
+
*/
|
|
100
|
+
export const EmailPluginSchema = z.object({
|
|
101
|
+
enabled: z.boolean().optional().default(false),
|
|
102
|
+
provider: z.enum(['resend', 'sendgrid', 'postmark', 'ses', 'mailgun', 'custom']).optional(),
|
|
103
|
+
features: z.array(z.enum([
|
|
104
|
+
'transactional', 'marketing', 'templates', 'tracking'
|
|
105
|
+
])).optional().default([])
|
|
106
|
+
}).passthrough();
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Analytics plugin schema
|
|
110
|
+
*/
|
|
111
|
+
export const AnalyticsPluginSchema = z.object({
|
|
112
|
+
enabled: z.boolean().optional().default(false),
|
|
113
|
+
provider: z.enum(['posthog', 'amplitude', 'mixpanel', 'google_analytics', 'custom']).optional(),
|
|
114
|
+
features: z.array(z.enum([
|
|
115
|
+
'page_views', 'events', 'user_tracking', 'funnels', 'experiments'
|
|
116
|
+
])).optional().default([])
|
|
117
|
+
}).passthrough();
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Monitoring plugin schema
|
|
121
|
+
*/
|
|
122
|
+
export const MonitoringPluginSchema = z.object({
|
|
123
|
+
enabled: z.boolean().optional().default(false),
|
|
124
|
+
provider: z.enum(['sentry', 'datadog', 'newrelic', 'logrocket', 'custom']).optional(),
|
|
125
|
+
features: z.array(z.enum([
|
|
126
|
+
'error_tracking', 'performance', 'logs', 'alerts', 'apm'
|
|
127
|
+
])).optional().default([])
|
|
128
|
+
}).passthrough();
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Combined plugins schema with specific plugin types
|
|
132
|
+
*/
|
|
133
|
+
export const PluginsSchema = z.object({
|
|
134
|
+
auth: AuthPluginSchema.optional(),
|
|
135
|
+
payments: PaymentsPluginSchema.optional(),
|
|
136
|
+
database: DatabasePluginSchema.optional(),
|
|
137
|
+
testing: TestingPluginSchema.optional(),
|
|
138
|
+
security: SecurityPluginSchema.optional(),
|
|
139
|
+
ai: AIPluginSchema.optional(),
|
|
140
|
+
email: EmailPluginSchema.optional(),
|
|
141
|
+
analytics: AnalyticsPluginSchema.optional(),
|
|
142
|
+
monitoring: MonitoringPluginSchema.optional()
|
|
143
|
+
}).catchall(BasePluginSchema);
|
|
144
|
+
|
|
145
|
+
// Backward compatible alias
|
|
146
|
+
export const PluginSchema = BasePluginSchema;
|
|
147
|
+
|
|
148
|
+
// -----------------------------------------------------------------------------
|
|
149
|
+
// Agent Configuration Schema
|
|
150
|
+
// -----------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Individual agent configuration
|
|
154
|
+
*/
|
|
155
|
+
export const AgentConfigSchema = z.object({
|
|
156
|
+
enabled: z.boolean().optional().default(true),
|
|
157
|
+
expertise: z.array(z.string()).optional(),
|
|
158
|
+
customInstructions: z.string().optional(),
|
|
159
|
+
priority: z.enum(['high', 'medium', 'low']).optional().default('medium')
|
|
160
|
+
}).passthrough();
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Agents section configuration
|
|
164
|
+
*/
|
|
165
|
+
export const AgentsConfigSchema = z.object({
|
|
166
|
+
enabled: z.record(z.string(), z.boolean()).optional(),
|
|
167
|
+
custom: z.record(z.string(), AgentConfigSchema).optional(),
|
|
168
|
+
defaults: z.array(z.string()).optional(),
|
|
169
|
+
settings: z.object({
|
|
170
|
+
maxConcurrent: z.number().int().min(1).max(10).optional().default(3),
|
|
171
|
+
timeout: z.number().int().min(1000).max(300000).optional().default(60000),
|
|
172
|
+
verbose: z.boolean().optional().default(false)
|
|
173
|
+
}).optional()
|
|
174
|
+
}).passthrough();
|
|
175
|
+
|
|
176
|
+
// -----------------------------------------------------------------------------
|
|
177
|
+
// Skills Configuration Schema
|
|
178
|
+
// -----------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Individual skill configuration
|
|
182
|
+
*/
|
|
183
|
+
export const SkillConfigSchema = z.object({
|
|
184
|
+
enabled: z.boolean().optional().default(true),
|
|
185
|
+
tier: z.enum(['free', 'pro', 'premium']).optional().default('free'),
|
|
186
|
+
category: z.string().optional(),
|
|
187
|
+
maxChars: z.number().int().min(100).optional()
|
|
188
|
+
}).passthrough();
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Skills section configuration
|
|
192
|
+
*/
|
|
193
|
+
export const SkillsConfigSchema = z.object({
|
|
194
|
+
categories: z.record(z.string(), z.boolean()).optional(),
|
|
195
|
+
custom: z.record(z.string(), SkillConfigSchema).optional(),
|
|
196
|
+
external: z.object({
|
|
197
|
+
enabled: z.boolean().optional().default(false),
|
|
198
|
+
manifestUrl: z.string().url().optional(),
|
|
199
|
+
cacheDir: z.string().optional(),
|
|
200
|
+
requireSignature: z.boolean().optional().default(false)
|
|
201
|
+
}).optional(),
|
|
202
|
+
settings: z.object({
|
|
203
|
+
defaultMaxChars: z.number().int().min(100).optional().default(50000),
|
|
204
|
+
summaryByDefault: z.boolean().optional().default(false),
|
|
205
|
+
includeExternal: z.boolean().optional().default(false)
|
|
206
|
+
}).optional()
|
|
207
|
+
}).passthrough();
|
|
208
|
+
|
|
209
|
+
// -----------------------------------------------------------------------------
|
|
210
|
+
// Workflows Configuration Schema
|
|
211
|
+
// -----------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Workflow phase schema
|
|
215
|
+
*/
|
|
216
|
+
export const WorkflowPhaseSchema = z.object({
|
|
217
|
+
name: z.string().min(1, 'Phase name is required'),
|
|
218
|
+
agents: z.array(z.string()).min(1, 'At least one agent is required'),
|
|
219
|
+
duration: z.string().optional(),
|
|
220
|
+
parallel: z.boolean().optional().default(false),
|
|
221
|
+
description: z.string().optional()
|
|
222
|
+
}).passthrough();
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Individual workflow configuration
|
|
226
|
+
*/
|
|
227
|
+
export const WorkflowConfigSchema = z.object({
|
|
228
|
+
name: z.string().min(1, 'Workflow name is required'),
|
|
229
|
+
description: z.string().optional(),
|
|
230
|
+
tier: z.enum(['free', 'pro']).optional().default('free'),
|
|
231
|
+
pack: z.string().optional(),
|
|
232
|
+
outcomes: z.array(z.string()).optional(),
|
|
233
|
+
completionSignals: z.array(z.string()).optional(),
|
|
234
|
+
phases: z.array(WorkflowPhaseSchema).min(1, 'At least one phase is required')
|
|
235
|
+
}).passthrough();
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Workflows section configuration
|
|
239
|
+
*/
|
|
240
|
+
export const WorkflowsConfigSchema = z.object({
|
|
241
|
+
enabled: z.record(z.string(), z.boolean()).optional(),
|
|
242
|
+
custom: z.record(z.string(), WorkflowConfigSchema).optional(),
|
|
243
|
+
default: z.string().optional(),
|
|
244
|
+
settings: z.object({
|
|
245
|
+
autoAdvance: z.boolean().optional().default(true),
|
|
246
|
+
pauseBetweenPhases: z.boolean().optional().default(false),
|
|
247
|
+
trackSignals: z.boolean().optional().default(true),
|
|
248
|
+
emitTelemetry: z.boolean().optional().default(true)
|
|
249
|
+
}).optional()
|
|
250
|
+
}).passthrough();
|
|
251
|
+
|
|
252
|
+
// -----------------------------------------------------------------------------
|
|
253
|
+
// Quality Configuration Schema
|
|
254
|
+
// -----------------------------------------------------------------------------
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Quality check configuration
|
|
258
|
+
*/
|
|
259
|
+
export const QualityCheckSchema = z.union([
|
|
260
|
+
z.boolean(),
|
|
261
|
+
z.object({
|
|
262
|
+
enabled: z.boolean().optional().default(true),
|
|
263
|
+
checks: z.array(z.enum([
|
|
264
|
+
'lint', 'typecheck', 'test', 'build', 'security', 'coverage', 'format'
|
|
265
|
+
])).optional()
|
|
266
|
+
})
|
|
267
|
+
]);
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Quality section configuration
|
|
271
|
+
*/
|
|
272
|
+
export const QualityConfigSchema = z.object({
|
|
273
|
+
preCommit: QualityCheckSchema.optional().default(true),
|
|
274
|
+
prePush: QualityCheckSchema.optional().default(false),
|
|
275
|
+
preDeploy: QualityCheckSchema.optional(),
|
|
276
|
+
strictMode: z.boolean().optional().default(false),
|
|
277
|
+
coverage: z.object({
|
|
278
|
+
statements: z.number().min(0).max(100).optional(),
|
|
279
|
+
branches: z.number().min(0).max(100).optional(),
|
|
280
|
+
functions: z.number().min(0).max(100).optional(),
|
|
281
|
+
lines: z.number().min(0).max(100).optional()
|
|
282
|
+
}).optional()
|
|
283
|
+
}).passthrough();
|
|
284
|
+
|
|
285
|
+
// -----------------------------------------------------------------------------
|
|
286
|
+
// Context Configuration Schema
|
|
287
|
+
// -----------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Context generation configuration
|
|
291
|
+
*/
|
|
292
|
+
export const ContextConfigSchema = z.object({
|
|
293
|
+
includeEnvVars: z.boolean().optional().default(true),
|
|
294
|
+
includeTechStack: z.boolean().optional().default(true),
|
|
295
|
+
includePlugins: z.boolean().optional().default(true),
|
|
296
|
+
includeGitInfo: z.boolean().optional().default(true),
|
|
297
|
+
includeTodos: z.boolean().optional().default(true),
|
|
298
|
+
includeLearnings: z.boolean().optional().default(true),
|
|
299
|
+
customSections: z.array(z.object({
|
|
300
|
+
title: z.string(),
|
|
301
|
+
content: z.string()
|
|
302
|
+
})).optional().default([]),
|
|
303
|
+
maxSize: z.number().int().min(1000).optional()
|
|
304
|
+
}).passthrough();
|
|
305
|
+
|
|
306
|
+
// -----------------------------------------------------------------------------
|
|
307
|
+
// Paths Configuration Schema
|
|
308
|
+
// -----------------------------------------------------------------------------
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Paths configuration
|
|
312
|
+
*/
|
|
313
|
+
export const PathsConfigSchema = z.object({
|
|
314
|
+
context: z.string().optional().default('CLAUDE.md'),
|
|
315
|
+
config: z.string().optional().default('bootspring.config.js'),
|
|
316
|
+
todo: z.string().optional().default('todo.md'),
|
|
317
|
+
roadmap: z.string().optional().default('ROADMAP.md'),
|
|
318
|
+
changelog: z.string().optional().default('CHANGELOG.md'),
|
|
319
|
+
state: z.string().optional().default('.bootspring')
|
|
320
|
+
}).passthrough();
|
|
321
|
+
|
|
322
|
+
// -----------------------------------------------------------------------------
|
|
323
|
+
// Dashboard Configuration Schema
|
|
324
|
+
// -----------------------------------------------------------------------------
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Dashboard configuration
|
|
328
|
+
*/
|
|
329
|
+
export const DashboardConfigSchema = z.object({
|
|
330
|
+
port: z.number().int().min(1024).max(65535).optional().default(3456),
|
|
331
|
+
autoOpen: z.boolean().optional().default(false),
|
|
332
|
+
host: z.string().optional().default('localhost'),
|
|
333
|
+
theme: z.enum(['light', 'dark', 'system']).optional().default('system')
|
|
334
|
+
}).passthrough();
|
|
335
|
+
|
|
336
|
+
// -----------------------------------------------------------------------------
|
|
337
|
+
// Project Configuration Schema
|
|
338
|
+
// -----------------------------------------------------------------------------
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Project information schema
|
|
342
|
+
*/
|
|
343
|
+
export const ProjectConfigSchema = z.object({
|
|
344
|
+
name: z.string().min(1, 'Project name is required'),
|
|
345
|
+
description: z.string().optional().default(''),
|
|
346
|
+
version: z.string().regex(/^\d+\.\d+\.\d+/, 'Version must be semver format').optional().default('1.0.0'),
|
|
347
|
+
author: z.string().optional(),
|
|
348
|
+
license: z.string().optional(),
|
|
349
|
+
repository: z.string().url().optional()
|
|
350
|
+
}).passthrough();
|
|
351
|
+
|
|
352
|
+
// -----------------------------------------------------------------------------
|
|
353
|
+
// Stack Configuration Schema
|
|
354
|
+
// -----------------------------------------------------------------------------
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Technology stack configuration
|
|
358
|
+
*/
|
|
359
|
+
export const StackConfigSchema = z.object({
|
|
360
|
+
framework: z.enum([
|
|
361
|
+
'nextjs', 'remix', 'nuxt', 'sveltekit', 'astro', 'express', 'fastify', 'hono', 'custom'
|
|
362
|
+
]).optional(),
|
|
363
|
+
language: z.enum(['typescript', 'javascript']).optional(),
|
|
364
|
+
database: z.enum(['postgresql', 'mysql', 'mongodb', 'sqlite', 'supabase', 'planetscale', 'none']).optional(),
|
|
365
|
+
hosting: z.enum([
|
|
366
|
+
'vercel', 'railway', 'render', 'fly', 'aws', 'gcp', 'azure', 'cloudflare', 'self-hosted', 'custom'
|
|
367
|
+
]).optional()
|
|
368
|
+
}).passthrough();
|
|
369
|
+
|
|
370
|
+
// -----------------------------------------------------------------------------
|
|
371
|
+
// Main Configuration Schema
|
|
372
|
+
// -----------------------------------------------------------------------------
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Complete Bootspring configuration schema
|
|
376
|
+
*/
|
|
377
|
+
export const ConfigSchema = z.object({
|
|
378
|
+
project: ProjectConfigSchema.optional(),
|
|
379
|
+
stack: StackConfigSchema.optional(),
|
|
380
|
+
plugins: PluginsSchema.optional(),
|
|
381
|
+
agents: AgentsConfigSchema.optional(),
|
|
382
|
+
skills: SkillsConfigSchema.optional(),
|
|
383
|
+
workflows: WorkflowsConfigSchema.optional(),
|
|
384
|
+
dashboard: DashboardConfigSchema.optional(),
|
|
385
|
+
quality: QualityConfigSchema.optional(),
|
|
386
|
+
context: ContextConfigSchema.optional(),
|
|
387
|
+
paths: PathsConfigSchema.optional(),
|
|
388
|
+
mcp: z.object({
|
|
389
|
+
enabled: z.boolean().optional().default(false),
|
|
390
|
+
servers: z.record(z.string(), z.object({
|
|
391
|
+
command: z.string(),
|
|
392
|
+
args: z.array(z.string()).optional(),
|
|
393
|
+
env: z.record(z.string(), z.string()).optional()
|
|
394
|
+
})).optional()
|
|
395
|
+
}).optional()
|
|
396
|
+
}).passthrough();
|
|
397
|
+
|
|
398
|
+
// =============================================================================
|
|
399
|
+
// TypeScript Types (using z.input for input types, z.infer for output types)
|
|
400
|
+
// =============================================================================
|
|
401
|
+
|
|
402
|
+
// Input types (what users provide - allows partial/missing defaults)
|
|
403
|
+
export type BasePluginInput = z.input<typeof BasePluginSchema>;
|
|
404
|
+
export type AuthPluginInput = z.input<typeof AuthPluginSchema>;
|
|
405
|
+
export type PaymentsPluginInput = z.input<typeof PaymentsPluginSchema>;
|
|
406
|
+
export type DatabasePluginInput = z.input<typeof DatabasePluginSchema>;
|
|
407
|
+
export type TestingPluginInput = z.input<typeof TestingPluginSchema>;
|
|
408
|
+
export type SecurityPluginInput = z.input<typeof SecurityPluginSchema>;
|
|
409
|
+
export type AIPluginInput = z.input<typeof AIPluginSchema>;
|
|
410
|
+
export type EmailPluginInput = z.input<typeof EmailPluginSchema>;
|
|
411
|
+
export type AnalyticsPluginInput = z.input<typeof AnalyticsPluginSchema>;
|
|
412
|
+
export type MonitoringPluginInput = z.input<typeof MonitoringPluginSchema>;
|
|
413
|
+
export type PluginsInput = z.input<typeof PluginsSchema>;
|
|
414
|
+
export type ConfigInput = z.input<typeof ConfigSchema>;
|
|
415
|
+
|
|
416
|
+
// Output types (after parsing with defaults applied)
|
|
417
|
+
export type BasePlugin = z.infer<typeof BasePluginSchema>;
|
|
418
|
+
export type AuthPlugin = z.infer<typeof AuthPluginSchema>;
|
|
419
|
+
export type PaymentsPlugin = z.infer<typeof PaymentsPluginSchema>;
|
|
420
|
+
export type DatabasePlugin = z.infer<typeof DatabasePluginSchema>;
|
|
421
|
+
export type TestingPlugin = z.infer<typeof TestingPluginSchema>;
|
|
422
|
+
export type SecurityPlugin = z.infer<typeof SecurityPluginSchema>;
|
|
423
|
+
export type AIPlugin = z.infer<typeof AIPluginSchema>;
|
|
424
|
+
export type EmailPlugin = z.infer<typeof EmailPluginSchema>;
|
|
425
|
+
export type AnalyticsPlugin = z.infer<typeof AnalyticsPluginSchema>;
|
|
426
|
+
export type MonitoringPlugin = z.infer<typeof MonitoringPluginSchema>;
|
|
427
|
+
export type Plugins = z.infer<typeof PluginsSchema>;
|
|
428
|
+
|
|
429
|
+
export type AgentConfig = z.infer<typeof AgentConfigSchema>;
|
|
430
|
+
export type AgentsConfig = z.infer<typeof AgentsConfigSchema>;
|
|
431
|
+
|
|
432
|
+
export type SkillConfig = z.infer<typeof SkillConfigSchema>;
|
|
433
|
+
export type SkillsConfig = z.infer<typeof SkillsConfigSchema>;
|
|
434
|
+
|
|
435
|
+
export type ConfigWorkflowPhase = z.infer<typeof WorkflowPhaseSchema>;
|
|
436
|
+
export type WorkflowConfig = z.infer<typeof WorkflowConfigSchema>;
|
|
437
|
+
export type WorkflowsConfig = z.infer<typeof WorkflowsConfigSchema>;
|
|
438
|
+
|
|
439
|
+
export type QualityCheck = z.infer<typeof QualityCheckSchema>;
|
|
440
|
+
export type QualityConfig = z.infer<typeof QualityConfigSchema>;
|
|
441
|
+
|
|
442
|
+
export type ContextConfig = z.infer<typeof ContextConfigSchema>;
|
|
443
|
+
export type PathsConfig = z.infer<typeof PathsConfigSchema>;
|
|
444
|
+
export type DashboardConfig = z.infer<typeof DashboardConfigSchema>;
|
|
445
|
+
export type ConfigProjectConfig = z.infer<typeof ProjectConfigSchema>;
|
|
446
|
+
export type ConfigStackConfig = z.infer<typeof StackConfigSchema>;
|
|
447
|
+
|
|
448
|
+
export type Config = z.infer<typeof ConfigSchema>;
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Loaded configuration with internal metadata
|
|
452
|
+
*/
|
|
453
|
+
export interface LoadedConfig extends ConfigInput {
|
|
454
|
+
_projectRoot?: string | undefined;
|
|
455
|
+
_configPath?: string | null | undefined;
|
|
456
|
+
_bootspringDir?: string | undefined;
|
|
457
|
+
_validation?: ValidationResult | { skipped: boolean } | undefined;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Validation result
|
|
462
|
+
*/
|
|
463
|
+
export interface ValidationResult {
|
|
464
|
+
valid: boolean;
|
|
465
|
+
errors: string[];
|
|
466
|
+
warnings?: string[] | undefined;
|
|
467
|
+
data?: Config | null | undefined;
|
|
468
|
+
validatedAt?: string | undefined;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Section validation result
|
|
473
|
+
*/
|
|
474
|
+
export interface SectionValidationResult {
|
|
475
|
+
valid: boolean;
|
|
476
|
+
errors: string[];
|
|
477
|
+
data: unknown;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Preset definition
|
|
482
|
+
*/
|
|
483
|
+
export interface PresetDefinition {
|
|
484
|
+
name: string;
|
|
485
|
+
description: string;
|
|
486
|
+
tags: string[];
|
|
487
|
+
extends: string | null;
|
|
488
|
+
config: Partial<ConfigInput>;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Preset info for listing
|
|
493
|
+
*/
|
|
494
|
+
export interface PresetInfo {
|
|
495
|
+
key: string;
|
|
496
|
+
name: string;
|
|
497
|
+
description: string;
|
|
498
|
+
tags?: string[] | undefined;
|
|
499
|
+
extends?: string | null | undefined;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* List presets options
|
|
504
|
+
*/
|
|
505
|
+
export interface ListPresetsOptions {
|
|
506
|
+
tag?: string | undefined;
|
|
507
|
+
verbose?: boolean | undefined;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Validate options
|
|
512
|
+
*/
|
|
513
|
+
export interface ValidateOptions {
|
|
514
|
+
strict?: boolean | undefined;
|
|
515
|
+
sections?: string[] | undefined;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Load with validation options
|
|
520
|
+
*/
|
|
521
|
+
export interface LoadWithValidationOptions {
|
|
522
|
+
validate?: boolean | undefined;
|
|
523
|
+
strict?: boolean | undefined;
|
|
524
|
+
silent?: boolean | undefined;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// =============================================================================
|
|
528
|
+
// Validation Helpers
|
|
529
|
+
// =============================================================================
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Format Zod validation errors into user-friendly messages
|
|
533
|
+
*/
|
|
534
|
+
export function formatValidationErrors(zodError: z.ZodError): string[] {
|
|
535
|
+
return zodError.issues.map(issue => {
|
|
536
|
+
const path = issue.path.join('.');
|
|
537
|
+
const prefix = path ? `${path}: ` : '';
|
|
538
|
+
|
|
539
|
+
switch (issue.code) {
|
|
540
|
+
case 'invalid_type':
|
|
541
|
+
return `${prefix}Expected ${issue.expected}, received ${issue.received}`;
|
|
542
|
+
case 'invalid_enum_value':
|
|
543
|
+
return `${prefix}Invalid value "${issue.received}". Expected one of: ${issue.options.join(', ')}`;
|
|
544
|
+
case 'too_small':
|
|
545
|
+
if (issue.type === 'string') {
|
|
546
|
+
return `${prefix}String must contain at least ${issue.minimum} character(s)`;
|
|
547
|
+
}
|
|
548
|
+
if (issue.type === 'array') {
|
|
549
|
+
return `${prefix}Array must contain at least ${issue.minimum} element(s)`;
|
|
550
|
+
}
|
|
551
|
+
return `${prefix}Value must be greater than or equal to ${issue.minimum}`;
|
|
552
|
+
case 'too_big':
|
|
553
|
+
return `${prefix}Value must be less than or equal to ${issue.maximum}`;
|
|
554
|
+
case 'invalid_string':
|
|
555
|
+
if (issue.validation === 'url') {
|
|
556
|
+
return `${prefix}Invalid URL format`;
|
|
557
|
+
}
|
|
558
|
+
if (issue.validation === 'regex') {
|
|
559
|
+
return `${prefix}Invalid format`;
|
|
560
|
+
}
|
|
561
|
+
return `${prefix}${issue.message}`;
|
|
562
|
+
default:
|
|
563
|
+
return `${prefix}${issue.message}`;
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Section schema mapping
|
|
570
|
+
*/
|
|
571
|
+
const sectionSchemas: Record<string, z.ZodTypeAny> = {
|
|
572
|
+
project: ProjectConfigSchema,
|
|
573
|
+
stack: StackConfigSchema,
|
|
574
|
+
plugins: PluginsSchema,
|
|
575
|
+
agents: AgentsConfigSchema,
|
|
576
|
+
skills: SkillsConfigSchema,
|
|
577
|
+
workflows: WorkflowsConfigSchema,
|
|
578
|
+
dashboard: DashboardConfigSchema,
|
|
579
|
+
quality: QualityConfigSchema,
|
|
580
|
+
context: ContextConfigSchema,
|
|
581
|
+
paths: PathsConfigSchema
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Validate a specific config section
|
|
586
|
+
*/
|
|
587
|
+
export function validateSection(section: string, data: unknown): SectionValidationResult {
|
|
588
|
+
const schema = sectionSchemas[section];
|
|
589
|
+
if (!schema) {
|
|
590
|
+
return {
|
|
591
|
+
valid: false,
|
|
592
|
+
errors: [`Unknown configuration section: ${section}`],
|
|
593
|
+
data: null
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const result = schema.safeParse(data);
|
|
598
|
+
|
|
599
|
+
if (result.success) {
|
|
600
|
+
return {
|
|
601
|
+
valid: true,
|
|
602
|
+
errors: [],
|
|
603
|
+
data: result.data
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return {
|
|
608
|
+
valid: false,
|
|
609
|
+
errors: formatValidationErrors(result.error),
|
|
610
|
+
data: null
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// =============================================================================
|
|
615
|
+
// Default Configuration
|
|
616
|
+
// =============================================================================
|
|
617
|
+
|
|
618
|
+
export const DEFAULT_CONFIG: ConfigInput = {
|
|
619
|
+
project: {
|
|
620
|
+
name: 'My Project',
|
|
621
|
+
description: '',
|
|
622
|
+
version: '1.0.0'
|
|
623
|
+
},
|
|
624
|
+
stack: {
|
|
625
|
+
framework: 'nextjs',
|
|
626
|
+
language: 'typescript',
|
|
627
|
+
database: 'postgresql',
|
|
628
|
+
hosting: 'vercel'
|
|
629
|
+
},
|
|
630
|
+
plugins: {
|
|
631
|
+
auth: { enabled: false, provider: 'clerk' },
|
|
632
|
+
payments: { enabled: false, provider: 'stripe' },
|
|
633
|
+
database: { enabled: true, provider: 'prisma' },
|
|
634
|
+
testing: { enabled: true, provider: 'vitest' },
|
|
635
|
+
security: { enabled: true },
|
|
636
|
+
ai: { enabled: false }
|
|
637
|
+
},
|
|
638
|
+
dashboard: {
|
|
639
|
+
port: 3456,
|
|
640
|
+
autoOpen: false
|
|
641
|
+
},
|
|
642
|
+
quality: {
|
|
643
|
+
preCommit: true,
|
|
644
|
+
prePush: false,
|
|
645
|
+
strictMode: false
|
|
646
|
+
},
|
|
647
|
+
paths: {
|
|
648
|
+
context: 'CLAUDE.md',
|
|
649
|
+
config: 'bootspring.config.js',
|
|
650
|
+
todo: 'todo.md'
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
// Config file names to search for
|
|
655
|
+
export const CONFIG_FILES = [
|
|
656
|
+
'bootspring.config.js',
|
|
657
|
+
'bootspring.config.mjs',
|
|
658
|
+
'bootspring.config.json',
|
|
659
|
+
'.bootspringrc',
|
|
660
|
+
'.bootspringrc.js',
|
|
661
|
+
'.bootspringrc.json'
|
|
662
|
+
];
|
|
663
|
+
|
|
664
|
+
// =============================================================================
|
|
665
|
+
// Configuration Presets
|
|
666
|
+
// =============================================================================
|
|
667
|
+
|
|
668
|
+
export const CONFIG_PRESETS: Record<string, PresetDefinition> = {
|
|
669
|
+
'saas-starter': {
|
|
670
|
+
name: 'SaaS Starter',
|
|
671
|
+
description: 'Full SaaS setup with auth, payments, and database',
|
|
672
|
+
tags: ['fullstack', 'production', 'monetization'],
|
|
673
|
+
extends: null,
|
|
674
|
+
config: {
|
|
675
|
+
stack: {
|
|
676
|
+
framework: 'nextjs',
|
|
677
|
+
language: 'typescript',
|
|
678
|
+
database: 'postgresql',
|
|
679
|
+
hosting: 'vercel'
|
|
680
|
+
},
|
|
681
|
+
plugins: {
|
|
682
|
+
auth: { enabled: true, provider: 'clerk', features: ['email_password', 'social_login'] },
|
|
683
|
+
payments: { enabled: true, provider: 'stripe', features: ['checkout', 'subscriptions'] },
|
|
684
|
+
database: { enabled: true, provider: 'prisma', features: ['migrations', 'seeding'] },
|
|
685
|
+
testing: { enabled: true, provider: 'vitest', features: ['unit', 'integration'] },
|
|
686
|
+
security: { enabled: true, features: ['input_validation', 'rate_limiting'] },
|
|
687
|
+
email: { enabled: true, provider: 'resend', features: ['transactional'] },
|
|
688
|
+
analytics: { enabled: true, provider: 'posthog', features: ['events', 'user_tracking'] },
|
|
689
|
+
ai: { enabled: false }
|
|
690
|
+
},
|
|
691
|
+
quality: {
|
|
692
|
+
preCommit: true,
|
|
693
|
+
prePush: false,
|
|
694
|
+
strictMode: false
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
|
|
699
|
+
'api-only': {
|
|
700
|
+
name: 'API Only',
|
|
701
|
+
description: 'Headless API configuration without frontend',
|
|
702
|
+
tags: ['backend', 'headless', 'microservice'],
|
|
703
|
+
extends: null,
|
|
704
|
+
config: {
|
|
705
|
+
stack: {
|
|
706
|
+
framework: 'express',
|
|
707
|
+
language: 'typescript',
|
|
708
|
+
database: 'postgresql',
|
|
709
|
+
hosting: 'railway'
|
|
710
|
+
},
|
|
711
|
+
plugins: {
|
|
712
|
+
auth: { enabled: true, provider: 'jwt', features: ['email_password'] },
|
|
713
|
+
payments: { enabled: false },
|
|
714
|
+
database: { enabled: true, provider: 'prisma', features: ['migrations', 'transactions'] },
|
|
715
|
+
testing: { enabled: true, provider: 'vitest', features: ['unit', 'integration'] },
|
|
716
|
+
security: { enabled: true, features: ['input_validation', 'rate_limiting', 'csrf'] },
|
|
717
|
+
monitoring: { enabled: true, provider: 'sentry', features: ['error_tracking', 'performance'] },
|
|
718
|
+
ai: { enabled: false }
|
|
719
|
+
},
|
|
720
|
+
quality: {
|
|
721
|
+
preCommit: true,
|
|
722
|
+
prePush: true,
|
|
723
|
+
strictMode: true
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
},
|
|
727
|
+
|
|
728
|
+
'static-site': {
|
|
729
|
+
name: 'Static Site',
|
|
730
|
+
description: 'Simple static site setup with optional CMS',
|
|
731
|
+
tags: ['static', 'content', 'marketing'],
|
|
732
|
+
extends: null,
|
|
733
|
+
config: {
|
|
734
|
+
stack: {
|
|
735
|
+
framework: 'astro',
|
|
736
|
+
language: 'typescript',
|
|
737
|
+
database: 'none',
|
|
738
|
+
hosting: 'vercel'
|
|
739
|
+
},
|
|
740
|
+
plugins: {
|
|
741
|
+
auth: { enabled: false },
|
|
742
|
+
payments: { enabled: false },
|
|
743
|
+
database: { enabled: false },
|
|
744
|
+
testing: { enabled: true, provider: 'vitest', features: ['unit'] },
|
|
745
|
+
security: { enabled: true, features: ['xss'] },
|
|
746
|
+
analytics: { enabled: true, provider: 'google_analytics', features: ['page_views'] },
|
|
747
|
+
ai: { enabled: false }
|
|
748
|
+
},
|
|
749
|
+
quality: {
|
|
750
|
+
preCommit: true,
|
|
751
|
+
prePush: false,
|
|
752
|
+
strictMode: false
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
},
|
|
756
|
+
|
|
757
|
+
'enterprise': {
|
|
758
|
+
name: 'Enterprise',
|
|
759
|
+
description: 'Enterprise-grade configuration with all security and quality features',
|
|
760
|
+
tags: ['enterprise', 'security', 'compliance'],
|
|
761
|
+
extends: 'saas-starter',
|
|
762
|
+
config: {
|
|
763
|
+
stack: {
|
|
764
|
+
framework: 'nextjs',
|
|
765
|
+
language: 'typescript',
|
|
766
|
+
database: 'postgresql',
|
|
767
|
+
hosting: 'aws'
|
|
768
|
+
},
|
|
769
|
+
plugins: {
|
|
770
|
+
auth: { enabled: true, provider: 'clerk', features: ['sso', 'mfa', 'rbac'] },
|
|
771
|
+
payments: { enabled: true, provider: 'stripe', features: ['checkout', 'subscriptions', 'invoices'] },
|
|
772
|
+
database: { enabled: true, provider: 'prisma', features: ['migrations', 'transactions', 'multi_tenant', 'soft_delete'] },
|
|
773
|
+
testing: { enabled: true, provider: 'vitest', features: ['unit', 'integration', 'e2e', 'coverage'] },
|
|
774
|
+
security: { enabled: true, features: ['input_validation', 'rate_limiting', 'csrf', 'audit', 'rbac', 'encryption'] },
|
|
775
|
+
monitoring: { enabled: true, provider: 'datadog', features: ['error_tracking', 'performance', 'logs', 'apm'] },
|
|
776
|
+
email: { enabled: true, provider: 'ses', features: ['transactional', 'tracking'] },
|
|
777
|
+
ai: { enabled: false }
|
|
778
|
+
},
|
|
779
|
+
quality: {
|
|
780
|
+
preCommit: true,
|
|
781
|
+
prePush: true,
|
|
782
|
+
preDeploy: { enabled: true, checks: ['lint', 'typecheck', 'test', 'build', 'security'] },
|
|
783
|
+
strictMode: true,
|
|
784
|
+
coverage: {
|
|
785
|
+
statements: 80,
|
|
786
|
+
branches: 75,
|
|
787
|
+
functions: 80,
|
|
788
|
+
lines: 80
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
},
|
|
793
|
+
|
|
794
|
+
'minimal': {
|
|
795
|
+
name: 'Minimal',
|
|
796
|
+
description: 'Bare minimum setup for quick prototyping',
|
|
797
|
+
tags: ['prototype', 'minimal', 'quick'],
|
|
798
|
+
extends: null,
|
|
799
|
+
config: {
|
|
800
|
+
stack: {
|
|
801
|
+
framework: 'nextjs',
|
|
802
|
+
language: 'typescript',
|
|
803
|
+
database: 'none',
|
|
804
|
+
hosting: 'vercel'
|
|
805
|
+
},
|
|
806
|
+
plugins: {
|
|
807
|
+
auth: { enabled: false },
|
|
808
|
+
payments: { enabled: false },
|
|
809
|
+
database: { enabled: false },
|
|
810
|
+
testing: { enabled: false },
|
|
811
|
+
security: { enabled: true },
|
|
812
|
+
ai: { enabled: false }
|
|
813
|
+
},
|
|
814
|
+
quality: {
|
|
815
|
+
preCommit: false,
|
|
816
|
+
prePush: false,
|
|
817
|
+
strictMode: false
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
},
|
|
821
|
+
|
|
822
|
+
'ai-app': {
|
|
823
|
+
name: 'AI Application',
|
|
824
|
+
description: 'AI-powered application with Claude integration',
|
|
825
|
+
tags: ['ai', 'llm', 'anthropic'],
|
|
826
|
+
extends: null,
|
|
827
|
+
config: {
|
|
828
|
+
stack: {
|
|
829
|
+
framework: 'nextjs',
|
|
830
|
+
language: 'typescript',
|
|
831
|
+
database: 'postgresql',
|
|
832
|
+
hosting: 'vercel'
|
|
833
|
+
},
|
|
834
|
+
plugins: {
|
|
835
|
+
auth: { enabled: true, provider: 'clerk', features: ['email_password'] },
|
|
836
|
+
payments: { enabled: false },
|
|
837
|
+
database: { enabled: true, provider: 'prisma', features: ['migrations'] },
|
|
838
|
+
testing: { enabled: true, provider: 'vitest', features: ['unit', 'mocking'] },
|
|
839
|
+
security: { enabled: true, features: ['input_validation', 'rate_limiting'] },
|
|
840
|
+
ai: { enabled: true, provider: 'anthropic', features: ['streaming', 'tool_use'] }
|
|
841
|
+
},
|
|
842
|
+
quality: {
|
|
843
|
+
preCommit: true,
|
|
844
|
+
prePush: false,
|
|
845
|
+
strictMode: false
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
// =============================================================================
|
|
852
|
+
// Helper Functions
|
|
853
|
+
// =============================================================================
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* Deep merge two objects
|
|
857
|
+
*/
|
|
858
|
+
export function deepMerge<T extends Record<string, unknown>>(target: T, source: Partial<T>): T {
|
|
859
|
+
const result = { ...target } as T;
|
|
860
|
+
|
|
861
|
+
for (const key of Object.keys(source) as (keyof T)[]) {
|
|
862
|
+
const sourceValue = source[key];
|
|
863
|
+
const targetValue = target[key];
|
|
864
|
+
|
|
865
|
+
if (Array.isArray(sourceValue)) {
|
|
866
|
+
(result as Record<string, unknown>)[key as string] = [...sourceValue];
|
|
867
|
+
} else if (
|
|
868
|
+
sourceValue !== null &&
|
|
869
|
+
typeof sourceValue === 'object' &&
|
|
870
|
+
!Array.isArray(sourceValue) &&
|
|
871
|
+
targetValue !== null &&
|
|
872
|
+
typeof targetValue === 'object' &&
|
|
873
|
+
!Array.isArray(targetValue)
|
|
874
|
+
) {
|
|
875
|
+
(result as Record<string, unknown>)[key as string] = deepMerge(
|
|
876
|
+
targetValue as Record<string, unknown>,
|
|
877
|
+
sourceValue as Record<string, unknown>
|
|
878
|
+
);
|
|
879
|
+
} else if (sourceValue !== undefined) {
|
|
880
|
+
(result as Record<string, unknown>)[key as string] = sourceValue;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
return result;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Find the project root directory
|
|
889
|
+
*/
|
|
890
|
+
export function findProjectRoot(): string {
|
|
891
|
+
let dir = process.cwd();
|
|
892
|
+
const root = path.parse(dir).root;
|
|
893
|
+
|
|
894
|
+
while (dir !== root) {
|
|
895
|
+
if (
|
|
896
|
+
fs.existsSync(path.join(dir, 'package.json')) ||
|
|
897
|
+
fs.existsSync(path.join(dir, 'bootspring.config.js')) ||
|
|
898
|
+
fs.existsSync(path.join(dir, '.git'))
|
|
899
|
+
) {
|
|
900
|
+
return dir;
|
|
901
|
+
}
|
|
902
|
+
dir = path.dirname(dir);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
return process.cwd();
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Find config file in project
|
|
910
|
+
*/
|
|
911
|
+
export function findConfigFile(projectRoot: string): string | null {
|
|
912
|
+
for (const filename of CONFIG_FILES) {
|
|
913
|
+
const filepath = path.join(projectRoot, filename);
|
|
914
|
+
if (fs.existsSync(filepath)) {
|
|
915
|
+
return filepath;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// =============================================================================
|
|
922
|
+
// Preset Functions
|
|
923
|
+
// =============================================================================
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Get a preset configuration
|
|
927
|
+
*/
|
|
928
|
+
export function getPreset(presetName: string): PresetDefinition | null {
|
|
929
|
+
return CONFIG_PRESETS[presetName] ?? null;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Resolve preset inheritance chain
|
|
934
|
+
*/
|
|
935
|
+
export function resolvePresetChain(presetName: string, seen: Set<string> = new Set()): ConfigInput {
|
|
936
|
+
const preset = CONFIG_PRESETS[presetName];
|
|
937
|
+
if (!preset) {
|
|
938
|
+
throw new Error(`Unknown preset: ${presetName}. Available: ${Object.keys(CONFIG_PRESETS).join(', ')}`);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
if (seen.has(presetName)) {
|
|
942
|
+
throw new Error(`Circular preset inheritance detected: ${presetName}`);
|
|
943
|
+
}
|
|
944
|
+
seen.add(presetName);
|
|
945
|
+
|
|
946
|
+
let config: ConfigInput = { ...DEFAULT_CONFIG };
|
|
947
|
+
|
|
948
|
+
if (preset.extends) {
|
|
949
|
+
const parentConfig = resolvePresetChain(preset.extends, seen);
|
|
950
|
+
config = deepMerge(config, parentConfig);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
config = deepMerge(config, preset.config as ConfigInput);
|
|
954
|
+
|
|
955
|
+
return config;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* Apply a preset to configuration
|
|
960
|
+
*/
|
|
961
|
+
export function applyPreset(presetNames: string | string[], overrides: Partial<ConfigInput> = {}): ConfigInput {
|
|
962
|
+
const presets = Array.isArray(presetNames) ? presetNames : [presetNames];
|
|
963
|
+
|
|
964
|
+
if (presets.length === 0) {
|
|
965
|
+
throw new Error('At least one preset name is required');
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
let config: ConfigInput = { ...DEFAULT_CONFIG };
|
|
969
|
+
|
|
970
|
+
for (const presetName of presets) {
|
|
971
|
+
const trimmedName = presetName.trim();
|
|
972
|
+
const presetConfig = resolvePresetChain(trimmedName);
|
|
973
|
+
config = deepMerge(config, presetConfig);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
config = deepMerge(config, overrides as ConfigInput);
|
|
977
|
+
|
|
978
|
+
return config;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Combine multiple presets
|
|
983
|
+
*/
|
|
984
|
+
export function combinePresets(presetNames: string[], overrides: Partial<ConfigInput> = {}): ConfigInput {
|
|
985
|
+
if (!Array.isArray(presetNames) || presetNames.length === 0) {
|
|
986
|
+
throw new Error('combinePresets requires a non-empty array of preset names');
|
|
987
|
+
}
|
|
988
|
+
return applyPreset(presetNames, overrides);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/**
|
|
992
|
+
* Parse preset string
|
|
993
|
+
*/
|
|
994
|
+
export function parsePresetString(presetString: string): string[] {
|
|
995
|
+
if (!presetString || typeof presetString !== 'string') {
|
|
996
|
+
return [];
|
|
997
|
+
}
|
|
998
|
+
return presetString.split(',').map(s => s.trim()).filter(Boolean);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/**
|
|
1002
|
+
* Validate preset names
|
|
1003
|
+
*/
|
|
1004
|
+
export function validatePresets(presetNames: string | string[]): { valid: boolean; errors: string[]; validPresets: string[] } {
|
|
1005
|
+
const presets = Array.isArray(presetNames) ? presetNames : [presetNames];
|
|
1006
|
+
const errors: string[] = [];
|
|
1007
|
+
const validPresets: string[] = [];
|
|
1008
|
+
const availablePresets = Object.keys(CONFIG_PRESETS);
|
|
1009
|
+
|
|
1010
|
+
for (const preset of presets) {
|
|
1011
|
+
if (CONFIG_PRESETS[preset]) {
|
|
1012
|
+
validPresets.push(preset);
|
|
1013
|
+
} else {
|
|
1014
|
+
errors.push(`Unknown preset: "${preset}". Available: ${availablePresets.join(', ')}`);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
return { valid: errors.length === 0, errors, validPresets };
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Get presets by tag
|
|
1023
|
+
*/
|
|
1024
|
+
export function getPresetsByTag(tag: string): string[] {
|
|
1025
|
+
return Object.entries(CONFIG_PRESETS)
|
|
1026
|
+
.filter(([, preset]) => preset.tags && preset.tags.includes(tag))
|
|
1027
|
+
.map(([key]) => key);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
/**
|
|
1031
|
+
* Create custom preset
|
|
1032
|
+
*/
|
|
1033
|
+
export function createCustomPreset(baseName: string, newName: string, customConfig: Partial<ConfigInput> = {}): PresetDefinition {
|
|
1034
|
+
const basePreset = CONFIG_PRESETS[baseName];
|
|
1035
|
+
if (!basePreset) {
|
|
1036
|
+
throw new Error(`Unknown base preset: ${baseName}`);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
return {
|
|
1040
|
+
name: newName,
|
|
1041
|
+
description: `Custom preset based on ${basePreset.name}`,
|
|
1042
|
+
tags: [...(basePreset.tags || []), 'custom'],
|
|
1043
|
+
extends: baseName,
|
|
1044
|
+
config: customConfig
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* List available presets
|
|
1050
|
+
*/
|
|
1051
|
+
export function listPresets(options: ListPresetsOptions = {}): PresetInfo[] {
|
|
1052
|
+
let entries = Object.entries(CONFIG_PRESETS);
|
|
1053
|
+
|
|
1054
|
+
if (options.tag) {
|
|
1055
|
+
entries = entries.filter(([, preset]) =>
|
|
1056
|
+
preset.tags && preset.tags.includes(options.tag!)
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
return entries.map(([key, preset]) => {
|
|
1061
|
+
const info: PresetInfo = {
|
|
1062
|
+
key,
|
|
1063
|
+
name: preset.name,
|
|
1064
|
+
description: preset.description
|
|
1065
|
+
};
|
|
1066
|
+
|
|
1067
|
+
if (options.verbose) {
|
|
1068
|
+
info.tags = preset.tags || [];
|
|
1069
|
+
info.extends = preset.extends;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
return info;
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// =============================================================================
|
|
1077
|
+
// Core Functions
|
|
1078
|
+
// =============================================================================
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* Load configuration from file
|
|
1082
|
+
*/
|
|
1083
|
+
export function load(projectRoot: string | null = null): LoadedConfig {
|
|
1084
|
+
const root = projectRoot ?? findProjectRoot();
|
|
1085
|
+
const configPath = findConfigFile(root);
|
|
1086
|
+
|
|
1087
|
+
let userConfig: Partial<Config> = {};
|
|
1088
|
+
|
|
1089
|
+
if (configPath) {
|
|
1090
|
+
try {
|
|
1091
|
+
if (configPath.endsWith('.json') || configPath.endsWith('.bootspringrc')) {
|
|
1092
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
1093
|
+
userConfig = JSON.parse(content) as Partial<Config>;
|
|
1094
|
+
} else {
|
|
1095
|
+
delete require.cache[require.resolve(configPath)];
|
|
1096
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1097
|
+
userConfig = require(configPath) as Partial<Config>;
|
|
1098
|
+
}
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1101
|
+
console.warn(`Warning: Could not load config from ${configPath}: ${message}`);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
const config: LoadedConfig = deepMerge(DEFAULT_CONFIG, userConfig);
|
|
1106
|
+
|
|
1107
|
+
config._projectRoot = root;
|
|
1108
|
+
config._configPath = configPath;
|
|
1109
|
+
config._bootspringDir = path.join(root, '.bootspring');
|
|
1110
|
+
|
|
1111
|
+
return config;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
/**
|
|
1115
|
+
* Save configuration to file
|
|
1116
|
+
*/
|
|
1117
|
+
export function save(config: LoadedConfig, filepath: string | null = null): boolean {
|
|
1118
|
+
const root = config._projectRoot ?? findProjectRoot();
|
|
1119
|
+
const targetPath = filepath ?? path.join(root, 'bootspring.config.js');
|
|
1120
|
+
|
|
1121
|
+
const cleanConfig = { ...config };
|
|
1122
|
+
delete cleanConfig._projectRoot;
|
|
1123
|
+
delete cleanConfig._configPath;
|
|
1124
|
+
delete cleanConfig._bootspringDir;
|
|
1125
|
+
delete cleanConfig._validation;
|
|
1126
|
+
|
|
1127
|
+
const content = `/**
|
|
1128
|
+
* Bootspring Configuration
|
|
1129
|
+
* https://bootspring.com/docs/configuration
|
|
1130
|
+
*/
|
|
1131
|
+
|
|
1132
|
+
module.exports = ${JSON.stringify(cleanConfig, null, 2)};
|
|
1133
|
+
`;
|
|
1134
|
+
|
|
1135
|
+
try {
|
|
1136
|
+
fs.writeFileSync(targetPath, content, 'utf-8');
|
|
1137
|
+
return true;
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1140
|
+
console.error(`Error saving config: ${message}`);
|
|
1141
|
+
return false;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
/**
|
|
1146
|
+
* Validate configuration
|
|
1147
|
+
*/
|
|
1148
|
+
export function validate(config: LoadedConfig, options: ValidateOptions = {}): ValidationResult {
|
|
1149
|
+
const configToValidate = { ...config };
|
|
1150
|
+
delete configToValidate._projectRoot;
|
|
1151
|
+
delete configToValidate._configPath;
|
|
1152
|
+
delete configToValidate._bootspringDir;
|
|
1153
|
+
delete configToValidate._validation;
|
|
1154
|
+
|
|
1155
|
+
const errors: string[] = [];
|
|
1156
|
+
const warnings: string[] = [];
|
|
1157
|
+
|
|
1158
|
+
if (options.sections && options.sections.length > 0) {
|
|
1159
|
+
let allValid = true;
|
|
1160
|
+
const validatedData: Record<string, unknown> = {};
|
|
1161
|
+
|
|
1162
|
+
for (const section of options.sections) {
|
|
1163
|
+
const sectionValue = (configToValidate as Record<string, unknown>)[section];
|
|
1164
|
+
if (sectionValue !== undefined) {
|
|
1165
|
+
const result = validateSection(section, sectionValue);
|
|
1166
|
+
if (!result.valid) {
|
|
1167
|
+
allValid = false;
|
|
1168
|
+
errors.push(...result.errors);
|
|
1169
|
+
} else {
|
|
1170
|
+
validatedData[section] = result.data;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
return {
|
|
1176
|
+
valid: allValid,
|
|
1177
|
+
errors,
|
|
1178
|
+
warnings,
|
|
1179
|
+
data: allValid ? validatedData as unknown as Config : null
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
const result = ConfigSchema.safeParse(configToValidate);
|
|
1184
|
+
|
|
1185
|
+
if (result.success) {
|
|
1186
|
+
return {
|
|
1187
|
+
valid: true,
|
|
1188
|
+
errors: [],
|
|
1189
|
+
warnings,
|
|
1190
|
+
data: result.data
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
return {
|
|
1195
|
+
valid: false,
|
|
1196
|
+
errors: formatValidationErrors(result.error),
|
|
1197
|
+
warnings,
|
|
1198
|
+
data: null
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/**
|
|
1203
|
+
* Load and optionally validate configuration
|
|
1204
|
+
*/
|
|
1205
|
+
export function loadWithValidation(projectRoot: string | null = null, options: LoadWithValidationOptions = {}): LoadedConfig {
|
|
1206
|
+
const { validate: shouldValidate = true, strict = false, silent = false } = options;
|
|
1207
|
+
|
|
1208
|
+
const config = load(projectRoot);
|
|
1209
|
+
|
|
1210
|
+
if (!shouldValidate) {
|
|
1211
|
+
config._validation = { skipped: true };
|
|
1212
|
+
return config;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
const validation = validate(config);
|
|
1216
|
+
|
|
1217
|
+
config._validation = {
|
|
1218
|
+
valid: validation.valid,
|
|
1219
|
+
errors: validation.errors,
|
|
1220
|
+
warnings: validation.warnings ?? [],
|
|
1221
|
+
validatedAt: new Date().toISOString()
|
|
1222
|
+
};
|
|
1223
|
+
|
|
1224
|
+
if (!validation.valid) {
|
|
1225
|
+
if (strict) {
|
|
1226
|
+
const errorMessage = `Configuration validation failed:\n${validation.errors.map(e => ` - ${e}`).join('\n')}`;
|
|
1227
|
+
throw new Error(errorMessage);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
if (!silent) {
|
|
1231
|
+
console.warn('\x1b[33mWarning: Configuration has validation issues:\x1b[0m');
|
|
1232
|
+
validation.errors.forEach(error => {
|
|
1233
|
+
console.warn(` \x1b[33m- ${error}\x1b[0m`);
|
|
1234
|
+
});
|
|
1235
|
+
console.warn('\x1b[33mThe configuration will still be used, but some features may not work correctly.\x1b[0m');
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
return config;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
/**
|
|
1243
|
+
* Get validation hint for error path
|
|
1244
|
+
*/
|
|
1245
|
+
export function getValidationHint(errorPath: string): string | null {
|
|
1246
|
+
const hints: Record<string, string> = {
|
|
1247
|
+
'stack.framework': 'Valid frameworks: nextjs, remix, nuxt, sveltekit, astro, express, fastify, hono, custom',
|
|
1248
|
+
'stack.language': 'Valid languages: typescript, javascript',
|
|
1249
|
+
'stack.database': 'Valid databases: postgresql, mysql, mongodb, sqlite, supabase, planetscale, none',
|
|
1250
|
+
'stack.hosting': 'Valid hosting options: vercel, railway, render, fly, aws, gcp, azure, cloudflare, self-hosted, custom',
|
|
1251
|
+
'plugins.auth.provider': 'Valid auth providers: clerk, nextauth, auth0, supabase, jwt, custom',
|
|
1252
|
+
'plugins.payments.provider': 'Valid payment providers: stripe, paddle, lemonsqueezy, paypal, custom',
|
|
1253
|
+
'plugins.database.provider': 'Valid database ORMs: prisma, drizzle, typeorm, kysely, custom',
|
|
1254
|
+
'plugins.testing.provider': 'Valid test frameworks: vitest, jest, playwright, cypress, custom',
|
|
1255
|
+
'plugins.ai.providers': 'Valid AI providers: anthropic, openai, google, cohere, huggingface, custom',
|
|
1256
|
+
'dashboard.port': 'Port must be between 1024 and 65535',
|
|
1257
|
+
'project.version': 'Version must follow semver format (e.g., 1.0.0)'
|
|
1258
|
+
};
|
|
1259
|
+
|
|
1260
|
+
return hints[errorPath] ?? null;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
/**
|
|
1264
|
+
* Parse and validate configuration with defaults
|
|
1265
|
+
*/
|
|
1266
|
+
export function parseConfig(config: LoadedConfig): Config {
|
|
1267
|
+
const configToParse = { ...config };
|
|
1268
|
+
delete configToParse._projectRoot;
|
|
1269
|
+
delete configToParse._configPath;
|
|
1270
|
+
delete configToParse._bootspringDir;
|
|
1271
|
+
delete configToParse._validation;
|
|
1272
|
+
|
|
1273
|
+
const result = ConfigSchema.safeParse(configToParse);
|
|
1274
|
+
if (result.success) {
|
|
1275
|
+
return result.data;
|
|
1276
|
+
}
|
|
1277
|
+
return configToParse as Config;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
/**
|
|
1281
|
+
* Get default configuration
|
|
1282
|
+
*/
|
|
1283
|
+
export function getDefaults(): ConfigInput {
|
|
1284
|
+
return { ...DEFAULT_CONFIG };
|
|
1285
|
+
}
|