@girardmedia/bootspring 2.2.0 → 2.2.1
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 +127 -73
- package/claude-commands/agent.md +34 -0
- package/claude-commands/bs.md +31 -0
- package/claude-commands/build.md +25 -0
- package/claude-commands/skill.md +31 -0
- package/claude-commands/todo.md +25 -0
- package/dist/core/index.d.ts +5814 -0
- package/dist/core.js +5779 -0
- package/dist/index.js +93883 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp-server.js +2298 -0
- package/package.json +22 -55
- package/core/api-client.d.ts +0 -69
- package/core/api-client.js +0 -1482
- package/core/auth.d.ts +0 -98
- package/core/auth.js +0 -737
- package/core/build-orchestrator.js +0 -508
- package/core/build-state.js +0 -612
- package/core/config.d.ts +0 -106
- package/core/config.js +0 -1328
- package/core/context-loader.js +0 -580
- package/core/context.d.ts +0 -61
- package/core/context.js +0 -327
- package/core/entitlements.d.ts +0 -70
- package/core/entitlements.js +0 -322
- package/core/index.d.ts +0 -53
- package/core/index.js +0 -62
- package/core/mcp-config.js +0 -115
- package/core/policies.d.ts +0 -43
- package/core/policies.js +0 -113
- package/core/policy-matrix.js +0 -303
- package/core/project-activity.js +0 -175
- package/core/redaction.d.ts +0 -5
- package/core/redaction.js +0 -63
- package/core/self-update.js +0 -259
- package/core/session.js +0 -353
- package/core/task-extractor.js +0 -1098
- package/core/telemetry.d.ts +0 -55
- package/core/telemetry.js +0 -617
- package/core/tier-enforcement.js +0 -928
- package/core/utils.d.ts +0 -90
- package/core/utils.js +0 -455
- package/core/validation.js +0 -572
- package/mcp/server.d.ts +0 -57
- package/mcp/server.js +0 -264
package/core/config.js
DELETED
|
@@ -1,1328 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bootspring Configuration
|
|
3
|
-
* Handles loading and validating bootspring.config.js
|
|
4
|
-
*
|
|
5
|
-
* @package bootspring
|
|
6
|
-
* @module core/config
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
const { z } = require('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
|
-
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
|
-
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
|
-
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
|
-
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
|
-
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
|
-
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
|
-
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
|
-
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
|
-
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
|
-
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
|
-
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); // Allow additional plugins with base schema
|
|
144
|
-
|
|
145
|
-
// Backward compatible alias
|
|
146
|
-
const PluginSchema = BasePluginSchema;
|
|
147
|
-
|
|
148
|
-
// -----------------------------------------------------------------------------
|
|
149
|
-
// Agent Configuration Schema
|
|
150
|
-
// -----------------------------------------------------------------------------
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Individual agent configuration
|
|
154
|
-
*/
|
|
155
|
-
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
|
-
const AgentsConfigSchema = z.object({
|
|
166
|
-
// Enable/disable specific agents
|
|
167
|
-
enabled: z.record(z.string(), z.boolean()).optional(),
|
|
168
|
-
// Custom agent configurations
|
|
169
|
-
custom: z.record(z.string(), AgentConfigSchema).optional(),
|
|
170
|
-
// Default agents to suggest based on project type
|
|
171
|
-
defaults: z.array(z.string()).optional(),
|
|
172
|
-
// Agent invocation settings
|
|
173
|
-
settings: z.object({
|
|
174
|
-
maxConcurrent: z.number().int().min(1).max(10).optional().default(3),
|
|
175
|
-
timeout: z.number().int().min(1000).max(300000).optional().default(60000),
|
|
176
|
-
verbose: z.boolean().optional().default(false)
|
|
177
|
-
}).optional()
|
|
178
|
-
}).passthrough();
|
|
179
|
-
|
|
180
|
-
// -----------------------------------------------------------------------------
|
|
181
|
-
// Skills Configuration Schema
|
|
182
|
-
// -----------------------------------------------------------------------------
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Individual skill configuration
|
|
186
|
-
*/
|
|
187
|
-
const SkillConfigSchema = z.object({
|
|
188
|
-
enabled: z.boolean().optional().default(true),
|
|
189
|
-
tier: z.enum(['free', 'pro', 'premium']).optional().default('free'),
|
|
190
|
-
category: z.string().optional(),
|
|
191
|
-
maxChars: z.number().int().min(100).optional()
|
|
192
|
-
}).passthrough();
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Skills section configuration
|
|
196
|
-
*/
|
|
197
|
-
const SkillsConfigSchema = z.object({
|
|
198
|
-
// Enable/disable skill categories
|
|
199
|
-
categories: z.record(z.string(), z.boolean()).optional(),
|
|
200
|
-
// Custom skill configurations
|
|
201
|
-
custom: z.record(z.string(), SkillConfigSchema).optional(),
|
|
202
|
-
// External skills settings
|
|
203
|
-
external: z.object({
|
|
204
|
-
enabled: z.boolean().optional().default(false),
|
|
205
|
-
manifestUrl: z.string().url().optional(),
|
|
206
|
-
cacheDir: z.string().optional(),
|
|
207
|
-
requireSignature: z.boolean().optional().default(false)
|
|
208
|
-
}).optional(),
|
|
209
|
-
// Skill invocation settings
|
|
210
|
-
settings: z.object({
|
|
211
|
-
defaultMaxChars: z.number().int().min(100).optional().default(50000),
|
|
212
|
-
summaryByDefault: z.boolean().optional().default(false),
|
|
213
|
-
includeExternal: z.boolean().optional().default(false)
|
|
214
|
-
}).optional()
|
|
215
|
-
}).passthrough();
|
|
216
|
-
|
|
217
|
-
// -----------------------------------------------------------------------------
|
|
218
|
-
// Workflows Configuration Schema
|
|
219
|
-
// -----------------------------------------------------------------------------
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Workflow phase schema
|
|
223
|
-
*/
|
|
224
|
-
const WorkflowPhaseSchema = z.object({
|
|
225
|
-
name: z.string().min(1, 'Phase name is required'),
|
|
226
|
-
agents: z.array(z.string()).min(1, 'At least one agent is required'),
|
|
227
|
-
duration: z.string().optional(),
|
|
228
|
-
parallel: z.boolean().optional().default(false),
|
|
229
|
-
description: z.string().optional()
|
|
230
|
-
}).passthrough();
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Individual workflow configuration
|
|
234
|
-
*/
|
|
235
|
-
const WorkflowConfigSchema = z.object({
|
|
236
|
-
name: z.string().min(1, 'Workflow name is required'),
|
|
237
|
-
description: z.string().optional(),
|
|
238
|
-
tier: z.enum(['free', 'pro']).optional().default('free'),
|
|
239
|
-
pack: z.string().optional(),
|
|
240
|
-
outcomes: z.array(z.string()).optional(),
|
|
241
|
-
completionSignals: z.array(z.string()).optional(),
|
|
242
|
-
phases: z.array(WorkflowPhaseSchema).min(1, 'At least one phase is required')
|
|
243
|
-
}).passthrough();
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Workflows section configuration
|
|
247
|
-
*/
|
|
248
|
-
const WorkflowsConfigSchema = z.object({
|
|
249
|
-
// Enable/disable specific workflows
|
|
250
|
-
enabled: z.record(z.string(), z.boolean()).optional(),
|
|
251
|
-
// Custom workflow definitions
|
|
252
|
-
custom: z.record(z.string(), WorkflowConfigSchema).optional(),
|
|
253
|
-
// Default workflow for the project
|
|
254
|
-
default: z.string().optional(),
|
|
255
|
-
// Workflow settings
|
|
256
|
-
settings: z.object({
|
|
257
|
-
autoAdvance: z.boolean().optional().default(true),
|
|
258
|
-
pauseBetweenPhases: z.boolean().optional().default(false),
|
|
259
|
-
trackSignals: z.boolean().optional().default(true),
|
|
260
|
-
emitTelemetry: z.boolean().optional().default(true)
|
|
261
|
-
}).optional()
|
|
262
|
-
}).passthrough();
|
|
263
|
-
|
|
264
|
-
// -----------------------------------------------------------------------------
|
|
265
|
-
// Quality Configuration Schema
|
|
266
|
-
// -----------------------------------------------------------------------------
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Quality check configuration
|
|
270
|
-
*/
|
|
271
|
-
const QualityCheckSchema = z.union([
|
|
272
|
-
z.boolean(),
|
|
273
|
-
z.object({
|
|
274
|
-
enabled: z.boolean().optional().default(true),
|
|
275
|
-
checks: z.array(z.enum([
|
|
276
|
-
'lint', 'typecheck', 'test', 'build', 'security', 'coverage', 'format'
|
|
277
|
-
])).optional()
|
|
278
|
-
})
|
|
279
|
-
]);
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Quality section configuration (extended)
|
|
283
|
-
*/
|
|
284
|
-
const QualityConfigSchema = z.object({
|
|
285
|
-
preCommit: QualityCheckSchema.optional().default(true),
|
|
286
|
-
prePush: QualityCheckSchema.optional().default(false),
|
|
287
|
-
preDeploy: QualityCheckSchema.optional(),
|
|
288
|
-
strictMode: z.boolean().optional().default(false),
|
|
289
|
-
// Coverage thresholds
|
|
290
|
-
coverage: z.object({
|
|
291
|
-
statements: z.number().min(0).max(100).optional(),
|
|
292
|
-
branches: z.number().min(0).max(100).optional(),
|
|
293
|
-
functions: z.number().min(0).max(100).optional(),
|
|
294
|
-
lines: z.number().min(0).max(100).optional()
|
|
295
|
-
}).optional()
|
|
296
|
-
}).passthrough();
|
|
297
|
-
|
|
298
|
-
// -----------------------------------------------------------------------------
|
|
299
|
-
// Context Configuration Schema
|
|
300
|
-
// -----------------------------------------------------------------------------
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Context generation configuration
|
|
304
|
-
*/
|
|
305
|
-
const ContextConfigSchema = z.object({
|
|
306
|
-
includeEnvVars: z.boolean().optional().default(true),
|
|
307
|
-
includeTechStack: z.boolean().optional().default(true),
|
|
308
|
-
includePlugins: z.boolean().optional().default(true),
|
|
309
|
-
includeGitInfo: z.boolean().optional().default(true),
|
|
310
|
-
includeTodos: z.boolean().optional().default(true),
|
|
311
|
-
includeLearnings: z.boolean().optional().default(true),
|
|
312
|
-
customSections: z.array(z.object({
|
|
313
|
-
title: z.string(),
|
|
314
|
-
content: z.string()
|
|
315
|
-
})).optional().default([]),
|
|
316
|
-
// Maximum context file size
|
|
317
|
-
maxSize: z.number().int().min(1000).optional()
|
|
318
|
-
}).passthrough();
|
|
319
|
-
|
|
320
|
-
// -----------------------------------------------------------------------------
|
|
321
|
-
// Paths Configuration Schema
|
|
322
|
-
// -----------------------------------------------------------------------------
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Paths configuration
|
|
326
|
-
*/
|
|
327
|
-
const PathsConfigSchema = z.object({
|
|
328
|
-
context: z.string().optional().default('CLAUDE.md'),
|
|
329
|
-
config: z.string().optional().default('bootspring.config.js'),
|
|
330
|
-
todo: z.string().optional().default('todo.md'),
|
|
331
|
-
roadmap: z.string().optional().default('ROADMAP.md'),
|
|
332
|
-
changelog: z.string().optional().default('CHANGELOG.md'),
|
|
333
|
-
state: z.string().optional().default('.bootspring')
|
|
334
|
-
}).passthrough();
|
|
335
|
-
|
|
336
|
-
// -----------------------------------------------------------------------------
|
|
337
|
-
// Dashboard Configuration Schema
|
|
338
|
-
// -----------------------------------------------------------------------------
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Dashboard configuration
|
|
342
|
-
*/
|
|
343
|
-
const DashboardConfigSchema = z.object({
|
|
344
|
-
port: z.number().int().min(1024).max(65535).optional().default(3456),
|
|
345
|
-
autoOpen: z.boolean().optional().default(false),
|
|
346
|
-
host: z.string().optional().default('localhost'),
|
|
347
|
-
theme: z.enum(['light', 'dark', 'system']).optional().default('system')
|
|
348
|
-
}).passthrough();
|
|
349
|
-
|
|
350
|
-
// -----------------------------------------------------------------------------
|
|
351
|
-
// Project Configuration Schema
|
|
352
|
-
// -----------------------------------------------------------------------------
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Project information schema
|
|
356
|
-
*/
|
|
357
|
-
const ProjectConfigSchema = z.object({
|
|
358
|
-
name: z.string().min(1, 'Project name is required'),
|
|
359
|
-
description: z.string().optional().default(''),
|
|
360
|
-
version: z.string().regex(/^\d+\.\d+\.\d+/, 'Version must be semver format').optional().default('1.0.0'),
|
|
361
|
-
author: z.string().optional(),
|
|
362
|
-
license: z.string().optional(),
|
|
363
|
-
repository: z.string().url().optional()
|
|
364
|
-
}).passthrough();
|
|
365
|
-
|
|
366
|
-
// -----------------------------------------------------------------------------
|
|
367
|
-
// Stack Configuration Schema
|
|
368
|
-
// -----------------------------------------------------------------------------
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Technology stack configuration
|
|
372
|
-
*/
|
|
373
|
-
const StackConfigSchema = z.object({
|
|
374
|
-
framework: z.enum([
|
|
375
|
-
'nextjs', 'remix', 'nuxt', 'sveltekit', 'astro', 'express', 'fastify', 'hono', 'custom'
|
|
376
|
-
]).optional(),
|
|
377
|
-
language: z.enum(['typescript', 'javascript']).optional(),
|
|
378
|
-
database: z.enum(['postgresql', 'mysql', 'mongodb', 'sqlite', 'supabase', 'planetscale', 'none']).optional(),
|
|
379
|
-
hosting: z.enum([
|
|
380
|
-
'vercel', 'railway', 'render', 'fly', 'aws', 'gcp', 'azure', 'cloudflare', 'self-hosted', 'custom'
|
|
381
|
-
]).optional()
|
|
382
|
-
}).passthrough();
|
|
383
|
-
|
|
384
|
-
// -----------------------------------------------------------------------------
|
|
385
|
-
// Main Configuration Schema
|
|
386
|
-
// -----------------------------------------------------------------------------
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Complete Bootspring configuration schema
|
|
390
|
-
*/
|
|
391
|
-
const ConfigSchema = z.object({
|
|
392
|
-
// Core project settings
|
|
393
|
-
project: ProjectConfigSchema.optional(),
|
|
394
|
-
stack: StackConfigSchema.optional(),
|
|
395
|
-
|
|
396
|
-
// Feature configurations
|
|
397
|
-
plugins: PluginsSchema.optional(),
|
|
398
|
-
agents: AgentsConfigSchema.optional(),
|
|
399
|
-
skills: SkillsConfigSchema.optional(),
|
|
400
|
-
workflows: WorkflowsConfigSchema.optional(),
|
|
401
|
-
|
|
402
|
-
// Tool configurations
|
|
403
|
-
dashboard: DashboardConfigSchema.optional(),
|
|
404
|
-
quality: QualityConfigSchema.optional(),
|
|
405
|
-
context: ContextConfigSchema.optional(),
|
|
406
|
-
paths: PathsConfigSchema.optional(),
|
|
407
|
-
|
|
408
|
-
// MCP configuration
|
|
409
|
-
mcp: z.object({
|
|
410
|
-
enabled: z.boolean().optional().default(false),
|
|
411
|
-
servers: z.record(z.string(), z.object({
|
|
412
|
-
command: z.string(),
|
|
413
|
-
args: z.array(z.string()).optional(),
|
|
414
|
-
env: z.record(z.string(), z.string()).optional()
|
|
415
|
-
})).optional()
|
|
416
|
-
}).optional()
|
|
417
|
-
}).passthrough();
|
|
418
|
-
|
|
419
|
-
// =============================================================================
|
|
420
|
-
// Validation Helpers
|
|
421
|
-
// =============================================================================
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Format Zod validation errors into user-friendly messages
|
|
425
|
-
* @param {z.ZodError} zodError - Zod error object
|
|
426
|
-
* @returns {string[]} Array of formatted error messages
|
|
427
|
-
*/
|
|
428
|
-
function formatValidationErrors(zodError) {
|
|
429
|
-
return zodError.issues.map(issue => {
|
|
430
|
-
const path = issue.path.join('.');
|
|
431
|
-
const prefix = path ? `${path}: ` : '';
|
|
432
|
-
|
|
433
|
-
switch (issue.code) {
|
|
434
|
-
case 'invalid_type':
|
|
435
|
-
return `${prefix}Expected ${issue.expected}, received ${issue.received}`;
|
|
436
|
-
case 'invalid_enum_value':
|
|
437
|
-
return `${prefix}Invalid value "${issue.received}". Expected one of: ${issue.options.join(', ')}`;
|
|
438
|
-
case 'too_small':
|
|
439
|
-
if (issue.type === 'string') {
|
|
440
|
-
return `${prefix}String must contain at least ${issue.minimum} character(s)`;
|
|
441
|
-
}
|
|
442
|
-
if (issue.type === 'array') {
|
|
443
|
-
return `${prefix}Array must contain at least ${issue.minimum} element(s)`;
|
|
444
|
-
}
|
|
445
|
-
return `${prefix}Value must be greater than or equal to ${issue.minimum}`;
|
|
446
|
-
case 'too_big':
|
|
447
|
-
return `${prefix}Value must be less than or equal to ${issue.maximum}`;
|
|
448
|
-
case 'invalid_string':
|
|
449
|
-
if (issue.validation === 'url') {
|
|
450
|
-
return `${prefix}Invalid URL format`;
|
|
451
|
-
}
|
|
452
|
-
if (issue.validation === 'regex') {
|
|
453
|
-
return `${prefix}Invalid format`;
|
|
454
|
-
}
|
|
455
|
-
return `${prefix}${issue.message}`;
|
|
456
|
-
default:
|
|
457
|
-
return `${prefix}${issue.message}`;
|
|
458
|
-
}
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/**
|
|
463
|
-
* Validate a specific config section
|
|
464
|
-
* @param {string} section - Section name (e.g., 'plugins', 'agents')
|
|
465
|
-
* @param {object} data - Section data to validate
|
|
466
|
-
* @returns {object} Validation result { valid, errors, data }
|
|
467
|
-
*/
|
|
468
|
-
function validateSection(section, data) {
|
|
469
|
-
const sectionSchemas = {
|
|
470
|
-
project: ProjectConfigSchema,
|
|
471
|
-
stack: StackConfigSchema,
|
|
472
|
-
plugins: PluginsSchema,
|
|
473
|
-
agents: AgentsConfigSchema,
|
|
474
|
-
skills: SkillsConfigSchema,
|
|
475
|
-
workflows: WorkflowsConfigSchema,
|
|
476
|
-
dashboard: DashboardConfigSchema,
|
|
477
|
-
quality: QualityConfigSchema,
|
|
478
|
-
context: ContextConfigSchema,
|
|
479
|
-
paths: PathsConfigSchema
|
|
480
|
-
};
|
|
481
|
-
|
|
482
|
-
const schema = sectionSchemas[section];
|
|
483
|
-
if (!schema) {
|
|
484
|
-
return {
|
|
485
|
-
valid: false,
|
|
486
|
-
errors: [`Unknown configuration section: ${section}`],
|
|
487
|
-
data: null
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
const result = schema.safeParse(data);
|
|
492
|
-
|
|
493
|
-
if (result.success) {
|
|
494
|
-
return {
|
|
495
|
-
valid: true,
|
|
496
|
-
errors: [],
|
|
497
|
-
data: result.data
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
return {
|
|
502
|
-
valid: false,
|
|
503
|
-
errors: formatValidationErrors(result.error),
|
|
504
|
-
data: null
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// Default configuration
|
|
509
|
-
const DEFAULT_CONFIG = {
|
|
510
|
-
project: {
|
|
511
|
-
name: 'My Project',
|
|
512
|
-
description: '',
|
|
513
|
-
version: '1.0.0'
|
|
514
|
-
},
|
|
515
|
-
|
|
516
|
-
stack: {
|
|
517
|
-
framework: 'nextjs',
|
|
518
|
-
language: 'typescript',
|
|
519
|
-
database: 'postgresql',
|
|
520
|
-
hosting: 'vercel'
|
|
521
|
-
},
|
|
522
|
-
|
|
523
|
-
plugins: {
|
|
524
|
-
auth: { enabled: false, provider: 'clerk' },
|
|
525
|
-
payments: { enabled: false, provider: 'stripe' },
|
|
526
|
-
database: { enabled: true, provider: 'prisma' },
|
|
527
|
-
testing: { enabled: true, provider: 'vitest' },
|
|
528
|
-
security: { enabled: true },
|
|
529
|
-
ai: { enabled: false, providers: [] }
|
|
530
|
-
},
|
|
531
|
-
|
|
532
|
-
dashboard: {
|
|
533
|
-
port: 3456,
|
|
534
|
-
autoOpen: false
|
|
535
|
-
},
|
|
536
|
-
|
|
537
|
-
quality: {
|
|
538
|
-
preCommit: true,
|
|
539
|
-
prePush: false,
|
|
540
|
-
strictMode: false
|
|
541
|
-
},
|
|
542
|
-
|
|
543
|
-
paths: {
|
|
544
|
-
context: 'CLAUDE.md',
|
|
545
|
-
config: 'bootspring.config.js',
|
|
546
|
-
todo: 'todo.md'
|
|
547
|
-
}
|
|
548
|
-
};
|
|
549
|
-
|
|
550
|
-
// Config file names to search for
|
|
551
|
-
const CONFIG_FILES = [
|
|
552
|
-
'bootspring.config.js',
|
|
553
|
-
'bootspring.config.mjs',
|
|
554
|
-
'bootspring.config.json',
|
|
555
|
-
'.bootspringrc',
|
|
556
|
-
'.bootspringrc.js',
|
|
557
|
-
'.bootspringrc.json'
|
|
558
|
-
];
|
|
559
|
-
|
|
560
|
-
/**
|
|
561
|
-
* Predefined configuration presets
|
|
562
|
-
* Use with --preset flag or applyPreset() function
|
|
563
|
-
*
|
|
564
|
-
* Available presets:
|
|
565
|
-
* - saas-starter: Full SaaS setup (auth, payments, database)
|
|
566
|
-
* - api-only: Headless API configuration
|
|
567
|
-
* - static-site: Simple static site setup
|
|
568
|
-
* - enterprise: Enterprise-grade configuration
|
|
569
|
-
* - minimal: Bare minimum for prototyping
|
|
570
|
-
* - ai-app: AI-powered application
|
|
571
|
-
*
|
|
572
|
-
* Usage: bootspring init --preset saas-starter
|
|
573
|
-
* Combine: bootspring init --preset saas-starter,ai-app
|
|
574
|
-
*/
|
|
575
|
-
const CONFIG_PRESETS = {
|
|
576
|
-
'saas-starter': {
|
|
577
|
-
name: 'SaaS Starter',
|
|
578
|
-
description: 'Full SaaS setup with auth, payments, and database',
|
|
579
|
-
tags: ['fullstack', 'production', 'monetization'],
|
|
580
|
-
extends: null,
|
|
581
|
-
config: {
|
|
582
|
-
stack: {
|
|
583
|
-
framework: 'nextjs',
|
|
584
|
-
language: 'typescript',
|
|
585
|
-
database: 'postgresql',
|
|
586
|
-
hosting: 'vercel'
|
|
587
|
-
},
|
|
588
|
-
plugins: {
|
|
589
|
-
auth: { enabled: true, provider: 'clerk', features: ['email_password', 'social_login'] },
|
|
590
|
-
payments: { enabled: true, provider: 'stripe', features: ['checkout', 'subscriptions'] },
|
|
591
|
-
database: { enabled: true, provider: 'prisma', features: ['migrations', 'seeding'] },
|
|
592
|
-
testing: { enabled: true, provider: 'vitest', features: ['unit', 'integration'] },
|
|
593
|
-
security: { enabled: true, features: ['input_validation', 'rate_limiting'] },
|
|
594
|
-
email: { enabled: true, provider: 'resend', features: ['transactional'] },
|
|
595
|
-
analytics: { enabled: true, provider: 'posthog', features: ['events', 'user_tracking'] },
|
|
596
|
-
ai: { enabled: false }
|
|
597
|
-
},
|
|
598
|
-
quality: {
|
|
599
|
-
preCommit: true,
|
|
600
|
-
prePush: false,
|
|
601
|
-
strictMode: false
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
},
|
|
605
|
-
|
|
606
|
-
'api-only': {
|
|
607
|
-
name: 'API Only',
|
|
608
|
-
description: 'Headless API configuration without frontend',
|
|
609
|
-
tags: ['backend', 'headless', 'microservice'],
|
|
610
|
-
extends: null,
|
|
611
|
-
config: {
|
|
612
|
-
stack: {
|
|
613
|
-
framework: 'express',
|
|
614
|
-
language: 'typescript',
|
|
615
|
-
database: 'postgresql',
|
|
616
|
-
hosting: 'railway'
|
|
617
|
-
},
|
|
618
|
-
plugins: {
|
|
619
|
-
auth: { enabled: true, provider: 'jwt', features: ['email_password'] },
|
|
620
|
-
payments: { enabled: false },
|
|
621
|
-
database: { enabled: true, provider: 'prisma', features: ['migrations', 'transactions'] },
|
|
622
|
-
testing: { enabled: true, provider: 'vitest', features: ['unit', 'integration'] },
|
|
623
|
-
security: { enabled: true, features: ['input_validation', 'rate_limiting', 'csrf'] },
|
|
624
|
-
monitoring: { enabled: true, provider: 'sentry', features: ['error_tracking', 'performance'] },
|
|
625
|
-
ai: { enabled: false }
|
|
626
|
-
},
|
|
627
|
-
quality: {
|
|
628
|
-
preCommit: true,
|
|
629
|
-
prePush: true,
|
|
630
|
-
strictMode: true
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
},
|
|
634
|
-
|
|
635
|
-
'static-site': {
|
|
636
|
-
name: 'Static Site',
|
|
637
|
-
description: 'Simple static site setup with optional CMS',
|
|
638
|
-
tags: ['static', 'content', 'marketing'],
|
|
639
|
-
extends: null,
|
|
640
|
-
config: {
|
|
641
|
-
stack: {
|
|
642
|
-
framework: 'astro',
|
|
643
|
-
language: 'typescript',
|
|
644
|
-
database: 'none',
|
|
645
|
-
hosting: 'vercel'
|
|
646
|
-
},
|
|
647
|
-
plugins: {
|
|
648
|
-
auth: { enabled: false },
|
|
649
|
-
payments: { enabled: false },
|
|
650
|
-
database: { enabled: false },
|
|
651
|
-
testing: { enabled: true, provider: 'vitest', features: ['unit'] },
|
|
652
|
-
security: { enabled: true, features: ['xss'] },
|
|
653
|
-
analytics: { enabled: true, provider: 'google_analytics', features: ['page_views'] },
|
|
654
|
-
ai: { enabled: false }
|
|
655
|
-
},
|
|
656
|
-
quality: {
|
|
657
|
-
preCommit: true,
|
|
658
|
-
prePush: false,
|
|
659
|
-
strictMode: false
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
},
|
|
663
|
-
|
|
664
|
-
'enterprise': {
|
|
665
|
-
name: 'Enterprise',
|
|
666
|
-
description: 'Enterprise-grade configuration with all security and quality features',
|
|
667
|
-
tags: ['enterprise', 'security', 'compliance'],
|
|
668
|
-
extends: 'saas-starter',
|
|
669
|
-
config: {
|
|
670
|
-
stack: {
|
|
671
|
-
framework: 'nextjs',
|
|
672
|
-
language: 'typescript',
|
|
673
|
-
database: 'postgresql',
|
|
674
|
-
hosting: 'aws'
|
|
675
|
-
},
|
|
676
|
-
plugins: {
|
|
677
|
-
auth: { enabled: true, provider: 'clerk', features: ['sso', 'mfa', 'rbac'] },
|
|
678
|
-
payments: { enabled: true, provider: 'stripe', features: ['checkout', 'subscriptions', 'invoices'] },
|
|
679
|
-
database: { enabled: true, provider: 'prisma', features: ['migrations', 'transactions', 'multi_tenant', 'soft_delete'] },
|
|
680
|
-
testing: { enabled: true, provider: 'vitest', features: ['unit', 'integration', 'e2e', 'coverage'] },
|
|
681
|
-
security: { enabled: true, features: ['input_validation', 'rate_limiting', 'csrf', 'audit', 'rbac', 'encryption'] },
|
|
682
|
-
monitoring: { enabled: true, provider: 'datadog', features: ['error_tracking', 'performance', 'logs', 'apm'] },
|
|
683
|
-
email: { enabled: true, provider: 'ses', features: ['transactional', 'tracking'] },
|
|
684
|
-
ai: { enabled: false }
|
|
685
|
-
},
|
|
686
|
-
quality: {
|
|
687
|
-
preCommit: true,
|
|
688
|
-
prePush: true,
|
|
689
|
-
preDeploy: { enabled: true, checks: ['lint', 'typecheck', 'test', 'build', 'security'] },
|
|
690
|
-
strictMode: true,
|
|
691
|
-
coverage: {
|
|
692
|
-
statements: 80,
|
|
693
|
-
branches: 75,
|
|
694
|
-
functions: 80,
|
|
695
|
-
lines: 80
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
},
|
|
700
|
-
|
|
701
|
-
'minimal': {
|
|
702
|
-
name: 'Minimal',
|
|
703
|
-
description: 'Bare minimum setup for quick prototyping',
|
|
704
|
-
tags: ['prototype', 'minimal', 'quick'],
|
|
705
|
-
extends: null,
|
|
706
|
-
config: {
|
|
707
|
-
stack: {
|
|
708
|
-
framework: 'nextjs',
|
|
709
|
-
language: 'typescript',
|
|
710
|
-
database: 'none',
|
|
711
|
-
hosting: 'vercel'
|
|
712
|
-
},
|
|
713
|
-
plugins: {
|
|
714
|
-
auth: { enabled: false },
|
|
715
|
-
payments: { enabled: false },
|
|
716
|
-
database: { enabled: false },
|
|
717
|
-
testing: { enabled: false },
|
|
718
|
-
security: { enabled: true },
|
|
719
|
-
ai: { enabled: false }
|
|
720
|
-
},
|
|
721
|
-
quality: {
|
|
722
|
-
preCommit: false,
|
|
723
|
-
prePush: false,
|
|
724
|
-
strictMode: false
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
},
|
|
728
|
-
|
|
729
|
-
'ai-app': {
|
|
730
|
-
name: 'AI Application',
|
|
731
|
-
description: 'AI-powered application with Claude integration',
|
|
732
|
-
tags: ['ai', 'llm', 'anthropic'],
|
|
733
|
-
extends: null,
|
|
734
|
-
config: {
|
|
735
|
-
stack: {
|
|
736
|
-
framework: 'nextjs',
|
|
737
|
-
language: 'typescript',
|
|
738
|
-
database: 'postgresql',
|
|
739
|
-
hosting: 'vercel'
|
|
740
|
-
},
|
|
741
|
-
plugins: {
|
|
742
|
-
auth: { enabled: true, provider: 'clerk', features: ['email_password'] },
|
|
743
|
-
payments: { enabled: false },
|
|
744
|
-
database: { enabled: true, provider: 'prisma', features: ['migrations'] },
|
|
745
|
-
testing: { enabled: true, provider: 'vitest', features: ['unit', 'mocking'] },
|
|
746
|
-
security: { enabled: true, features: ['input_validation', 'rate_limiting'] },
|
|
747
|
-
ai: { enabled: true, providers: ['anthropic'], features: ['streaming', 'tool_use'] }
|
|
748
|
-
},
|
|
749
|
-
quality: {
|
|
750
|
-
preCommit: true,
|
|
751
|
-
prePush: false,
|
|
752
|
-
strictMode: false
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
};
|
|
757
|
-
|
|
758
|
-
/**
|
|
759
|
-
* Get a preset configuration
|
|
760
|
-
* @param {string} presetName - Preset name
|
|
761
|
-
* @returns {object|null} Preset config or null if not found
|
|
762
|
-
*/
|
|
763
|
-
function getPreset(presetName) {
|
|
764
|
-
return CONFIG_PRESETS[presetName] || null;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
/**
|
|
768
|
-
* Resolve preset inheritance chain
|
|
769
|
-
* @param {string} presetName - Preset name to resolve
|
|
770
|
-
* @param {Set} seen - Set of already seen presets (for circular detection)
|
|
771
|
-
* @returns {object} Resolved configuration including all parent presets
|
|
772
|
-
*/
|
|
773
|
-
function resolvePresetChain(presetName, seen = new Set()) {
|
|
774
|
-
const preset = CONFIG_PRESETS[presetName];
|
|
775
|
-
if (!preset) {
|
|
776
|
-
throw new Error(`Unknown preset: ${presetName}. Available: ${Object.keys(CONFIG_PRESETS).join(', ')}`);
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
// Detect circular inheritance
|
|
780
|
-
if (seen.has(presetName)) {
|
|
781
|
-
throw new Error(`Circular preset inheritance detected: ${presetName}`);
|
|
782
|
-
}
|
|
783
|
-
seen.add(presetName);
|
|
784
|
-
|
|
785
|
-
// Start with defaults
|
|
786
|
-
let config = { ...DEFAULT_CONFIG };
|
|
787
|
-
|
|
788
|
-
// If this preset extends another, resolve the parent first
|
|
789
|
-
if (preset.extends) {
|
|
790
|
-
const parentConfig = resolvePresetChain(preset.extends, seen);
|
|
791
|
-
config = deepMerge(config, parentConfig);
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
// Apply this preset's config on top
|
|
795
|
-
config = deepMerge(config, preset.config);
|
|
796
|
-
|
|
797
|
-
return config;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
/**
|
|
801
|
-
* Apply a preset to the current configuration
|
|
802
|
-
* Supports single preset string or array of presets
|
|
803
|
-
* @param {string|string[]} presetNames - Preset name(s)
|
|
804
|
-
* @param {object} overrides - Additional overrides to apply
|
|
805
|
-
* @returns {object} Merged configuration
|
|
806
|
-
*/
|
|
807
|
-
function applyPreset(presetNames, overrides = {}) {
|
|
808
|
-
// Normalize to array
|
|
809
|
-
const presets = Array.isArray(presetNames) ? presetNames : [presetNames];
|
|
810
|
-
|
|
811
|
-
if (presets.length === 0) {
|
|
812
|
-
throw new Error('At least one preset name is required');
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
// Start with defaults
|
|
816
|
-
let config = { ...DEFAULT_CONFIG };
|
|
817
|
-
|
|
818
|
-
// Apply each preset in order (later presets override earlier ones)
|
|
819
|
-
for (const presetName of presets) {
|
|
820
|
-
const trimmedName = presetName.trim();
|
|
821
|
-
const presetConfig = resolvePresetChain(trimmedName);
|
|
822
|
-
config = deepMerge(config, presetConfig);
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
// Apply any overrides
|
|
826
|
-
config = deepMerge(config, overrides);
|
|
827
|
-
|
|
828
|
-
return config;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
/**
|
|
832
|
-
* Combine multiple presets into a single configuration
|
|
833
|
-
* This is an alias for applyPreset with array input
|
|
834
|
-
* @param {string[]} presetNames - Array of preset names to combine
|
|
835
|
-
* @param {object} overrides - Additional overrides to apply
|
|
836
|
-
* @returns {object} Combined configuration
|
|
837
|
-
*/
|
|
838
|
-
function combinePresets(presetNames, overrides = {}) {
|
|
839
|
-
if (!Array.isArray(presetNames) || presetNames.length === 0) {
|
|
840
|
-
throw new Error('combinePresets requires a non-empty array of preset names');
|
|
841
|
-
}
|
|
842
|
-
return applyPreset(presetNames, overrides);
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
/**
|
|
846
|
-
* Parse preset string (supports comma-separated presets)
|
|
847
|
-
* @param {string} presetString - Preset string (e.g., "saas-starter" or "saas-starter,ai-app")
|
|
848
|
-
* @returns {string[]} Array of preset names
|
|
849
|
-
*/
|
|
850
|
-
function parsePresetString(presetString) {
|
|
851
|
-
if (!presetString || typeof presetString !== 'string') {
|
|
852
|
-
return [];
|
|
853
|
-
}
|
|
854
|
-
return presetString.split(',').map(s => s.trim()).filter(Boolean);
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
/**
|
|
858
|
-
* Validate preset name(s)
|
|
859
|
-
* @param {string|string[]} presetNames - Preset name(s) to validate
|
|
860
|
-
* @returns {object} Validation result { valid, errors, validPresets }
|
|
861
|
-
*/
|
|
862
|
-
function validatePresets(presetNames) {
|
|
863
|
-
const presets = Array.isArray(presetNames) ? presetNames : [presetNames];
|
|
864
|
-
const errors = [];
|
|
865
|
-
const validPresets = [];
|
|
866
|
-
const availablePresets = Object.keys(CONFIG_PRESETS);
|
|
867
|
-
|
|
868
|
-
for (const preset of presets) {
|
|
869
|
-
if (CONFIG_PRESETS[preset]) {
|
|
870
|
-
validPresets.push(preset);
|
|
871
|
-
} else {
|
|
872
|
-
errors.push(`Unknown preset: "${preset}". Available: ${availablePresets.join(', ')}`);
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
return {
|
|
877
|
-
valid: errors.length === 0,
|
|
878
|
-
errors,
|
|
879
|
-
validPresets
|
|
880
|
-
};
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
/**
|
|
884
|
-
* Get preset by tag
|
|
885
|
-
* @param {string} tag - Tag to search for
|
|
886
|
-
* @returns {Array} Array of preset keys that have this tag
|
|
887
|
-
*/
|
|
888
|
-
function getPresetsByTag(tag) {
|
|
889
|
-
return Object.entries(CONFIG_PRESETS)
|
|
890
|
-
.filter(([_key, preset]) => preset.tags && preset.tags.includes(tag))
|
|
891
|
-
.map(([key]) => key);
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
/**
|
|
895
|
-
* Create a custom preset from an existing preset
|
|
896
|
-
* @param {string} baseName - Base preset name to extend
|
|
897
|
-
* @param {string} newName - Name for the new preset
|
|
898
|
-
* @param {object} customConfig - Custom configuration to apply
|
|
899
|
-
* @returns {object} The new preset definition
|
|
900
|
-
*/
|
|
901
|
-
function createCustomPreset(baseName, newName, customConfig = {}) {
|
|
902
|
-
const basePreset = CONFIG_PRESETS[baseName];
|
|
903
|
-
if (!basePreset) {
|
|
904
|
-
throw new Error(`Unknown base preset: ${baseName}`);
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
const customPreset = {
|
|
908
|
-
name: newName,
|
|
909
|
-
description: `Custom preset based on ${basePreset.name}`,
|
|
910
|
-
tags: [...(basePreset.tags || []), 'custom'],
|
|
911
|
-
extends: baseName,
|
|
912
|
-
config: customConfig
|
|
913
|
-
};
|
|
914
|
-
|
|
915
|
-
return customPreset;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
/**
|
|
919
|
-
* List available presets
|
|
920
|
-
* @param {object} options - List options
|
|
921
|
-
* @param {string} options.tag - Filter by tag
|
|
922
|
-
* @param {boolean} options.verbose - Include full descriptions and tags
|
|
923
|
-
* @returns {Array} Array of preset info objects
|
|
924
|
-
*/
|
|
925
|
-
function listPresets(options = {}) {
|
|
926
|
-
let entries = Object.entries(CONFIG_PRESETS);
|
|
927
|
-
|
|
928
|
-
// Filter by tag if specified
|
|
929
|
-
if (options.tag) {
|
|
930
|
-
entries = entries.filter(([_key, preset]) =>
|
|
931
|
-
preset.tags && preset.tags.includes(options.tag)
|
|
932
|
-
);
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
return entries.map(([key, preset]) => {
|
|
936
|
-
const info = {
|
|
937
|
-
key,
|
|
938
|
-
name: preset.name,
|
|
939
|
-
description: preset.description
|
|
940
|
-
};
|
|
941
|
-
|
|
942
|
-
if (options.verbose) {
|
|
943
|
-
info.tags = preset.tags || [];
|
|
944
|
-
info.extends = preset.extends || null;
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
return info;
|
|
948
|
-
});
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
/**
|
|
952
|
-
* Find the project root directory
|
|
953
|
-
* @returns {string|null} Project root path or null
|
|
954
|
-
*/
|
|
955
|
-
function findProjectRoot() {
|
|
956
|
-
let dir = process.cwd();
|
|
957
|
-
const root = path.parse(dir).root;
|
|
958
|
-
|
|
959
|
-
while (dir !== root) {
|
|
960
|
-
// Check for common project indicators
|
|
961
|
-
if (
|
|
962
|
-
fs.existsSync(path.join(dir, 'package.json')) ||
|
|
963
|
-
fs.existsSync(path.join(dir, 'bootspring.config.js')) ||
|
|
964
|
-
fs.existsSync(path.join(dir, '.git'))
|
|
965
|
-
) {
|
|
966
|
-
return dir;
|
|
967
|
-
}
|
|
968
|
-
dir = path.dirname(dir);
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
return process.cwd();
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
/**
|
|
975
|
-
* Find config file in project
|
|
976
|
-
* @param {string} projectRoot - Project root directory
|
|
977
|
-
* @returns {string|null} Config file path or null
|
|
978
|
-
*/
|
|
979
|
-
function findConfigFile(projectRoot) {
|
|
980
|
-
for (const filename of CONFIG_FILES) {
|
|
981
|
-
const filepath = path.join(projectRoot, filename);
|
|
982
|
-
if (fs.existsSync(filepath)) {
|
|
983
|
-
return filepath;
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
return null;
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
/**
|
|
990
|
-
* Deep merge two objects
|
|
991
|
-
* Arrays are replaced (not merged) to maintain preset integrity
|
|
992
|
-
* @param {object} target - Target object
|
|
993
|
-
* @param {object} source - Source object
|
|
994
|
-
* @returns {object} Merged object
|
|
995
|
-
*/
|
|
996
|
-
function deepMerge(target, source) {
|
|
997
|
-
const result = { ...target };
|
|
998
|
-
|
|
999
|
-
for (const key of Object.keys(source)) {
|
|
1000
|
-
const sourceValue = source[key];
|
|
1001
|
-
const targetValue = target[key];
|
|
1002
|
-
|
|
1003
|
-
// Arrays are replaced entirely (not merged element by element)
|
|
1004
|
-
if (Array.isArray(sourceValue)) {
|
|
1005
|
-
result[key] = [...sourceValue];
|
|
1006
|
-
}
|
|
1007
|
-
// Objects are deep merged (but not arrays, null, or other non-plain objects)
|
|
1008
|
-
else if (
|
|
1009
|
-
sourceValue !== null &&
|
|
1010
|
-
typeof sourceValue === 'object' &&
|
|
1011
|
-
!Array.isArray(sourceValue) &&
|
|
1012
|
-
targetValue !== null &&
|
|
1013
|
-
typeof targetValue === 'object' &&
|
|
1014
|
-
!Array.isArray(targetValue)
|
|
1015
|
-
) {
|
|
1016
|
-
result[key] = deepMerge(targetValue, sourceValue);
|
|
1017
|
-
}
|
|
1018
|
-
// Primitives and other values are replaced
|
|
1019
|
-
else {
|
|
1020
|
-
result[key] = sourceValue;
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
return result;
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
/**
|
|
1028
|
-
* Load configuration from file
|
|
1029
|
-
* @param {string} [projectRoot] - Optional project root path
|
|
1030
|
-
* @returns {object} Configuration object
|
|
1031
|
-
*/
|
|
1032
|
-
function load(projectRoot = null) {
|
|
1033
|
-
const root = projectRoot || findProjectRoot();
|
|
1034
|
-
const configPath = findConfigFile(root);
|
|
1035
|
-
|
|
1036
|
-
let userConfig = {};
|
|
1037
|
-
|
|
1038
|
-
if (configPath) {
|
|
1039
|
-
try {
|
|
1040
|
-
if (configPath.endsWith('.json') || configPath.endsWith('.bootspringrc')) {
|
|
1041
|
-
const content = fs.readFileSync(configPath, 'utf-8');
|
|
1042
|
-
userConfig = JSON.parse(content);
|
|
1043
|
-
} else {
|
|
1044
|
-
// Clear require cache to get fresh config
|
|
1045
|
-
delete require.cache[require.resolve(configPath)];
|
|
1046
|
-
userConfig = require(configPath);
|
|
1047
|
-
}
|
|
1048
|
-
} catch (error) {
|
|
1049
|
-
console.warn(`Warning: Could not load config from ${configPath}: ${error.message}`);
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
// Merge with defaults
|
|
1054
|
-
const config = deepMerge(DEFAULT_CONFIG, userConfig);
|
|
1055
|
-
|
|
1056
|
-
// Add computed paths
|
|
1057
|
-
config._projectRoot = root;
|
|
1058
|
-
config._configPath = configPath;
|
|
1059
|
-
config._bootspringDir = path.join(root, '.bootspring');
|
|
1060
|
-
|
|
1061
|
-
return config;
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
/**
|
|
1065
|
-
* Save configuration to file
|
|
1066
|
-
* @param {object} config - Configuration object
|
|
1067
|
-
* @param {string} [filepath] - Optional file path
|
|
1068
|
-
* @returns {boolean} Success status
|
|
1069
|
-
*/
|
|
1070
|
-
function save(config, filepath = null) {
|
|
1071
|
-
const root = config._projectRoot || findProjectRoot();
|
|
1072
|
-
const targetPath = filepath || path.join(root, 'bootspring.config.js');
|
|
1073
|
-
|
|
1074
|
-
// Remove internal properties
|
|
1075
|
-
const cleanConfig = { ...config };
|
|
1076
|
-
delete cleanConfig._projectRoot;
|
|
1077
|
-
delete cleanConfig._configPath;
|
|
1078
|
-
delete cleanConfig._bootspringDir;
|
|
1079
|
-
|
|
1080
|
-
const content = `/**
|
|
1081
|
-
* Bootspring Configuration
|
|
1082
|
-
* https://bootspring.com/docs/configuration
|
|
1083
|
-
*/
|
|
1084
|
-
|
|
1085
|
-
module.exports = ${JSON.stringify(cleanConfig, null, 2)};
|
|
1086
|
-
`;
|
|
1087
|
-
|
|
1088
|
-
try {
|
|
1089
|
-
fs.writeFileSync(targetPath, content, 'utf-8');
|
|
1090
|
-
return true;
|
|
1091
|
-
} catch (error) {
|
|
1092
|
-
console.error(`Error saving config: ${error.message}`);
|
|
1093
|
-
return false;
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
/**
|
|
1098
|
-
* Validate configuration using Zod schema
|
|
1099
|
-
* @param {object} config - Configuration object
|
|
1100
|
-
* @param {object} options - Validation options
|
|
1101
|
-
* @param {boolean} options.strict - If true, fail on unknown fields
|
|
1102
|
-
* @param {string[]} options.sections - Only validate specific sections
|
|
1103
|
-
* @returns {object} Validation result { valid, errors, warnings, data }
|
|
1104
|
-
*/
|
|
1105
|
-
function validate(config, options = {}) {
|
|
1106
|
-
// Remove internal properties before validation
|
|
1107
|
-
const configToValidate = { ...config };
|
|
1108
|
-
delete configToValidate._projectRoot;
|
|
1109
|
-
delete configToValidate._configPath;
|
|
1110
|
-
delete configToValidate._bootspringDir;
|
|
1111
|
-
|
|
1112
|
-
const errors = [];
|
|
1113
|
-
const warnings = [];
|
|
1114
|
-
|
|
1115
|
-
// If specific sections requested, validate only those
|
|
1116
|
-
if (options.sections && options.sections.length > 0) {
|
|
1117
|
-
let allValid = true;
|
|
1118
|
-
const validatedData = {};
|
|
1119
|
-
|
|
1120
|
-
for (const section of options.sections) {
|
|
1121
|
-
if (configToValidate[section] !== undefined) {
|
|
1122
|
-
const result = validateSection(section, configToValidate[section]);
|
|
1123
|
-
if (!result.valid) {
|
|
1124
|
-
allValid = false;
|
|
1125
|
-
errors.push(...result.errors);
|
|
1126
|
-
} else {
|
|
1127
|
-
validatedData[section] = result.data;
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
return {
|
|
1133
|
-
valid: allValid,
|
|
1134
|
-
errors,
|
|
1135
|
-
warnings,
|
|
1136
|
-
data: allValid ? validatedData : null
|
|
1137
|
-
};
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
// Full config validation
|
|
1141
|
-
const result = ConfigSchema.safeParse(configToValidate);
|
|
1142
|
-
|
|
1143
|
-
if (result.success) {
|
|
1144
|
-
return {
|
|
1145
|
-
valid: true,
|
|
1146
|
-
errors: [],
|
|
1147
|
-
warnings,
|
|
1148
|
-
data: result.data
|
|
1149
|
-
};
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
return {
|
|
1153
|
-
valid: false,
|
|
1154
|
-
errors: formatValidationErrors(result.error),
|
|
1155
|
-
warnings,
|
|
1156
|
-
data: null
|
|
1157
|
-
};
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
/**
|
|
1161
|
-
* Load and optionally validate configuration
|
|
1162
|
-
* Validation is graceful by default - invalid configs still load with warnings
|
|
1163
|
-
* @param {string} [projectRoot] - Optional project root path
|
|
1164
|
-
* @param {object} [options] - Load options
|
|
1165
|
-
* @param {boolean} [options.validate] - Whether to validate (default: true)
|
|
1166
|
-
* @param {boolean} [options.strict] - If true, throw on validation errors (default: false)
|
|
1167
|
-
* @param {boolean} [options.silent] - If true, suppress validation warnings (default: false)
|
|
1168
|
-
* @returns {object} Configuration object with _validation property
|
|
1169
|
-
*/
|
|
1170
|
-
function loadWithValidation(projectRoot = null, options = {}) {
|
|
1171
|
-
const { validate: shouldValidate = true, strict = false, silent = false } = options;
|
|
1172
|
-
|
|
1173
|
-
// Load the config normally
|
|
1174
|
-
const config = load(projectRoot);
|
|
1175
|
-
|
|
1176
|
-
// Skip validation if not requested
|
|
1177
|
-
if (!shouldValidate) {
|
|
1178
|
-
config._validation = { skipped: true };
|
|
1179
|
-
return config;
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
// Validate the loaded config
|
|
1183
|
-
const validation = validate(config);
|
|
1184
|
-
|
|
1185
|
-
// Attach validation results
|
|
1186
|
-
config._validation = {
|
|
1187
|
-
valid: validation.valid,
|
|
1188
|
-
errors: validation.errors,
|
|
1189
|
-
warnings: validation.warnings || [],
|
|
1190
|
-
validatedAt: new Date().toISOString()
|
|
1191
|
-
};
|
|
1192
|
-
|
|
1193
|
-
// Handle validation results
|
|
1194
|
-
if (!validation.valid) {
|
|
1195
|
-
if (strict) {
|
|
1196
|
-
const errorMessage = `Configuration validation failed:\n${validation.errors.map(e => ` - ${e}`).join('\n')}`;
|
|
1197
|
-
throw new Error(errorMessage);
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
if (!silent) {
|
|
1201
|
-
console.warn('\x1b[33mWarning: Configuration has validation issues:\x1b[0m');
|
|
1202
|
-
validation.errors.forEach(error => {
|
|
1203
|
-
console.warn(` \x1b[33m- ${error}\x1b[0m`);
|
|
1204
|
-
});
|
|
1205
|
-
console.warn('\x1b[33mThe configuration will still be used, but some features may not work correctly.\x1b[0m');
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
return config;
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
/**
|
|
1213
|
-
* Get a helpful error message for a validation error
|
|
1214
|
-
* @param {string} errorPath - Dot-notation path to the error
|
|
1215
|
-
* @param {string} errorMessage - The error message
|
|
1216
|
-
* @returns {string} A helpful message with suggestions
|
|
1217
|
-
*/
|
|
1218
|
-
function getValidationHint(errorPath, errorMessage) {
|
|
1219
|
-
const hints = {
|
|
1220
|
-
'stack.framework': 'Valid frameworks: nextjs, remix, nuxt, sveltekit, astro, express, fastify, hono, custom',
|
|
1221
|
-
'stack.language': 'Valid languages: typescript, javascript',
|
|
1222
|
-
'stack.database': 'Valid databases: postgresql, mysql, mongodb, sqlite, supabase, planetscale, none',
|
|
1223
|
-
'stack.hosting': 'Valid hosting options: vercel, railway, render, fly, aws, gcp, azure, cloudflare, self-hosted, custom',
|
|
1224
|
-
'plugins.auth.provider': 'Valid auth providers: clerk, nextauth, auth0, supabase, jwt, custom',
|
|
1225
|
-
'plugins.payments.provider': 'Valid payment providers: stripe, paddle, lemonsqueezy, paypal, custom',
|
|
1226
|
-
'plugins.database.provider': 'Valid database ORMs: prisma, drizzle, typeorm, kysely, custom',
|
|
1227
|
-
'plugins.testing.provider': 'Valid test frameworks: vitest, jest, playwright, cypress, custom',
|
|
1228
|
-
'plugins.ai.providers': 'Valid AI providers: anthropic, openai, google, cohere, huggingface, custom',
|
|
1229
|
-
'dashboard.port': 'Port must be between 1024 and 65535',
|
|
1230
|
-
'project.version': 'Version must follow semver format (e.g., 1.0.0)'
|
|
1231
|
-
};
|
|
1232
|
-
|
|
1233
|
-
return hints[errorPath] || null;
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
/**
|
|
1237
|
-
* Parse and validate configuration with defaults
|
|
1238
|
-
* @param {object} config - Raw configuration object
|
|
1239
|
-
* @returns {object} Parsed configuration with defaults applied
|
|
1240
|
-
*/
|
|
1241
|
-
function parseConfig(config) {
|
|
1242
|
-
// Remove internal properties before parsing
|
|
1243
|
-
const configToParse = { ...config };
|
|
1244
|
-
delete configToParse._projectRoot;
|
|
1245
|
-
delete configToParse._configPath;
|
|
1246
|
-
delete configToParse._bootspringDir;
|
|
1247
|
-
|
|
1248
|
-
const result = ConfigSchema.safeParse(configToParse);
|
|
1249
|
-
if (result.success) {
|
|
1250
|
-
return result.data;
|
|
1251
|
-
}
|
|
1252
|
-
// Return original config if parsing fails (for backwards compatibility)
|
|
1253
|
-
return configToParse;
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
/**
|
|
1257
|
-
* Get default configuration
|
|
1258
|
-
* @returns {object} Default configuration
|
|
1259
|
-
*/
|
|
1260
|
-
function getDefaults() {
|
|
1261
|
-
return { ...DEFAULT_CONFIG };
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
module.exports = {
|
|
1265
|
-
// Core functions
|
|
1266
|
-
load,
|
|
1267
|
-
loadWithValidation,
|
|
1268
|
-
save,
|
|
1269
|
-
validate,
|
|
1270
|
-
validateSection,
|
|
1271
|
-
parseConfig,
|
|
1272
|
-
getDefaults,
|
|
1273
|
-
findProjectRoot,
|
|
1274
|
-
findConfigFile,
|
|
1275
|
-
|
|
1276
|
-
// Preset functions
|
|
1277
|
-
getPreset,
|
|
1278
|
-
applyPreset,
|
|
1279
|
-
combinePresets,
|
|
1280
|
-
listPresets,
|
|
1281
|
-
parsePresetString,
|
|
1282
|
-
validatePresets,
|
|
1283
|
-
getPresetsByTag,
|
|
1284
|
-
createCustomPreset,
|
|
1285
|
-
resolvePresetChain,
|
|
1286
|
-
|
|
1287
|
-
// Validation helpers
|
|
1288
|
-
formatValidationErrors,
|
|
1289
|
-
getValidationHint,
|
|
1290
|
-
|
|
1291
|
-
// Constants
|
|
1292
|
-
DEFAULT_CONFIG,
|
|
1293
|
-
CONFIG_FILES,
|
|
1294
|
-
CONFIG_PRESETS,
|
|
1295
|
-
|
|
1296
|
-
// Schemas - Main
|
|
1297
|
-
ConfigSchema,
|
|
1298
|
-
|
|
1299
|
-
// Schemas - Plugins
|
|
1300
|
-
PluginSchema,
|
|
1301
|
-
PluginsSchema,
|
|
1302
|
-
AuthPluginSchema,
|
|
1303
|
-
PaymentsPluginSchema,
|
|
1304
|
-
DatabasePluginSchema,
|
|
1305
|
-
TestingPluginSchema,
|
|
1306
|
-
SecurityPluginSchema,
|
|
1307
|
-
AIPluginSchema,
|
|
1308
|
-
EmailPluginSchema,
|
|
1309
|
-
AnalyticsPluginSchema,
|
|
1310
|
-
MonitoringPluginSchema,
|
|
1311
|
-
|
|
1312
|
-
// Schemas - Features
|
|
1313
|
-
AgentsConfigSchema,
|
|
1314
|
-
AgentConfigSchema,
|
|
1315
|
-
SkillsConfigSchema,
|
|
1316
|
-
SkillConfigSchema,
|
|
1317
|
-
WorkflowsConfigSchema,
|
|
1318
|
-
WorkflowConfigSchema,
|
|
1319
|
-
WorkflowPhaseSchema,
|
|
1320
|
-
|
|
1321
|
-
// Schemas - Settings
|
|
1322
|
-
ProjectConfigSchema,
|
|
1323
|
-
StackConfigSchema,
|
|
1324
|
-
QualityConfigSchema,
|
|
1325
|
-
DashboardConfigSchema,
|
|
1326
|
-
ContextConfigSchema,
|
|
1327
|
-
PathsConfigSchema
|
|
1328
|
-
};
|