@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.
Files changed (45) hide show
  1. package/bin/bootspring.js +127 -73
  2. package/claude-commands/agent.md +34 -0
  3. package/claude-commands/bs.md +31 -0
  4. package/claude-commands/build.md +25 -0
  5. package/claude-commands/skill.md +31 -0
  6. package/claude-commands/todo.md +25 -0
  7. package/dist/core/index.d.ts +5814 -0
  8. package/dist/core.js +5779 -0
  9. package/dist/index.js +93883 -0
  10. package/dist/mcp/index.d.ts +1 -0
  11. package/dist/mcp-server.js +2298 -0
  12. package/package.json +22 -55
  13. package/core/api-client.d.ts +0 -69
  14. package/core/api-client.js +0 -1482
  15. package/core/auth.d.ts +0 -98
  16. package/core/auth.js +0 -737
  17. package/core/build-orchestrator.js +0 -508
  18. package/core/build-state.js +0 -612
  19. package/core/config.d.ts +0 -106
  20. package/core/config.js +0 -1328
  21. package/core/context-loader.js +0 -580
  22. package/core/context.d.ts +0 -61
  23. package/core/context.js +0 -327
  24. package/core/entitlements.d.ts +0 -70
  25. package/core/entitlements.js +0 -322
  26. package/core/index.d.ts +0 -53
  27. package/core/index.js +0 -62
  28. package/core/mcp-config.js +0 -115
  29. package/core/policies.d.ts +0 -43
  30. package/core/policies.js +0 -113
  31. package/core/policy-matrix.js +0 -303
  32. package/core/project-activity.js +0 -175
  33. package/core/redaction.d.ts +0 -5
  34. package/core/redaction.js +0 -63
  35. package/core/self-update.js +0 -259
  36. package/core/session.js +0 -353
  37. package/core/task-extractor.js +0 -1098
  38. package/core/telemetry.d.ts +0 -55
  39. package/core/telemetry.js +0 -617
  40. package/core/tier-enforcement.js +0 -928
  41. package/core/utils.d.ts +0 -90
  42. package/core/utils.js +0 -455
  43. package/core/validation.js +0 -572
  44. package/mcp/server.d.ts +0 -57
  45. 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
- };