@girardmedia/bootspring 1.2.0 → 2.0.3

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 (253) hide show
  1. package/README.md +107 -14
  2. package/bin/bootspring.js +166 -27
  3. package/cli/agent.js +189 -17
  4. package/cli/analyze.js +499 -0
  5. package/cli/audit.js +557 -0
  6. package/cli/auth.js +495 -38
  7. package/cli/billing.js +302 -0
  8. package/cli/build.js +695 -0
  9. package/cli/business.js +109 -26
  10. package/cli/checkpoint-utils.js +168 -0
  11. package/cli/checkpoint.js +639 -0
  12. package/cli/cloud-sync.js +447 -0
  13. package/cli/content.js +198 -0
  14. package/cli/context.js +1 -1
  15. package/cli/deploy.js +543 -0
  16. package/cli/fundraise.js +112 -50
  17. package/cli/github-cmd.js +435 -0
  18. package/cli/health.js +477 -0
  19. package/cli/init.js +84 -13
  20. package/cli/legal.js +107 -95
  21. package/cli/log.js +2 -2
  22. package/cli/loop.js +976 -73
  23. package/cli/manager.js +711 -0
  24. package/cli/metrics.js +480 -0
  25. package/cli/monitor.js +812 -0
  26. package/cli/onboard.js +521 -0
  27. package/cli/orchestrator.js +12 -24
  28. package/cli/prd.js +594 -0
  29. package/cli/preseed-start.js +1483 -0
  30. package/cli/preseed.js +2302 -0
  31. package/cli/project.js +436 -0
  32. package/cli/quality.js +233 -0
  33. package/cli/security.js +913 -0
  34. package/cli/seed.js +1441 -5
  35. package/cli/skill.js +273 -211
  36. package/cli/suggest.js +989 -0
  37. package/cli/switch.js +453 -0
  38. package/cli/visualize.js +527 -0
  39. package/cli/watch.js +769 -0
  40. package/cli/workspace.js +607 -0
  41. package/core/analyze-workflow.js +1134 -0
  42. package/core/api-client.js +535 -22
  43. package/core/audit-workflow.js +1350 -0
  44. package/core/build-orchestrator.js +480 -0
  45. package/core/build-state.js +577 -0
  46. package/core/checkpoint-engine.js +408 -0
  47. package/core/config.js +1109 -26
  48. package/core/context-loader.js +21 -1
  49. package/core/deploy-workflow.js +836 -0
  50. package/core/entitlements.js +93 -22
  51. package/core/github-sync.js +610 -0
  52. package/core/index.js +8 -1
  53. package/core/ingest.js +1111 -0
  54. package/core/metrics-engine.js +768 -0
  55. package/core/onboard-workflow.js +1007 -0
  56. package/core/preseed-workflow.js +934 -0
  57. package/core/preseed.js +1617 -0
  58. package/core/project-context.js +325 -0
  59. package/core/project-state.js +694 -0
  60. package/core/r2-sync.js +583 -0
  61. package/core/scaffold.js +525 -7
  62. package/core/session.js +258 -0
  63. package/core/task-extractor.js +758 -0
  64. package/core/telemetry.js +28 -6
  65. package/core/tier-enforcement.js +737 -0
  66. package/core/utils.js +38 -14
  67. package/generators/questionnaire.js +15 -12
  68. package/generators/sections/ai.js +7 -7
  69. package/generators/sections/content.js +300 -0
  70. package/generators/sections/index.js +3 -0
  71. package/generators/sections/plugins.js +7 -6
  72. package/generators/templates/build-planning.template.js +596 -0
  73. package/generators/templates/content.template.js +819 -0
  74. package/generators/templates/index.js +2 -1
  75. package/hooks/git-autopilot.js +1250 -0
  76. package/hooks/index.js +9 -0
  77. package/intelligence/agent-collab.js +2057 -0
  78. package/intelligence/auto-suggest.js +634 -0
  79. package/intelligence/content-gen.js +1589 -0
  80. package/intelligence/cross-project.js +1647 -0
  81. package/intelligence/index.js +184 -0
  82. package/intelligence/learning/insights.json +517 -7
  83. package/intelligence/learning/pattern-learner.js +1008 -14
  84. package/intelligence/memory/decision-tracker.js +1431 -31
  85. package/intelligence/memory/decisions.jsonl +0 -0
  86. package/intelligence/orchestrator.js +2896 -1
  87. package/intelligence/prd.js +92 -1
  88. package/intelligence/recommendation-weights.json +14 -2
  89. package/intelligence/recommendations.js +463 -9
  90. package/intelligence/workflow-composer.js +1451 -0
  91. package/marketplace/index.d.ts +324 -0
  92. package/marketplace/index.js +1921 -0
  93. package/mcp/contracts/mcp-contract.v1.json +342 -4
  94. package/mcp/registry.js +680 -3
  95. package/mcp/response-formatter.js +23 -0
  96. package/mcp/tools/assist-tool.js +78 -4
  97. package/mcp/tools/autopilot-tool.js +408 -0
  98. package/mcp/tools/content-tool.js +571 -0
  99. package/mcp/tools/dashboard-tool.js +251 -5
  100. package/mcp/tools/mvp-tool.js +344 -0
  101. package/mcp/tools/plugin-tool.js +23 -1
  102. package/mcp/tools/prd-tool.js +579 -0
  103. package/mcp/tools/seed-tool.js +447 -0
  104. package/mcp/tools/skill-tool.js +43 -14
  105. package/mcp/tools/suggest-tool.js +147 -0
  106. package/package.json +15 -6
  107. package/agents/README.md +0 -93
  108. package/agents/ai-integration-expert/context.md +0 -386
  109. package/agents/api-expert/context.md +0 -416
  110. package/agents/architecture-expert/context.md +0 -454
  111. package/agents/auth-expert/context.md +0 -399
  112. package/agents/backend-expert/context.md +0 -483
  113. package/agents/business-strategy-expert/context.md +0 -180
  114. package/agents/code-review-expert/context.md +0 -365
  115. package/agents/competitive-analysis-expert/context.md +0 -239
  116. package/agents/data-modeling-expert/context.md +0 -352
  117. package/agents/database-expert/context.md +0 -250
  118. package/agents/devops-expert/context.md +0 -446
  119. package/agents/email-expert/context.md +0 -379
  120. package/agents/financial-expert/context.md +0 -213
  121. package/agents/frontend-expert/context.md +0 -364
  122. package/agents/fundraising-expert/context.md +0 -257
  123. package/agents/growth-expert/context.md +0 -249
  124. package/agents/index.js +0 -140
  125. package/agents/investor-relations-expert/context.md +0 -266
  126. package/agents/legal-expert/context.md +0 -284
  127. package/agents/marketing-expert/context.md +0 -236
  128. package/agents/monitoring-expert/context.md +0 -362
  129. package/agents/operations-expert/context.md +0 -279
  130. package/agents/partnerships-expert/context.md +0 -286
  131. package/agents/payment-expert/context.md +0 -340
  132. package/agents/performance-expert/context.md +0 -377
  133. package/agents/private-equity-expert/context.md +0 -246
  134. package/agents/railway-expert/context.md +0 -284
  135. package/agents/research-expert/context.md +0 -245
  136. package/agents/sales-expert/context.md +0 -241
  137. package/agents/security-expert/context.md +0 -343
  138. package/agents/testing-expert/context.md +0 -414
  139. package/agents/ui-ux-expert/context.md +0 -448
  140. package/agents/vercel-expert/context.md +0 -426
  141. package/skills/index.js +0 -787
  142. package/skills/patterns/README.md +0 -163
  143. package/skills/patterns/ai/agents.md +0 -281
  144. package/skills/patterns/ai/claude.md +0 -138
  145. package/skills/patterns/ai/embeddings.md +0 -150
  146. package/skills/patterns/ai/rag.md +0 -266
  147. package/skills/patterns/ai/streaming.md +0 -170
  148. package/skills/patterns/ai/structured-output.md +0 -162
  149. package/skills/patterns/ai/tools.md +0 -154
  150. package/skills/patterns/analytics/tracking.md +0 -220
  151. package/skills/patterns/api/errors.md +0 -296
  152. package/skills/patterns/api/graphql.md +0 -440
  153. package/skills/patterns/api/middleware.md +0 -279
  154. package/skills/patterns/api/openapi.md +0 -285
  155. package/skills/patterns/api/rate-limiting.md +0 -231
  156. package/skills/patterns/api/route-handler.md +0 -217
  157. package/skills/patterns/api/server-action.md +0 -249
  158. package/skills/patterns/api/versioning.md +0 -443
  159. package/skills/patterns/api/webhooks.md +0 -247
  160. package/skills/patterns/auth/clerk.md +0 -132
  161. package/skills/patterns/auth/mfa.md +0 -313
  162. package/skills/patterns/auth/nextauth.md +0 -140
  163. package/skills/patterns/auth/oauth.md +0 -237
  164. package/skills/patterns/auth/rbac.md +0 -152
  165. package/skills/patterns/auth/session-management.md +0 -367
  166. package/skills/patterns/auth/session.md +0 -120
  167. package/skills/patterns/database/audit.md +0 -177
  168. package/skills/patterns/database/migrations.md +0 -177
  169. package/skills/patterns/database/pagination.md +0 -230
  170. package/skills/patterns/database/pooling.md +0 -357
  171. package/skills/patterns/database/prisma.md +0 -180
  172. package/skills/patterns/database/relations.md +0 -187
  173. package/skills/patterns/database/seeding.md +0 -246
  174. package/skills/patterns/database/soft-delete.md +0 -153
  175. package/skills/patterns/database/transactions.md +0 -162
  176. package/skills/patterns/deployment/ci-cd.md +0 -231
  177. package/skills/patterns/deployment/docker.md +0 -188
  178. package/skills/patterns/deployment/monitoring.md +0 -387
  179. package/skills/patterns/deployment/vercel.md +0 -160
  180. package/skills/patterns/email/resend.md +0 -143
  181. package/skills/patterns/email/templates.md +0 -245
  182. package/skills/patterns/email/transactional.md +0 -503
  183. package/skills/patterns/email/verification.md +0 -176
  184. package/skills/patterns/files/download.md +0 -243
  185. package/skills/patterns/files/upload.md +0 -239
  186. package/skills/patterns/i18n/nextintl.md +0 -188
  187. package/skills/patterns/logging/structured.md +0 -292
  188. package/skills/patterns/notifications/email-queue.md +0 -248
  189. package/skills/patterns/notifications/push.md +0 -279
  190. package/skills/patterns/payments/checkout.md +0 -303
  191. package/skills/patterns/payments/invoices.md +0 -287
  192. package/skills/patterns/payments/portal.md +0 -245
  193. package/skills/patterns/payments/stripe.md +0 -272
  194. package/skills/patterns/payments/subscriptions.md +0 -300
  195. package/skills/patterns/payments/usage.md +0 -279
  196. package/skills/patterns/performance/caching.md +0 -276
  197. package/skills/patterns/performance/code-splitting.md +0 -233
  198. package/skills/patterns/performance/edge.md +0 -254
  199. package/skills/patterns/performance/isr.md +0 -266
  200. package/skills/patterns/performance/lazy-loading.md +0 -281
  201. package/skills/patterns/realtime/sse.md +0 -327
  202. package/skills/patterns/realtime/websockets.md +0 -336
  203. package/skills/patterns/search/filtering.md +0 -329
  204. package/skills/patterns/search/fulltext.md +0 -260
  205. package/skills/patterns/security/audit-logging.md +0 -444
  206. package/skills/patterns/security/csrf.md +0 -234
  207. package/skills/patterns/security/headers.md +0 -252
  208. package/skills/patterns/security/sanitization.md +0 -258
  209. package/skills/patterns/security/secrets.md +0 -261
  210. package/skills/patterns/security/validation.md +0 -268
  211. package/skills/patterns/security/xss.md +0 -229
  212. package/skills/patterns/seo/metadata.md +0 -252
  213. package/skills/patterns/state/context.md +0 -349
  214. package/skills/patterns/state/react-query.md +0 -313
  215. package/skills/patterns/state/url-state.md +0 -482
  216. package/skills/patterns/state/zustand.md +0 -262
  217. package/skills/patterns/testing/api.md +0 -259
  218. package/skills/patterns/testing/component.md +0 -233
  219. package/skills/patterns/testing/coverage.md +0 -207
  220. package/skills/patterns/testing/fixtures.md +0 -225
  221. package/skills/patterns/testing/integration.md +0 -436
  222. package/skills/patterns/testing/mocking.md +0 -177
  223. package/skills/patterns/testing/playwright.md +0 -162
  224. package/skills/patterns/testing/snapshot.md +0 -175
  225. package/skills/patterns/testing/vitest.md +0 -307
  226. package/skills/patterns/ui/accordions.md +0 -395
  227. package/skills/patterns/ui/cards.md +0 -299
  228. package/skills/patterns/ui/dropdowns.md +0 -476
  229. package/skills/patterns/ui/empty-states.md +0 -320
  230. package/skills/patterns/ui/forms.md +0 -405
  231. package/skills/patterns/ui/inputs.md +0 -319
  232. package/skills/patterns/ui/layouts.md +0 -282
  233. package/skills/patterns/ui/loading.md +0 -291
  234. package/skills/patterns/ui/modals.md +0 -338
  235. package/skills/patterns/ui/navigation.md +0 -374
  236. package/skills/patterns/ui/tables.md +0 -407
  237. package/skills/patterns/ui/toasts.md +0 -300
  238. package/skills/patterns/ui/tooltips.md +0 -396
  239. package/skills/patterns/utils/dates.md +0 -435
  240. package/skills/patterns/utils/errors.md +0 -451
  241. package/skills/patterns/utils/formatting.md +0 -345
  242. package/skills/patterns/utils/validation.md +0 -434
  243. package/templates/bootspring.config.js +0 -83
  244. package/templates/business/business-model-canvas.md +0 -246
  245. package/templates/business/business-plan.md +0 -266
  246. package/templates/business/competitive-analysis.md +0 -312
  247. package/templates/fundraising/data-room-checklist.md +0 -300
  248. package/templates/fundraising/investor-research.md +0 -243
  249. package/templates/fundraising/pitch-deck-outline.md +0 -253
  250. package/templates/legal/gdpr-checklist.md +0 -339
  251. package/templates/legal/privacy-policy.md +0 -285
  252. package/templates/legal/terms-of-service.md +0 -222
  253. package/templates/mcp.json +0 -9
@@ -0,0 +1,1921 @@
1
+ /**
2
+ * Bootspring Template Marketplace
3
+ *
4
+ * Community template discovery, installation, and management.
5
+ * Enables sharing and reusing project templates across the community.
6
+ *
7
+ * Features:
8
+ * - Template discovery and search
9
+ * - Versioned template installation with dependency resolution
10
+ * - Community template publishing
11
+ * - Template updates and version management
12
+ *
13
+ * @package bootspring
14
+ * @module marketplace
15
+ */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const https = require('https');
20
+ const http = require('http');
21
+ const crypto = require('crypto');
22
+
23
+ /**
24
+ * Marketplace configuration
25
+ */
26
+ const MARKETPLACE_CONFIG = {
27
+ registryUrl: process.env.BOOTSPRING_REGISTRY_URL || 'https://registry.bootspring.com',
28
+ cacheDir: '.bootspring/marketplace-cache',
29
+ templatesDir: '.bootspring/templates',
30
+ cacheTTL: 3600000, // 1 hour in ms
31
+ maxRetries: 3,
32
+ retryDelay: 1000
33
+ };
34
+
35
+ /**
36
+ * Built-in template categories
37
+ */
38
+ const TEMPLATE_CATEGORIES = {
39
+ 'starter': {
40
+ name: 'Starters',
41
+ description: 'Complete project starter templates',
42
+ icon: 'rocket'
43
+ },
44
+ 'saas': {
45
+ name: 'SaaS',
46
+ description: 'Software-as-a-Service templates',
47
+ icon: 'cloud'
48
+ },
49
+ 'component': {
50
+ name: 'Components',
51
+ description: 'Reusable UI components',
52
+ icon: 'puzzle'
53
+ },
54
+ 'feature': {
55
+ name: 'Features',
56
+ description: 'Feature modules (auth, payments, etc.)',
57
+ icon: 'box'
58
+ },
59
+ 'workflow': {
60
+ name: 'Workflows',
61
+ description: 'Custom workflow definitions',
62
+ icon: 'workflow'
63
+ },
64
+ 'agent': {
65
+ name: 'Agents',
66
+ description: 'Custom agent profiles',
67
+ icon: 'bot'
68
+ },
69
+ 'skill': {
70
+ name: 'Skills',
71
+ description: 'Code generation skills',
72
+ icon: 'code'
73
+ },
74
+ 'integration': {
75
+ name: 'Integrations',
76
+ description: 'Third-party service integrations',
77
+ icon: 'plug'
78
+ },
79
+ 'auth': {
80
+ name: 'Authentication',
81
+ description: 'Authentication and authorization templates',
82
+ icon: 'lock'
83
+ },
84
+ 'payments': {
85
+ name: 'Payments',
86
+ description: 'Payment processing integrations',
87
+ icon: 'credit-card'
88
+ },
89
+ 'ai': {
90
+ name: 'AI',
91
+ description: 'AI and machine learning integrations',
92
+ icon: 'brain'
93
+ }
94
+ };
95
+
96
+ /**
97
+ * Official/featured templates (bundled)
98
+ */
99
+ const OFFICIAL_TEMPLATES = {
100
+ 'saas-starter': {
101
+ id: 'saas-starter',
102
+ name: 'SaaS Starter',
103
+ description: 'Complete SaaS application with auth, payments, and dashboard. Includes user management, subscription billing, team features, and admin panel.',
104
+ category: 'saas',
105
+ version: '1.2.0',
106
+ author: {
107
+ name: 'bootspring',
108
+ email: 'templates@bootspring.com',
109
+ verified: true
110
+ },
111
+ official: true,
112
+ featured: true,
113
+ tags: ['saas', 'auth', 'payments', 'dashboard', 'teams', 'stripe', 'clerk'],
114
+ stack: ['nextjs', 'prisma', 'stripe', 'clerk', 'tailwindcss'],
115
+ license: 'MIT',
116
+ repository: 'https://github.com/bootspring/saas-starter',
117
+ documentation: 'https://bootspring.com/templates/saas-starter',
118
+ files: [
119
+ 'src/app/layout.tsx',
120
+ 'src/app/dashboard/**/*',
121
+ 'src/app/api/stripe/**/*',
122
+ 'src/app/api/webhooks/**/*',
123
+ 'src/components/billing/**/*',
124
+ 'src/components/dashboard/**/*',
125
+ 'src/lib/stripe.ts',
126
+ 'src/lib/auth.ts',
127
+ 'prisma/schema.prisma',
128
+ 'prisma/seed.ts'
129
+ ],
130
+ dependencies: {
131
+ '@clerk/nextjs': '^5.0.0',
132
+ 'stripe': '^14.0.0',
133
+ '@prisma/client': '^5.0.0',
134
+ '@stripe/stripe-js': '^2.0.0'
135
+ },
136
+ devDependencies: {
137
+ 'prisma': '^5.0.0'
138
+ },
139
+ peerDependencies: {},
140
+ templateDependencies: [],
141
+ envVariables: [
142
+ 'CLERK_SECRET_KEY',
143
+ 'CLERK_PUBLISHABLE_KEY',
144
+ 'STRIPE_SECRET_KEY',
145
+ 'STRIPE_PUBLISHABLE_KEY',
146
+ 'STRIPE_WEBHOOK_SECRET',
147
+ 'DATABASE_URL'
148
+ ],
149
+ scripts: {
150
+ 'db:push': 'prisma db push',
151
+ 'db:seed': 'prisma db seed',
152
+ 'stripe:listen': 'stripe listen --forward-to localhost:3000/api/webhooks/stripe'
153
+ },
154
+ stats: {
155
+ downloads: 15420,
156
+ stars: 342,
157
+ rating: 4.8,
158
+ reviews: 89
159
+ },
160
+ changelog: [
161
+ { version: '1.2.0', date: '2025-01-15', changes: ['Added team management', 'Improved Stripe webhook handling'] },
162
+ { version: '1.1.0', date: '2024-12-01', changes: ['Added admin panel', 'Updated to Clerk v5'] },
163
+ { version: '1.0.0', date: '2024-10-15', changes: ['Initial release'] }
164
+ ],
165
+ createdAt: '2024-10-15T00:00:00Z',
166
+ updatedAt: '2025-01-15T00:00:00Z'
167
+ },
168
+
169
+ 'api-starter': {
170
+ id: 'api-starter',
171
+ name: 'API Starter',
172
+ description: 'REST API with authentication, validation, documentation, and rate limiting. Includes OpenAPI/Swagger docs generation.',
173
+ category: 'starter',
174
+ version: '1.1.0',
175
+ author: {
176
+ name: 'bootspring',
177
+ email: 'templates@bootspring.com',
178
+ verified: true
179
+ },
180
+ official: true,
181
+ featured: true,
182
+ tags: ['api', 'rest', 'auth', 'docs', 'openapi', 'swagger', 'validation'],
183
+ stack: ['express', 'prisma', 'swagger', 'zod'],
184
+ license: 'MIT',
185
+ repository: 'https://github.com/bootspring/api-starter',
186
+ documentation: 'https://bootspring.com/templates/api-starter',
187
+ files: [
188
+ 'src/routes/**/*',
189
+ 'src/middleware/**/*',
190
+ 'src/controllers/**/*',
191
+ 'src/validators/**/*',
192
+ 'src/lib/swagger.ts',
193
+ 'prisma/schema.prisma'
194
+ ],
195
+ dependencies: {
196
+ 'express': '^4.18.0',
197
+ '@prisma/client': '^5.0.0',
198
+ 'swagger-ui-express': '^5.0.0',
199
+ 'zod': '^3.22.0',
200
+ 'express-rate-limit': '^7.0.0',
201
+ 'helmet': '^7.0.0',
202
+ 'cors': '^2.8.0'
203
+ },
204
+ devDependencies: {
205
+ 'prisma': '^5.0.0',
206
+ '@types/express': '^4.17.0',
207
+ '@types/swagger-ui-express': '^4.1.0'
208
+ },
209
+ peerDependencies: {},
210
+ templateDependencies: [],
211
+ envVariables: [
212
+ 'DATABASE_URL',
213
+ 'JWT_SECRET',
214
+ 'API_RATE_LIMIT'
215
+ ],
216
+ scripts: {
217
+ 'db:push': 'prisma db push',
218
+ 'docs:generate': 'ts-node scripts/generate-docs.ts'
219
+ },
220
+ stats: {
221
+ downloads: 8750,
222
+ stars: 156,
223
+ rating: 4.6,
224
+ reviews: 42
225
+ },
226
+ changelog: [
227
+ { version: '1.1.0', date: '2025-01-10', changes: ['Added rate limiting', 'Improved error handling'] },
228
+ { version: '1.0.0', date: '2024-11-20', changes: ['Initial release'] }
229
+ ],
230
+ createdAt: '2024-11-20T00:00:00Z',
231
+ updatedAt: '2025-01-10T00:00:00Z'
232
+ },
233
+
234
+ 'auth-clerk': {
235
+ id: 'auth-clerk',
236
+ name: 'Clerk Authentication',
237
+ description: 'Complete Clerk auth integration with protected routes, user profiles, organization support, and SSO.',
238
+ category: 'auth',
239
+ version: '2.0.0',
240
+ author: {
241
+ name: 'bootspring',
242
+ email: 'templates@bootspring.com',
243
+ verified: true
244
+ },
245
+ official: true,
246
+ featured: false,
247
+ tags: ['auth', 'clerk', 'sso', 'oauth', 'organizations', 'profiles'],
248
+ stack: ['nextjs', 'clerk'],
249
+ license: 'MIT',
250
+ repository: 'https://github.com/bootspring/auth-clerk',
251
+ documentation: 'https://bootspring.com/templates/auth-clerk',
252
+ files: [
253
+ 'src/middleware.ts',
254
+ 'src/app/sign-in/**/*',
255
+ 'src/app/sign-up/**/*',
256
+ 'src/app/user-profile/**/*',
257
+ 'src/components/auth/**/*',
258
+ 'src/lib/auth.ts'
259
+ ],
260
+ dependencies: {
261
+ '@clerk/nextjs': '^5.0.0'
262
+ },
263
+ devDependencies: {},
264
+ peerDependencies: {
265
+ 'next': '>=14.0.0'
266
+ },
267
+ templateDependencies: [],
268
+ envVariables: [
269
+ 'CLERK_SECRET_KEY',
270
+ 'CLERK_PUBLISHABLE_KEY'
271
+ ],
272
+ scripts: {},
273
+ stats: {
274
+ downloads: 12300,
275
+ stars: 234,
276
+ rating: 4.9,
277
+ reviews: 67
278
+ },
279
+ changelog: [
280
+ { version: '2.0.0', date: '2025-01-20', changes: ['Upgraded to Clerk v5', 'Added organization support'] },
281
+ { version: '1.0.0', date: '2024-09-01', changes: ['Initial release'] }
282
+ ],
283
+ createdAt: '2024-09-01T00:00:00Z',
284
+ updatedAt: '2025-01-20T00:00:00Z'
285
+ },
286
+
287
+ 'auth-nextauth': {
288
+ id: 'auth-nextauth',
289
+ name: 'NextAuth.js Integration',
290
+ description: 'NextAuth.js authentication with multiple providers, session handling, and database adapters.',
291
+ category: 'auth',
292
+ version: '1.0.0',
293
+ author: {
294
+ name: 'bootspring',
295
+ email: 'templates@bootspring.com',
296
+ verified: true
297
+ },
298
+ official: true,
299
+ featured: false,
300
+ tags: ['auth', 'nextauth', 'oauth', 'session', 'providers'],
301
+ stack: ['nextjs', 'nextauth', 'prisma'],
302
+ license: 'MIT',
303
+ repository: 'https://github.com/bootspring/auth-nextauth',
304
+ documentation: 'https://bootspring.com/templates/auth-nextauth',
305
+ files: [
306
+ 'src/app/api/auth/[...nextauth]/route.ts',
307
+ 'src/lib/auth.ts',
308
+ 'src/components/auth/**/*',
309
+ 'prisma/schema.prisma'
310
+ ],
311
+ dependencies: {
312
+ 'next-auth': '^4.24.0',
313
+ '@next-auth/prisma-adapter': '^1.0.0',
314
+ '@prisma/client': '^5.0.0'
315
+ },
316
+ devDependencies: {
317
+ 'prisma': '^5.0.0'
318
+ },
319
+ peerDependencies: {
320
+ 'next': '>=14.0.0'
321
+ },
322
+ templateDependencies: [],
323
+ envVariables: [
324
+ 'NEXTAUTH_SECRET',
325
+ 'NEXTAUTH_URL',
326
+ 'DATABASE_URL',
327
+ 'GOOGLE_CLIENT_ID',
328
+ 'GOOGLE_CLIENT_SECRET',
329
+ 'GITHUB_CLIENT_ID',
330
+ 'GITHUB_CLIENT_SECRET'
331
+ ],
332
+ scripts: {
333
+ 'db:push': 'prisma db push'
334
+ },
335
+ stats: {
336
+ downloads: 6200,
337
+ stars: 98,
338
+ rating: 4.5,
339
+ reviews: 31
340
+ },
341
+ changelog: [
342
+ { version: '1.0.0', date: '2024-12-15', changes: ['Initial release'] }
343
+ ],
344
+ createdAt: '2024-12-15T00:00:00Z',
345
+ updatedAt: '2024-12-15T00:00:00Z'
346
+ },
347
+
348
+ 'payments-stripe': {
349
+ id: 'payments-stripe',
350
+ name: 'Stripe Payments',
351
+ description: 'Complete Stripe integration with one-time payments, subscriptions, customer portal, and webhook handling.',
352
+ category: 'payments',
353
+ version: '1.3.0',
354
+ author: {
355
+ name: 'bootspring',
356
+ email: 'templates@bootspring.com',
357
+ verified: true
358
+ },
359
+ official: true,
360
+ featured: true,
361
+ tags: ['payments', 'stripe', 'subscriptions', 'checkout', 'webhooks', 'billing'],
362
+ stack: ['nextjs', 'stripe', 'prisma'],
363
+ license: 'MIT',
364
+ repository: 'https://github.com/bootspring/payments-stripe',
365
+ documentation: 'https://bootspring.com/templates/payments-stripe',
366
+ files: [
367
+ 'src/app/api/stripe/**/*',
368
+ 'src/app/api/webhooks/stripe/**/*',
369
+ 'src/components/billing/**/*',
370
+ 'src/components/checkout/**/*',
371
+ 'src/lib/stripe.ts',
372
+ 'prisma/schema.prisma'
373
+ ],
374
+ dependencies: {
375
+ 'stripe': '^14.0.0',
376
+ '@stripe/stripe-js': '^2.0.0',
377
+ '@prisma/client': '^5.0.0'
378
+ },
379
+ devDependencies: {
380
+ 'prisma': '^5.0.0'
381
+ },
382
+ peerDependencies: {
383
+ 'next': '>=14.0.0'
384
+ },
385
+ templateDependencies: [],
386
+ envVariables: [
387
+ 'STRIPE_SECRET_KEY',
388
+ 'STRIPE_PUBLISHABLE_KEY',
389
+ 'STRIPE_WEBHOOK_SECRET',
390
+ 'DATABASE_URL'
391
+ ],
392
+ scripts: {
393
+ 'stripe:listen': 'stripe listen --forward-to localhost:3000/api/webhooks/stripe',
394
+ 'db:push': 'prisma db push'
395
+ },
396
+ stats: {
397
+ downloads: 9800,
398
+ stars: 189,
399
+ rating: 4.7,
400
+ reviews: 54
401
+ },
402
+ changelog: [
403
+ { version: '1.3.0', date: '2025-01-25', changes: ['Added subscription pause/resume', 'Improved webhook reliability'] },
404
+ { version: '1.2.0', date: '2024-12-20', changes: ['Added customer portal'] },
405
+ { version: '1.1.0', date: '2024-11-15', changes: ['Added metered billing support'] },
406
+ { version: '1.0.0', date: '2024-10-01', changes: ['Initial release'] }
407
+ ],
408
+ createdAt: '2024-10-01T00:00:00Z',
409
+ updatedAt: '2025-01-25T00:00:00Z'
410
+ },
411
+
412
+ 'payments-lemonsqueezy': {
413
+ id: 'payments-lemonsqueezy',
414
+ name: 'LemonSqueezy Payments',
415
+ description: 'LemonSqueezy integration for payments and subscriptions. Simplified billing with built-in tax handling.',
416
+ category: 'payments',
417
+ version: '1.0.0',
418
+ author: {
419
+ name: 'bootspring',
420
+ email: 'templates@bootspring.com',
421
+ verified: true
422
+ },
423
+ official: true,
424
+ featured: false,
425
+ tags: ['payments', 'lemonsqueezy', 'subscriptions', 'checkout'],
426
+ stack: ['nextjs'],
427
+ license: 'MIT',
428
+ repository: 'https://github.com/bootspring/payments-lemonsqueezy',
429
+ documentation: 'https://bootspring.com/templates/payments-lemonsqueezy',
430
+ files: [
431
+ 'src/app/api/lemonsqueezy/**/*',
432
+ 'src/app/api/webhooks/lemonsqueezy/**/*',
433
+ 'src/components/billing/**/*',
434
+ 'src/lib/lemonsqueezy.ts'
435
+ ],
436
+ dependencies: {
437
+ '@lemonsqueezy/lemonsqueezy.js': '^2.0.0'
438
+ },
439
+ devDependencies: {},
440
+ peerDependencies: {
441
+ 'next': '>=14.0.0'
442
+ },
443
+ templateDependencies: [],
444
+ envVariables: [
445
+ 'LEMONSQUEEZY_API_KEY',
446
+ 'LEMONSQUEEZY_STORE_ID',
447
+ 'LEMONSQUEEZY_WEBHOOK_SECRET'
448
+ ],
449
+ scripts: {},
450
+ stats: {
451
+ downloads: 2100,
452
+ stars: 45,
453
+ rating: 4.4,
454
+ reviews: 12
455
+ },
456
+ changelog: [
457
+ { version: '1.0.0', date: '2025-01-10', changes: ['Initial release'] }
458
+ ],
459
+ createdAt: '2025-01-10T00:00:00Z',
460
+ updatedAt: '2025-01-10T00:00:00Z'
461
+ },
462
+
463
+ 'ai-chat': {
464
+ id: 'ai-chat',
465
+ name: 'AI Chat Interface',
466
+ description: 'Claude-powered chat interface with streaming responses, conversation history, and tool use support.',
467
+ category: 'ai',
468
+ version: '1.1.0',
469
+ author: {
470
+ name: 'bootspring',
471
+ email: 'templates@bootspring.com',
472
+ verified: true
473
+ },
474
+ official: true,
475
+ featured: true,
476
+ tags: ['ai', 'chat', 'claude', 'streaming', 'anthropic', 'llm'],
477
+ stack: ['nextjs', 'anthropic'],
478
+ license: 'MIT',
479
+ repository: 'https://github.com/bootspring/ai-chat',
480
+ documentation: 'https://bootspring.com/templates/ai-chat',
481
+ files: [
482
+ 'src/app/api/chat/**/*',
483
+ 'src/components/chat/**/*',
484
+ 'src/lib/anthropic.ts',
485
+ 'src/hooks/useChat.ts'
486
+ ],
487
+ dependencies: {
488
+ '@anthropic-ai/sdk': '^0.20.0'
489
+ },
490
+ devDependencies: {},
491
+ peerDependencies: {
492
+ 'next': '>=14.0.0'
493
+ },
494
+ templateDependencies: [],
495
+ envVariables: [
496
+ 'ANTHROPIC_API_KEY'
497
+ ],
498
+ scripts: {},
499
+ stats: {
500
+ downloads: 7500,
501
+ stars: 167,
502
+ rating: 4.8,
503
+ reviews: 38
504
+ },
505
+ changelog: [
506
+ { version: '1.1.0', date: '2025-02-01', changes: ['Added tool use support', 'Improved streaming'] },
507
+ { version: '1.0.0', date: '2024-11-01', changes: ['Initial release'] }
508
+ ],
509
+ createdAt: '2024-11-01T00:00:00Z',
510
+ updatedAt: '2025-02-01T00:00:00Z'
511
+ },
512
+
513
+ 'ai-rag': {
514
+ id: 'ai-rag',
515
+ name: 'RAG Document Chat',
516
+ description: 'Retrieval-Augmented Generation chat with document upload, embedding, and semantic search.',
517
+ category: 'ai',
518
+ version: '1.0.0',
519
+ author: {
520
+ name: 'bootspring',
521
+ email: 'templates@bootspring.com',
522
+ verified: true
523
+ },
524
+ official: true,
525
+ featured: false,
526
+ tags: ['ai', 'rag', 'embeddings', 'documents', 'vector-search', 'claude'],
527
+ stack: ['nextjs', 'anthropic', 'pinecone'],
528
+ license: 'MIT',
529
+ repository: 'https://github.com/bootspring/ai-rag',
530
+ documentation: 'https://bootspring.com/templates/ai-rag',
531
+ files: [
532
+ 'src/app/api/chat/**/*',
533
+ 'src/app/api/documents/**/*',
534
+ 'src/components/chat/**/*',
535
+ 'src/components/documents/**/*',
536
+ 'src/lib/anthropic.ts',
537
+ 'src/lib/embeddings.ts',
538
+ 'src/lib/vectorstore.ts'
539
+ ],
540
+ dependencies: {
541
+ '@anthropic-ai/sdk': '^0.20.0',
542
+ '@pinecone-database/pinecone': '^2.0.0',
543
+ 'pdf-parse': '^1.1.1'
544
+ },
545
+ devDependencies: {},
546
+ peerDependencies: {
547
+ 'next': '>=14.0.0'
548
+ },
549
+ templateDependencies: ['ai-chat'],
550
+ envVariables: [
551
+ 'ANTHROPIC_API_KEY',
552
+ 'PINECONE_API_KEY',
553
+ 'PINECONE_INDEX'
554
+ ],
555
+ scripts: {},
556
+ stats: {
557
+ downloads: 3200,
558
+ stars: 78,
559
+ rating: 4.6,
560
+ reviews: 19
561
+ },
562
+ changelog: [
563
+ { version: '1.0.0', date: '2025-01-15', changes: ['Initial release'] }
564
+ ],
565
+ createdAt: '2025-01-15T00:00:00Z',
566
+ updatedAt: '2025-01-15T00:00:00Z'
567
+ },
568
+
569
+ 'email-resend': {
570
+ id: 'email-resend',
571
+ name: 'Resend Email Integration',
572
+ description: 'Transactional emails with Resend. Includes email templates, sending, and delivery tracking.',
573
+ category: 'integration',
574
+ version: '1.0.0',
575
+ author: {
576
+ name: 'bootspring',
577
+ email: 'templates@bootspring.com',
578
+ verified: true
579
+ },
580
+ official: true,
581
+ featured: false,
582
+ tags: ['email', 'resend', 'transactional', 'templates'],
583
+ stack: ['nextjs', 'resend', 'react-email'],
584
+ license: 'MIT',
585
+ repository: 'https://github.com/bootspring/email-resend',
586
+ documentation: 'https://bootspring.com/templates/email-resend',
587
+ files: [
588
+ 'src/app/api/email/**/*',
589
+ 'src/lib/email.ts',
590
+ 'src/emails/**/*'
591
+ ],
592
+ dependencies: {
593
+ 'resend': '^3.0.0',
594
+ '@react-email/components': '^0.0.15',
595
+ 'react-email': '^2.0.0'
596
+ },
597
+ devDependencies: {},
598
+ peerDependencies: {
599
+ 'next': '>=14.0.0'
600
+ },
601
+ templateDependencies: [],
602
+ envVariables: [
603
+ 'RESEND_API_KEY',
604
+ 'EMAIL_FROM'
605
+ ],
606
+ scripts: {
607
+ 'email:dev': 'email dev'
608
+ },
609
+ stats: {
610
+ downloads: 4500,
611
+ stars: 67,
612
+ rating: 4.5,
613
+ reviews: 22
614
+ },
615
+ changelog: [
616
+ { version: '1.0.0', date: '2024-12-01', changes: ['Initial release'] }
617
+ ],
618
+ createdAt: '2024-12-01T00:00:00Z',
619
+ updatedAt: '2024-12-01T00:00:00Z'
620
+ },
621
+
622
+ 'analytics-posthog': {
623
+ id: 'analytics-posthog',
624
+ name: 'PostHog Analytics',
625
+ description: 'Product analytics with PostHog. Track events, user behavior, and feature flags.',
626
+ category: 'integration',
627
+ version: '1.0.0',
628
+ author: {
629
+ name: 'bootspring',
630
+ email: 'templates@bootspring.com',
631
+ verified: true
632
+ },
633
+ official: true,
634
+ featured: false,
635
+ tags: ['analytics', 'posthog', 'tracking', 'feature-flags'],
636
+ stack: ['nextjs', 'posthog'],
637
+ license: 'MIT',
638
+ repository: 'https://github.com/bootspring/analytics-posthog',
639
+ documentation: 'https://bootspring.com/templates/analytics-posthog',
640
+ files: [
641
+ 'src/components/PostHogProvider.tsx',
642
+ 'src/lib/posthog.ts',
643
+ 'src/hooks/useAnalytics.ts'
644
+ ],
645
+ dependencies: {
646
+ 'posthog-js': '^1.100.0',
647
+ 'posthog-node': '^3.0.0'
648
+ },
649
+ devDependencies: {},
650
+ peerDependencies: {
651
+ 'next': '>=14.0.0'
652
+ },
653
+ templateDependencies: [],
654
+ envVariables: [
655
+ 'NEXT_PUBLIC_POSTHOG_KEY',
656
+ 'NEXT_PUBLIC_POSTHOG_HOST'
657
+ ],
658
+ scripts: {},
659
+ stats: {
660
+ downloads: 3800,
661
+ stars: 54,
662
+ rating: 4.4,
663
+ reviews: 16
664
+ },
665
+ changelog: [
666
+ { version: '1.0.0', date: '2024-12-10', changes: ['Initial release'] }
667
+ ],
668
+ createdAt: '2024-12-10T00:00:00Z',
669
+ updatedAt: '2024-12-10T00:00:00Z'
670
+ }
671
+ };
672
+
673
+ // ============================================================================
674
+ // Helper Functions
675
+ // ============================================================================
676
+
677
+ /**
678
+ * Get paths for marketplace operations
679
+ * @returns {object} Paths configuration
680
+ */
681
+ function getPaths() {
682
+ const projectRoot = process.cwd();
683
+ return {
684
+ projectRoot,
685
+ cacheDir: path.join(projectRoot, MARKETPLACE_CONFIG.cacheDir),
686
+ templatesDir: path.join(projectRoot, MARKETPLACE_CONFIG.templatesDir),
687
+ installedPath: path.join(projectRoot, MARKETPLACE_CONFIG.templatesDir, 'installed.json'),
688
+ lockPath: path.join(projectRoot, MARKETPLACE_CONFIG.templatesDir, 'templates.lock')
689
+ };
690
+ }
691
+
692
+ /**
693
+ * Ensure marketplace directories exist
694
+ */
695
+ function ensureDirectories() {
696
+ const paths = getPaths();
697
+ if (!fs.existsSync(paths.cacheDir)) {
698
+ fs.mkdirSync(paths.cacheDir, { recursive: true });
699
+ }
700
+ if (!fs.existsSync(paths.templatesDir)) {
701
+ fs.mkdirSync(paths.templatesDir, { recursive: true });
702
+ }
703
+ }
704
+
705
+ /**
706
+ * Load installed templates
707
+ * @returns {object} Installed templates data
708
+ */
709
+ function loadInstalled() {
710
+ const paths = getPaths();
711
+ if (fs.existsSync(paths.installedPath)) {
712
+ try {
713
+ return JSON.parse(fs.readFileSync(paths.installedPath, 'utf8'));
714
+ } catch {
715
+ return { templates: [], lastUpdated: null };
716
+ }
717
+ }
718
+ return { templates: [], lastUpdated: null };
719
+ }
720
+
721
+ /**
722
+ * Save installed templates
723
+ * @param {object} installed - Installed templates data
724
+ */
725
+ function saveInstalled(installed) {
726
+ const paths = getPaths();
727
+ ensureDirectories();
728
+ installed.lastUpdated = new Date().toISOString();
729
+ fs.writeFileSync(paths.installedPath, JSON.stringify(installed, null, 2));
730
+ }
731
+
732
+ /**
733
+ * Load the lockfile
734
+ * @returns {object} Lockfile data
735
+ */
736
+ function loadLockfile() {
737
+ const paths = getPaths();
738
+ if (fs.existsSync(paths.lockPath)) {
739
+ try {
740
+ return JSON.parse(fs.readFileSync(paths.lockPath, 'utf8'));
741
+ } catch {
742
+ return { version: 1, templates: {} };
743
+ }
744
+ }
745
+ return { version: 1, templates: {} };
746
+ }
747
+
748
+ /**
749
+ * Save the lockfile
750
+ * @param {object} lockfile - Lockfile data
751
+ */
752
+ function saveLockfile(lockfile) {
753
+ const paths = getPaths();
754
+ ensureDirectories();
755
+ lockfile.generatedAt = new Date().toISOString();
756
+ fs.writeFileSync(paths.lockPath, JSON.stringify(lockfile, null, 2));
757
+ }
758
+
759
+ /**
760
+ * Parse semver version string
761
+ * @param {string} version - Version string
762
+ * @returns {object} Parsed version { major, minor, patch, prerelease }
763
+ */
764
+ function parseSemver(version) {
765
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
766
+ if (!match) return null;
767
+ return {
768
+ major: parseInt(match[1], 10),
769
+ minor: parseInt(match[2], 10),
770
+ patch: parseInt(match[3], 10),
771
+ prerelease: match[4] || null
772
+ };
773
+ }
774
+
775
+ /**
776
+ * Compare two semver versions
777
+ * @param {string} a - First version
778
+ * @param {string} b - Second version
779
+ * @returns {number} -1 if a < b, 0 if equal, 1 if a > b
780
+ */
781
+ function compareSemver(a, b) {
782
+ const vA = parseSemver(a);
783
+ const vB = parseSemver(b);
784
+ if (!vA || !vB) return 0;
785
+
786
+ if (vA.major !== vB.major) return vA.major - vB.major;
787
+ if (vA.minor !== vB.minor) return vA.minor - vB.minor;
788
+ if (vA.patch !== vB.patch) return vA.patch - vB.patch;
789
+
790
+ // Handle prerelease
791
+ if (vA.prerelease && !vB.prerelease) return -1;
792
+ if (!vA.prerelease && vB.prerelease) return 1;
793
+ if (vA.prerelease && vB.prerelease) {
794
+ return vA.prerelease.localeCompare(vB.prerelease);
795
+ }
796
+
797
+ return 0;
798
+ }
799
+
800
+ /**
801
+ * Check if version satisfies a version range
802
+ * @param {string} version - Version to check
803
+ * @param {string} range - Version range (e.g., "^1.0.0", ">=1.0.0", "1.x")
804
+ * @returns {boolean} True if version satisfies range
805
+ */
806
+ function satisfiesVersionRange(version, range) {
807
+ const ver = parseSemver(version);
808
+ if (!ver) return false;
809
+
810
+ // Handle exact version
811
+ if (/^\d+\.\d+\.\d+$/.test(range)) {
812
+ return version === range;
813
+ }
814
+
815
+ // Handle caret range (^1.0.0 - compatible with major version)
816
+ if (range.startsWith('^')) {
817
+ const rangeVer = parseSemver(range.slice(1));
818
+ if (!rangeVer) return false;
819
+ if (ver.major !== rangeVer.major) return false;
820
+ if (ver.major === 0) {
821
+ return ver.minor >= rangeVer.minor && (ver.minor > rangeVer.minor || ver.patch >= rangeVer.patch);
822
+ }
823
+ return ver.minor > rangeVer.minor || (ver.minor === rangeVer.minor && ver.patch >= rangeVer.patch);
824
+ }
825
+
826
+ // Handle tilde range (~1.0.0 - compatible with minor version)
827
+ if (range.startsWith('~')) {
828
+ const rangeVer = parseSemver(range.slice(1));
829
+ if (!rangeVer) return false;
830
+ return ver.major === rangeVer.major && ver.minor === rangeVer.minor && ver.patch >= rangeVer.patch;
831
+ }
832
+
833
+ // Handle >= range
834
+ if (range.startsWith('>=')) {
835
+ const rangeVer = parseSemver(range.slice(2));
836
+ if (!rangeVer) return false;
837
+ return compareSemver(version, range.slice(2)) >= 0;
838
+ }
839
+
840
+ // Handle > range
841
+ if (range.startsWith('>')) {
842
+ const rangeVer = parseSemver(range.slice(1));
843
+ if (!rangeVer) return false;
844
+ return compareSemver(version, range.slice(1)) > 0;
845
+ }
846
+
847
+ // Handle x-range (1.x, 1.2.x)
848
+ if (range.includes('x') || range.includes('*')) {
849
+ const parts = range.split('.');
850
+ if (parts[0] === '*' || parts[0] === 'x') return true;
851
+ if (parseInt(parts[0], 10) !== ver.major) return false;
852
+ if (parts.length === 1 || parts[1] === '*' || parts[1] === 'x') return true;
853
+ if (parseInt(parts[1], 10) !== ver.minor) return false;
854
+ return true;
855
+ }
856
+
857
+ // Default: exact match
858
+ return version === range;
859
+ }
860
+
861
+ /**
862
+ * Make an HTTP request to the registry
863
+ * Reserved for future use when full registry API is available.
864
+ * @param {string} urlPath - URL path
865
+ * @param {object} options - Request options
866
+ * @returns {Promise<object>} Response data
867
+ */
868
+ async function _registryRequest(urlPath, options = {}) {
869
+ const url = new URL(urlPath, MARKETPLACE_CONFIG.registryUrl);
870
+ const isHttps = url.protocol === 'https:';
871
+ const httpModule = isHttps ? https : http;
872
+
873
+ return new Promise((resolve, reject) => {
874
+ const req = httpModule.request(url, {
875
+ method: options.method || 'GET',
876
+ headers: {
877
+ 'Content-Type': 'application/json',
878
+ 'User-Agent': 'bootspring-cli',
879
+ ...options.headers
880
+ },
881
+ timeout: options.timeout || 30000
882
+ }, (res) => {
883
+ let body = '';
884
+ res.on('data', chunk => body += chunk);
885
+ res.on('end', () => {
886
+ try {
887
+ const json = JSON.parse(body);
888
+ if (res.statusCode >= 400) {
889
+ const error = new Error(json.message || json.error || 'Registry error');
890
+ error.status = res.statusCode;
891
+ error.code = json.code;
892
+ reject(error);
893
+ } else {
894
+ resolve(json);
895
+ }
896
+ } catch {
897
+ if (res.statusCode >= 400) {
898
+ reject(new Error(body || 'Registry error'));
899
+ } else {
900
+ resolve(body);
901
+ }
902
+ }
903
+ });
904
+ });
905
+
906
+ req.on('error', (err) => {
907
+ if (err.code === 'ECONNREFUSED') {
908
+ reject(new Error('Cannot connect to Bootspring registry'));
909
+ } else {
910
+ reject(err);
911
+ }
912
+ });
913
+
914
+ req.on('timeout', () => {
915
+ req.destroy();
916
+ reject(new Error('Registry request timeout'));
917
+ });
918
+
919
+ if (options.body) {
920
+ req.write(JSON.stringify(options.body));
921
+ }
922
+ req.end();
923
+ });
924
+ }
925
+
926
+ /**
927
+ * Generate a hash for template content
928
+ * @param {object} template - Template definition
929
+ * @returns {string} SHA256 hash
930
+ */
931
+ function generateTemplateHash(template) {
932
+ const content = JSON.stringify({
933
+ id: template.id,
934
+ version: template.version,
935
+ files: template.files,
936
+ dependencies: template.dependencies
937
+ });
938
+ return crypto.createHash('sha256').update(content).digest('hex').slice(0, 12);
939
+ }
940
+
941
+ // ============================================================================
942
+ // Core API Functions
943
+ // ============================================================================
944
+
945
+ /**
946
+ * List all available templates (official + cached community)
947
+ * @param {object} options - Filter options
948
+ * @param {string} options.category - Filter by category
949
+ * @param {string} options.search - Search term
950
+ * @param {boolean} options.official - Filter official only
951
+ * @param {boolean} options.featured - Filter featured only
952
+ * @param {string} options.sort - Sort by: 'downloads', 'rating', 'updated', 'name'
953
+ * @param {number} options.limit - Maximum results
954
+ * @param {number} options.offset - Pagination offset
955
+ * @returns {object} Templates list with metadata
956
+ */
957
+ function listTemplates(options = {}) {
958
+ const { category, search, official, featured, sort = 'downloads', limit, offset = 0 } = options;
959
+ let templates = Object.values(OFFICIAL_TEMPLATES);
960
+
961
+ // Load cached community templates
962
+ const paths = getPaths();
963
+ const cachePath = path.join(paths.cacheDir, 'community-templates.json');
964
+ if (fs.existsSync(cachePath)) {
965
+ try {
966
+ const cached = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
967
+ if (Date.now() - new Date(cached.fetchedAt).getTime() < MARKETPLACE_CONFIG.cacheTTL) {
968
+ templates = templates.concat(cached.templates || []);
969
+ }
970
+ } catch {
971
+ // Ignore cache errors
972
+ }
973
+ }
974
+
975
+ // Filter by category
976
+ if (category) {
977
+ templates = templates.filter(t => t.category === category);
978
+ }
979
+
980
+ // Filter by official status
981
+ if (official !== undefined) {
982
+ templates = templates.filter(t => t.official === official);
983
+ }
984
+
985
+ // Filter by featured status
986
+ if (featured) {
987
+ templates = templates.filter(t => t.featured === true);
988
+ }
989
+
990
+ // Filter by search term
991
+ if (search) {
992
+ const searchLower = search.toLowerCase();
993
+ templates = templates.filter(t =>
994
+ t.name.toLowerCase().includes(searchLower) ||
995
+ t.description.toLowerCase().includes(searchLower) ||
996
+ t.tags?.some(tag => tag.toLowerCase().includes(searchLower)) ||
997
+ t.stack?.some(tech => tech.toLowerCase().includes(searchLower))
998
+ );
999
+ }
1000
+
1001
+ // Sort results
1002
+ switch (sort) {
1003
+ case 'downloads':
1004
+ templates.sort((a, b) => (b.stats?.downloads || 0) - (a.stats?.downloads || 0));
1005
+ break;
1006
+ case 'rating':
1007
+ templates.sort((a, b) => (b.stats?.rating || 0) - (a.stats?.rating || 0));
1008
+ break;
1009
+ case 'updated':
1010
+ templates.sort((a, b) => new Date(b.updatedAt || 0) - new Date(a.updatedAt || 0));
1011
+ break;
1012
+ case 'name':
1013
+ templates.sort((a, b) => a.name.localeCompare(b.name));
1014
+ break;
1015
+ }
1016
+
1017
+ const total = templates.length;
1018
+
1019
+ // Apply pagination
1020
+ if (offset > 0) {
1021
+ templates = templates.slice(offset);
1022
+ }
1023
+ if (limit) {
1024
+ templates = templates.slice(0, limit);
1025
+ }
1026
+
1027
+ return {
1028
+ templates,
1029
+ total,
1030
+ offset,
1031
+ limit: limit || total,
1032
+ hasMore: offset + templates.length < total
1033
+ };
1034
+ }
1035
+
1036
+ /**
1037
+ * Search templates with advanced filtering
1038
+ * @param {string} query - Search query
1039
+ * @param {object} options - Search options
1040
+ * @returns {object} Search results
1041
+ */
1042
+ function searchTemplates(query, options = {}) {
1043
+ const results = listTemplates({ ...options, search: query });
1044
+
1045
+ // Score and rank results
1046
+ const queryLower = query.toLowerCase();
1047
+ const queryTerms = queryLower.split(/\s+/);
1048
+
1049
+ results.templates = results.templates.map(template => {
1050
+ let score = 0;
1051
+
1052
+ // Exact name match
1053
+ if (template.name.toLowerCase() === queryLower) score += 100;
1054
+ else if (template.name.toLowerCase().includes(queryLower)) score += 50;
1055
+
1056
+ // Tag matches
1057
+ for (const term of queryTerms) {
1058
+ if (template.tags?.some(tag => tag.toLowerCase() === term)) score += 30;
1059
+ if (template.stack?.some(tech => tech.toLowerCase() === term)) score += 25;
1060
+ }
1061
+
1062
+ // Description match
1063
+ if (template.description.toLowerCase().includes(queryLower)) score += 10;
1064
+
1065
+ // Boost official and featured
1066
+ if (template.official) score += 15;
1067
+ if (template.featured) score += 10;
1068
+
1069
+ // Boost by popularity
1070
+ score += Math.min((template.stats?.downloads || 0) / 1000, 20);
1071
+ score += (template.stats?.rating || 0) * 2;
1072
+
1073
+ return { ...template, _score: score };
1074
+ });
1075
+
1076
+ // Sort by score
1077
+ results.templates.sort((a, b) => b._score - a._score);
1078
+
1079
+ return {
1080
+ query,
1081
+ results: results.templates,
1082
+ total: results.total,
1083
+ categories: [...new Set(results.templates.map(t => t.category))],
1084
+ tags: [...new Set(results.templates.flatMap(t => t.tags || []))]
1085
+ };
1086
+ }
1087
+
1088
+ /**
1089
+ * Get template by ID with full details
1090
+ * @param {string} templateId - Template ID
1091
+ * @returns {object|null} Template details
1092
+ */
1093
+ function getTemplate(templateId) {
1094
+ // Check official templates first
1095
+ if (OFFICIAL_TEMPLATES[templateId]) {
1096
+ return { ...OFFICIAL_TEMPLATES[templateId] };
1097
+ }
1098
+
1099
+ // Check cached community templates
1100
+ const paths = getPaths();
1101
+ const cachePath = path.join(paths.cacheDir, 'community-templates.json');
1102
+ if (fs.existsSync(cachePath)) {
1103
+ try {
1104
+ const cached = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
1105
+ const template = cached.templates?.find(t => t.id === templateId);
1106
+ if (template) return { ...template };
1107
+ } catch {
1108
+ // Ignore errors
1109
+ }
1110
+ }
1111
+
1112
+ return null;
1113
+ }
1114
+
1115
+ /**
1116
+ * Get detailed template information including dependencies and compatibility
1117
+ * @param {string} templateId - Template ID
1118
+ * @returns {object} Template details with resolved info
1119
+ */
1120
+ function getTemplateDetails(templateId) {
1121
+ const template = getTemplate(templateId);
1122
+ if (!template) {
1123
+ return { success: false, error: `Template not found: ${templateId}` };
1124
+ }
1125
+
1126
+ const installed = getInstalled(templateId);
1127
+
1128
+ // Resolve template dependencies
1129
+ const resolvedDeps = [];
1130
+ const missingDeps = [];
1131
+ for (const depId of (template.templateDependencies || [])) {
1132
+ const dep = getTemplate(depId);
1133
+ if (dep) {
1134
+ const depInstalled = getInstalled(depId);
1135
+ resolvedDeps.push({
1136
+ id: depId,
1137
+ name: dep.name,
1138
+ version: dep.version,
1139
+ installed: !!depInstalled,
1140
+ installedVersion: depInstalled?.version
1141
+ });
1142
+ } else {
1143
+ missingDeps.push(depId);
1144
+ }
1145
+ }
1146
+
1147
+ // Check for conflicts with installed templates
1148
+ const conflicts = [];
1149
+ const installedTemplates = loadInstalled().templates;
1150
+ for (const inst of installedTemplates) {
1151
+ // Check for file conflicts (simplified - would need actual file check)
1152
+ if (inst.files && template.files) {
1153
+ const overlap = inst.files.filter(f => template.files.some(tf => {
1154
+ const instBase = f.replace(/\*\*/g, '').replace(/\*/g, '');
1155
+ const tempBase = tf.replace(/\*\*/g, '').replace(/\*/g, '');
1156
+ return instBase.startsWith(tempBase) || tempBase.startsWith(instBase);
1157
+ }));
1158
+ if (overlap.length > 0) {
1159
+ conflicts.push({
1160
+ templateId: inst.id,
1161
+ templateName: inst.name,
1162
+ potentialOverlap: overlap.length
1163
+ });
1164
+ }
1165
+ }
1166
+ }
1167
+
1168
+ // Calculate total dependencies
1169
+ const totalDeps = Object.keys(template.dependencies || {}).length +
1170
+ Object.keys(template.devDependencies || {}).length;
1171
+
1172
+ return {
1173
+ success: true,
1174
+ template: {
1175
+ ...template,
1176
+ hash: generateTemplateHash(template)
1177
+ },
1178
+ installation: {
1179
+ installed: !!installed,
1180
+ installedVersion: installed?.version,
1181
+ installedAt: installed?.installedAt,
1182
+ needsUpdate: installed && compareSemver(template.version, installed.version) > 0
1183
+ },
1184
+ dependencies: {
1185
+ npm: {
1186
+ production: template.dependencies || {},
1187
+ development: template.devDependencies || {},
1188
+ peer: template.peerDependencies || {},
1189
+ total: totalDeps
1190
+ },
1191
+ templates: {
1192
+ required: resolvedDeps,
1193
+ missing: missingDeps,
1194
+ allInstalled: missingDeps.length === 0 && resolvedDeps.every(d => d.installed)
1195
+ }
1196
+ },
1197
+ compatibility: {
1198
+ conflicts,
1199
+ hasConflicts: conflicts.length > 0
1200
+ },
1201
+ envVariables: template.envVariables || [],
1202
+ scripts: template.scripts || {}
1203
+ };
1204
+ }
1205
+
1206
+ /**
1207
+ * Get templates by category
1208
+ * @param {string} category - Category key
1209
+ * @returns {object[]} Templates in category
1210
+ */
1211
+ function getByCategory(category) {
1212
+ return listTemplates({ category }).templates;
1213
+ }
1214
+
1215
+ /**
1216
+ * List template categories with counts
1217
+ * @returns {object[]} Category list
1218
+ */
1219
+ function listCategories() {
1220
+ return Object.entries(TEMPLATE_CATEGORIES).map(([key, cat]) => ({
1221
+ key,
1222
+ name: cat.name,
1223
+ description: cat.description,
1224
+ icon: cat.icon,
1225
+ count: listTemplates({ category: key }).total
1226
+ }));
1227
+ }
1228
+
1229
+ /**
1230
+ * Install a template
1231
+ * @param {string} templateId - Template ID
1232
+ * @param {object} options - Installation options
1233
+ * @param {boolean} options.force - Force reinstall
1234
+ * @param {boolean} options.skipDeps - Skip template dependencies
1235
+ * @param {boolean} options.dryRun - Only show what would happen
1236
+ * @param {string} options.version - Specific version to install
1237
+ * @returns {object} Installation result
1238
+ */
1239
+ function installTemplate(templateId, options = {}) {
1240
+ const { force = false, skipDeps = false, dryRun = false, version } = options;
1241
+
1242
+ // Get template details
1243
+ const details = getTemplateDetails(templateId);
1244
+ if (!details.success) {
1245
+ return details;
1246
+ }
1247
+
1248
+ const template = details.template;
1249
+
1250
+ // Check version if specified
1251
+ if (version && version !== template.version) {
1252
+ // In a full implementation, would fetch specific version from registry
1253
+ return {
1254
+ success: false,
1255
+ error: `Version ${version} not available. Latest version is ${template.version}`
1256
+ };
1257
+ }
1258
+
1259
+ // Check if already installed
1260
+ if (details.installation.installed && !force) {
1261
+ if (!details.installation.needsUpdate) {
1262
+ return {
1263
+ success: false,
1264
+ error: 'Template already installed at latest version. Use force: true to reinstall.',
1265
+ installed: getInstalled(templateId)
1266
+ };
1267
+ }
1268
+ }
1269
+
1270
+ // Check template dependencies
1271
+ if (!skipDeps && !details.dependencies.templates.allInstalled) {
1272
+ const missing = details.dependencies.templates.required.filter(d => !d.installed);
1273
+ const notFound = details.dependencies.templates.missing;
1274
+
1275
+ if (notFound.length > 0) {
1276
+ return {
1277
+ success: false,
1278
+ error: `Missing template dependencies: ${notFound.join(', ')}`,
1279
+ missingDependencies: notFound
1280
+ };
1281
+ }
1282
+
1283
+ if (missing.length > 0 && !dryRun) {
1284
+ // Install missing dependencies first
1285
+ const depResults = [];
1286
+ for (const dep of missing) {
1287
+ const result = installTemplate(dep.id, { ...options, skipDeps: false });
1288
+ depResults.push({ id: dep.id, ...result });
1289
+ if (!result.success) {
1290
+ return {
1291
+ success: false,
1292
+ error: `Failed to install dependency ${dep.id}: ${result.error}`,
1293
+ dependencyResults: depResults
1294
+ };
1295
+ }
1296
+ }
1297
+ }
1298
+ }
1299
+
1300
+ // Warn about conflicts
1301
+ if (details.compatibility.hasConflicts) {
1302
+ const conflictInfo = details.compatibility.conflicts.map(c =>
1303
+ `${c.templateName} (${c.potentialOverlap} potential file overlaps)`
1304
+ ).join(', ');
1305
+
1306
+ if (!force) {
1307
+ return {
1308
+ success: false,
1309
+ error: `Potential conflicts with installed templates: ${conflictInfo}. Use force: true to override.`,
1310
+ conflicts: details.compatibility.conflicts
1311
+ };
1312
+ }
1313
+ }
1314
+
1315
+ // Dry run - return what would happen
1316
+ if (dryRun) {
1317
+ return {
1318
+ success: true,
1319
+ dryRun: true,
1320
+ template: templateId,
1321
+ name: template.name,
1322
+ version: template.version,
1323
+ wouldInstall: {
1324
+ files: template.files?.length || 0,
1325
+ dependencies: Object.keys(template.dependencies || {}).length,
1326
+ devDependencies: Object.keys(template.devDependencies || {}).length
1327
+ },
1328
+ envVariables: template.envVariables || [],
1329
+ scripts: template.scripts || {},
1330
+ conflicts: details.compatibility.conflicts,
1331
+ message: `Would install "${template.name}" v${template.version}`
1332
+ };
1333
+ }
1334
+
1335
+ // Perform installation
1336
+ ensureDirectories();
1337
+ const installedData = loadInstalled();
1338
+ const lockfile = loadLockfile();
1339
+
1340
+ // Create installation record
1341
+ const installation = {
1342
+ id: templateId,
1343
+ name: template.name,
1344
+ version: template.version,
1345
+ category: template.category,
1346
+ hash: details.template.hash,
1347
+ installedAt: new Date().toISOString(),
1348
+ files: template.files || [],
1349
+ dependencies: template.dependencies || {},
1350
+ devDependencies: template.devDependencies || {},
1351
+ envVariables: template.envVariables || [],
1352
+ templateDependencies: template.templateDependencies || []
1353
+ };
1354
+
1355
+ // Update or add to installed list
1356
+ const existingIndex = installedData.templates.findIndex(t => t.id === templateId);
1357
+ if (existingIndex >= 0) {
1358
+ installedData.templates[existingIndex] = installation;
1359
+ } else {
1360
+ installedData.templates.push(installation);
1361
+ }
1362
+
1363
+ // Update lockfile
1364
+ lockfile.templates[templateId] = {
1365
+ version: template.version,
1366
+ hash: installation.hash,
1367
+ resolved: OFFICIAL_TEMPLATES[templateId] ? 'official' : 'registry',
1368
+ dependencies: template.dependencies || {}
1369
+ };
1370
+
1371
+ saveInstalled(installedData);
1372
+ saveLockfile(lockfile);
1373
+
1374
+ return {
1375
+ success: true,
1376
+ template: templateId,
1377
+ name: template.name,
1378
+ version: template.version,
1379
+ files: installation.files.length,
1380
+ dependencies: Object.keys(template.dependencies || {}).length,
1381
+ devDependencies: Object.keys(template.devDependencies || {}).length,
1382
+ envVariables: template.envVariables || [],
1383
+ scripts: template.scripts || {},
1384
+ message: `Template "${template.name}" v${template.version} installed successfully`,
1385
+ nextSteps: [
1386
+ template.envVariables?.length ? `Set up environment variables: ${template.envVariables.join(', ')}` : null,
1387
+ Object.keys(template.dependencies || {}).length ? 'Run: npm install' : null,
1388
+ Object.keys(template.scripts || {}).length ? `Available scripts: ${Object.keys(template.scripts).join(', ')}` : null
1389
+ ].filter(Boolean)
1390
+ };
1391
+ }
1392
+
1393
+ /**
1394
+ * Uninstall a template
1395
+ * @param {string} templateId - Template ID
1396
+ * @param {object} options - Uninstall options
1397
+ * @param {boolean} options.force - Force uninstall even if other templates depend on it
1398
+ * @returns {object} Uninstall result
1399
+ */
1400
+ function uninstallTemplate(templateId, options = {}) {
1401
+ const installed = loadInstalled();
1402
+ const lockfile = loadLockfile();
1403
+
1404
+ const index = installed.templates.findIndex(t => t.id === templateId);
1405
+ if (index === -1) {
1406
+ return { success: false, error: `Template not installed: ${templateId}` };
1407
+ }
1408
+
1409
+ // Check if other templates depend on this one
1410
+ if (!options.force) {
1411
+ const dependents = installed.templates.filter(t =>
1412
+ t.templateDependencies?.includes(templateId)
1413
+ );
1414
+ if (dependents.length > 0) {
1415
+ return {
1416
+ success: false,
1417
+ error: `Cannot uninstall: other templates depend on this (${dependents.map(d => d.name).join(', ')}). Use force: true to override.`,
1418
+ dependents: dependents.map(d => ({ id: d.id, name: d.name }))
1419
+ };
1420
+ }
1421
+ }
1422
+
1423
+ const template = installed.templates[index];
1424
+ installed.templates.splice(index, 1);
1425
+ delete lockfile.templates[templateId];
1426
+
1427
+ saveInstalled(installed);
1428
+ saveLockfile(lockfile);
1429
+
1430
+ return {
1431
+ success: true,
1432
+ template: templateId,
1433
+ name: template.name,
1434
+ version: template.version,
1435
+ message: `Template "${template.name}" uninstalled successfully`,
1436
+ note: 'Template files and npm dependencies were not removed. Clean up manually if needed.'
1437
+ };
1438
+ }
1439
+
1440
+ /**
1441
+ * List installed templates
1442
+ * @returns {object[]} Installed templates
1443
+ */
1444
+ function listInstalled() {
1445
+ const installed = loadInstalled();
1446
+ return installed.templates.map(t => ({
1447
+ ...t,
1448
+ needsUpdate: (() => {
1449
+ const latest = getTemplate(t.id);
1450
+ return latest && compareSemver(latest.version, t.version) > 0;
1451
+ })()
1452
+ }));
1453
+ }
1454
+
1455
+ /**
1456
+ * Check if a template is installed
1457
+ * @param {string} templateId - Template ID
1458
+ * @returns {boolean} Installation status
1459
+ */
1460
+ function isInstalled(templateId) {
1461
+ const installed = loadInstalled();
1462
+ return installed.templates.some(t => t.id === templateId);
1463
+ }
1464
+
1465
+ /**
1466
+ * Get installed template info
1467
+ * @param {string} templateId - Template ID
1468
+ * @returns {object|null} Installed template or null
1469
+ */
1470
+ function getInstalled(templateId) {
1471
+ const installed = loadInstalled();
1472
+ return installed.templates.find(t => t.id === templateId) || null;
1473
+ }
1474
+
1475
+ /**
1476
+ * Check for template updates
1477
+ * @returns {object} Update check results
1478
+ */
1479
+ function checkUpdates() {
1480
+ const installed = loadInstalled();
1481
+ const updates = [];
1482
+
1483
+ for (const inst of installed.templates) {
1484
+ const current = getTemplate(inst.id);
1485
+ if (current && compareSemver(current.version, inst.version) > 0) {
1486
+ updates.push({
1487
+ id: inst.id,
1488
+ name: inst.name,
1489
+ currentVersion: inst.version,
1490
+ latestVersion: current.version,
1491
+ changelog: current.changelog?.filter(c =>
1492
+ compareSemver(c.version, inst.version) > 0
1493
+ ).slice(0, 3) || []
1494
+ });
1495
+ }
1496
+ }
1497
+
1498
+ return {
1499
+ hasUpdates: updates.length > 0,
1500
+ updates,
1501
+ checkedAt: new Date().toISOString()
1502
+ };
1503
+ }
1504
+
1505
+ /**
1506
+ * Update an installed template
1507
+ * @param {string} templateId - Template ID
1508
+ * @param {object} options - Update options
1509
+ * @returns {object} Update result
1510
+ */
1511
+ function updateTemplate(templateId, options = {}) {
1512
+ const installed = getInstalled(templateId);
1513
+ if (!installed) {
1514
+ return { success: false, error: `Template not installed: ${templateId}` };
1515
+ }
1516
+
1517
+ const latest = getTemplate(templateId);
1518
+ if (!latest) {
1519
+ return { success: false, error: `Template not found in registry: ${templateId}` };
1520
+ }
1521
+
1522
+ if (compareSemver(latest.version, installed.version) <= 0) {
1523
+ return {
1524
+ success: true,
1525
+ message: 'Template is already up to date',
1526
+ version: installed.version
1527
+ };
1528
+ }
1529
+
1530
+ // Reinstall with new version
1531
+ const result = installTemplate(templateId, { ...options, force: true });
1532
+
1533
+ if (result.success) {
1534
+ result.previousVersion = installed.version;
1535
+ result.changelog = latest.changelog?.filter(c =>
1536
+ compareSemver(c.version, installed.version) > 0
1537
+ ) || [];
1538
+ }
1539
+
1540
+ return result;
1541
+ }
1542
+
1543
+ /**
1544
+ * Update all installed templates
1545
+ * @param {object} options - Update options
1546
+ * @returns {object} Batch update results
1547
+ */
1548
+ function updateAll(options = {}) {
1549
+ const updateCheck = checkUpdates();
1550
+ if (!updateCheck.hasUpdates) {
1551
+ return {
1552
+ success: true,
1553
+ message: 'All templates are up to date',
1554
+ updated: 0
1555
+ };
1556
+ }
1557
+
1558
+ const results = [];
1559
+ for (const update of updateCheck.updates) {
1560
+ const result = updateTemplate(update.id, options);
1561
+ results.push({
1562
+ id: update.id,
1563
+ name: update.name,
1564
+ from: update.currentVersion,
1565
+ to: update.latestVersion,
1566
+ ...result
1567
+ });
1568
+ }
1569
+
1570
+ const succeeded = results.filter(r => r.success).length;
1571
+ const failed = results.filter(r => !r.success).length;
1572
+
1573
+ return {
1574
+ success: failed === 0,
1575
+ message: `Updated ${succeeded} template(s)${failed > 0 ? `, ${failed} failed` : ''}`,
1576
+ updated: succeeded,
1577
+ failed,
1578
+ results
1579
+ };
1580
+ }
1581
+
1582
+ /**
1583
+ * Publish a template to the marketplace
1584
+ * @param {object} templateDef - Template definition
1585
+ * @param {object} options - Publishing options
1586
+ * @returns {object} Publish result
1587
+ */
1588
+ function publishTemplate(templateDef, options = {}) {
1589
+ const { dryRun = false } = options;
1590
+ const errors = [];
1591
+ const warnings = [];
1592
+
1593
+ // Validate required fields
1594
+ if (!templateDef.id) errors.push('Missing required field: id');
1595
+ if (!templateDef.name) errors.push('Missing required field: name');
1596
+ if (!templateDef.description) errors.push('Missing required field: description');
1597
+ if (!templateDef.version) errors.push('Missing required field: version');
1598
+
1599
+ // Validate ID format
1600
+ if (templateDef.id && !/^[a-z0-9-]+$/.test(templateDef.id)) {
1601
+ errors.push('ID must contain only lowercase letters, numbers, and hyphens');
1602
+ }
1603
+
1604
+ // Validate version format
1605
+ if (templateDef.version && !parseSemver(templateDef.version)) {
1606
+ errors.push('Version must be valid semver (e.g., 1.0.0)');
1607
+ }
1608
+
1609
+ // Validate category
1610
+ if (!templateDef.category || !TEMPLATE_CATEGORIES[templateDef.category]) {
1611
+ errors.push(`Invalid category. Must be one of: ${Object.keys(TEMPLATE_CATEGORIES).join(', ')}`);
1612
+ }
1613
+
1614
+ // Validate files
1615
+ if (!templateDef.files || !Array.isArray(templateDef.files) || templateDef.files.length === 0) {
1616
+ errors.push('Template must include at least one file');
1617
+ }
1618
+
1619
+ // Check for reserved IDs
1620
+ if (OFFICIAL_TEMPLATES[templateDef.id]) {
1621
+ errors.push(`Template ID "${templateDef.id}" is reserved for official templates`);
1622
+ }
1623
+
1624
+ // Validate author
1625
+ if (!templateDef.author) {
1626
+ warnings.push('No author specified');
1627
+ } else if (typeof templateDef.author === 'string') {
1628
+ templateDef.author = { name: templateDef.author };
1629
+ }
1630
+
1631
+ // Validate dependencies format
1632
+ if (templateDef.dependencies && typeof templateDef.dependencies !== 'object') {
1633
+ errors.push('dependencies must be an object');
1634
+ }
1635
+
1636
+ // Validate template dependencies exist
1637
+ if (templateDef.templateDependencies) {
1638
+ for (const depId of templateDef.templateDependencies) {
1639
+ if (!getTemplate(depId)) {
1640
+ warnings.push(`Template dependency "${depId}" not found in registry`);
1641
+ }
1642
+ }
1643
+ }
1644
+
1645
+ // Validate tags
1646
+ if (templateDef.tags && !Array.isArray(templateDef.tags)) {
1647
+ errors.push('tags must be an array');
1648
+ }
1649
+
1650
+ // Check description length
1651
+ if (templateDef.description && templateDef.description.length < 20) {
1652
+ warnings.push('Description is very short. Consider adding more detail.');
1653
+ }
1654
+
1655
+ if (errors.length > 0) {
1656
+ return {
1657
+ success: false,
1658
+ errors,
1659
+ warnings
1660
+ };
1661
+ }
1662
+
1663
+ // Generate hash and prepare package
1664
+ const templatePackage = {
1665
+ id: templateDef.id,
1666
+ name: templateDef.name,
1667
+ description: templateDef.description,
1668
+ category: templateDef.category,
1669
+ version: templateDef.version,
1670
+ author: templateDef.author,
1671
+ official: false,
1672
+ featured: false,
1673
+ tags: templateDef.tags || [],
1674
+ stack: templateDef.stack || [],
1675
+ license: templateDef.license || 'MIT',
1676
+ repository: templateDef.repository,
1677
+ documentation: templateDef.documentation,
1678
+ files: templateDef.files,
1679
+ dependencies: templateDef.dependencies || {},
1680
+ devDependencies: templateDef.devDependencies || {},
1681
+ peerDependencies: templateDef.peerDependencies || {},
1682
+ templateDependencies: templateDef.templateDependencies || [],
1683
+ envVariables: templateDef.envVariables || [],
1684
+ scripts: templateDef.scripts || {},
1685
+ changelog: templateDef.changelog || [
1686
+ { version: templateDef.version, date: new Date().toISOString().split('T')[0], changes: ['Initial release'] }
1687
+ ],
1688
+ createdAt: new Date().toISOString(),
1689
+ updatedAt: new Date().toISOString()
1690
+ };
1691
+
1692
+ templatePackage.hash = generateTemplateHash(templatePackage);
1693
+
1694
+ if (dryRun) {
1695
+ return {
1696
+ success: true,
1697
+ dryRun: true,
1698
+ message: 'Template validation passed',
1699
+ template: {
1700
+ id: templatePackage.id,
1701
+ name: templatePackage.name,
1702
+ version: templatePackage.version,
1703
+ category: templatePackage.category,
1704
+ hash: templatePackage.hash
1705
+ },
1706
+ warnings,
1707
+ note: 'Run without dryRun to publish to registry'
1708
+ };
1709
+ }
1710
+
1711
+ // In a full implementation, this would upload to the registry
1712
+ // For now, we return a success with a note
1713
+ return {
1714
+ success: true,
1715
+ message: 'Template package prepared successfully',
1716
+ template: {
1717
+ id: templatePackage.id,
1718
+ name: templatePackage.name,
1719
+ version: templatePackage.version,
1720
+ category: templatePackage.category,
1721
+ hash: templatePackage.hash,
1722
+ files: templatePackage.files.length
1723
+ },
1724
+ warnings,
1725
+ note: 'Full publishing requires registry authentication. Contact support@bootspring.com to become a template author.',
1726
+ package: templatePackage
1727
+ };
1728
+ }
1729
+
1730
+ /**
1731
+ * Validate a template definition
1732
+ * @param {object} templateDef - Template definition
1733
+ * @returns {object} Validation result
1734
+ */
1735
+ function validateTemplate(templateDef) {
1736
+ return publishTemplate(templateDef, { dryRun: true });
1737
+ }
1738
+
1739
+ /**
1740
+ * Refresh community templates cache from registry
1741
+ * @returns {Promise<object>} Refresh result
1742
+ */
1743
+ async function refreshCache() {
1744
+ const paths = getPaths();
1745
+ ensureDirectories();
1746
+
1747
+ try {
1748
+ // In a full implementation, this would fetch from the registry
1749
+ // For now, we create a placeholder cache with example community templates
1750
+ const cache = {
1751
+ fetchedAt: new Date().toISOString(),
1752
+ templates: [
1753
+ // Example community templates that would come from registry
1754
+ {
1755
+ id: 'community-blog-mdx',
1756
+ name: 'MDX Blog Starter',
1757
+ description: 'Blog with MDX content, syntax highlighting, and RSS feed.',
1758
+ category: 'starter',
1759
+ version: '1.0.0',
1760
+ author: { name: 'community', verified: false },
1761
+ official: false,
1762
+ featured: false,
1763
+ tags: ['blog', 'mdx', 'content', 'rss'],
1764
+ stack: ['nextjs', 'mdx', 'tailwindcss'],
1765
+ license: 'MIT',
1766
+ files: ['src/app/blog/**/*', 'content/**/*'],
1767
+ dependencies: {
1768
+ 'next-mdx-remote': '^4.0.0',
1769
+ 'rehype-highlight': '^6.0.0'
1770
+ },
1771
+ templateDependencies: [],
1772
+ envVariables: [],
1773
+ stats: { downloads: 1200, stars: 34, rating: 4.3, reviews: 8 },
1774
+ createdAt: '2024-12-01T00:00:00Z',
1775
+ updatedAt: '2025-01-01T00:00:00Z'
1776
+ }
1777
+ ]
1778
+ };
1779
+
1780
+ const cachePath = path.join(paths.cacheDir, 'community-templates.json');
1781
+ fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2));
1782
+
1783
+ return {
1784
+ success: true,
1785
+ cached: cache.templates.length,
1786
+ message: 'Cache refreshed',
1787
+ fetchedAt: cache.fetchedAt
1788
+ };
1789
+ } catch (error) {
1790
+ return {
1791
+ success: false,
1792
+ error: error.message,
1793
+ message: 'Failed to refresh cache'
1794
+ };
1795
+ }
1796
+ }
1797
+
1798
+ /**
1799
+ * Get marketplace statistics
1800
+ * @returns {object} Marketplace stats
1801
+ */
1802
+ function getStats() {
1803
+ const installed = loadInstalled();
1804
+ const categories = listCategories();
1805
+ const allTemplates = listTemplates();
1806
+
1807
+ return {
1808
+ totalTemplates: allTemplates.total,
1809
+ officialTemplates: Object.keys(OFFICIAL_TEMPLATES).length,
1810
+ communityTemplates: allTemplates.total - Object.keys(OFFICIAL_TEMPLATES).length,
1811
+ installedTemplates: installed.templates.length,
1812
+ categories: categories.length,
1813
+ byCategory: categories.reduce((acc, cat) => {
1814
+ acc[cat.key] = cat.count;
1815
+ return acc;
1816
+ }, {}),
1817
+ featured: listTemplates({ featured: true }).total,
1818
+ hasUpdates: checkUpdates().hasUpdates
1819
+ };
1820
+ }
1821
+
1822
+ /**
1823
+ * Get featured templates
1824
+ * @returns {object[]} Featured templates
1825
+ */
1826
+ function getFeatured() {
1827
+ return listTemplates({ featured: true, limit: 6 }).templates;
1828
+ }
1829
+
1830
+ /**
1831
+ * Get popular templates
1832
+ * @param {number} limit - Maximum results
1833
+ * @returns {object[]} Popular templates
1834
+ */
1835
+ function getPopular(limit = 10) {
1836
+ return listTemplates({ sort: 'downloads', limit }).templates;
1837
+ }
1838
+
1839
+ /**
1840
+ * Get recently updated templates
1841
+ * @param {number} limit - Maximum results
1842
+ * @returns {object[]} Recently updated templates
1843
+ */
1844
+ function getRecentlyUpdated(limit = 10) {
1845
+ return listTemplates({ sort: 'updated', limit }).templates;
1846
+ }
1847
+
1848
+ /**
1849
+ * Get templates by stack/technology
1850
+ * @param {string} tech - Technology name
1851
+ * @returns {object[]} Matching templates
1852
+ */
1853
+ function getByStack(tech) {
1854
+ const techLower = tech.toLowerCase();
1855
+ return listTemplates().templates.filter(t =>
1856
+ t.stack?.some(s => s.toLowerCase() === techLower)
1857
+ );
1858
+ }
1859
+
1860
+ /**
1861
+ * Get templates by tag
1862
+ * @param {string} tag - Tag name
1863
+ * @returns {object[]} Matching templates
1864
+ */
1865
+ function getByTag(tag) {
1866
+ const tagLower = tag.toLowerCase();
1867
+ return listTemplates().templates.filter(t =>
1868
+ t.tags?.some(t => t.toLowerCase() === tagLower)
1869
+ );
1870
+ }
1871
+
1872
+ // ============================================================================
1873
+ // Exports
1874
+ // ============================================================================
1875
+
1876
+ module.exports = {
1877
+ // Core listing and search
1878
+ listTemplates,
1879
+ searchTemplates,
1880
+ getTemplate,
1881
+ getTemplateDetails,
1882
+ getByCategory,
1883
+ listCategories,
1884
+
1885
+ // Installation
1886
+ installTemplate,
1887
+ uninstallTemplate,
1888
+ listInstalled,
1889
+ isInstalled,
1890
+ getInstalled,
1891
+
1892
+ // Updates
1893
+ checkUpdates,
1894
+ updateTemplate,
1895
+ updateAll,
1896
+
1897
+ // Publishing
1898
+ publishTemplate,
1899
+ validateTemplate,
1900
+
1901
+ // Discovery helpers
1902
+ getFeatured,
1903
+ getPopular,
1904
+ getRecentlyUpdated,
1905
+ getByStack,
1906
+ getByTag,
1907
+
1908
+ // Cache and stats
1909
+ refreshCache,
1910
+ getStats,
1911
+
1912
+ // Version utilities
1913
+ parseSemver,
1914
+ compareSemver,
1915
+ satisfiesVersionRange,
1916
+
1917
+ // Constants
1918
+ TEMPLATE_CATEGORIES,
1919
+ OFFICIAL_TEMPLATES,
1920
+ MARKETPLACE_CONFIG
1921
+ };