@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.
- package/README.md +107 -14
- package/bin/bootspring.js +166 -27
- package/cli/agent.js +189 -17
- package/cli/analyze.js +499 -0
- package/cli/audit.js +557 -0
- package/cli/auth.js +495 -38
- package/cli/billing.js +302 -0
- package/cli/build.js +695 -0
- package/cli/business.js +109 -26
- package/cli/checkpoint-utils.js +168 -0
- package/cli/checkpoint.js +639 -0
- package/cli/cloud-sync.js +447 -0
- package/cli/content.js +198 -0
- package/cli/context.js +1 -1
- package/cli/deploy.js +543 -0
- package/cli/fundraise.js +112 -50
- package/cli/github-cmd.js +435 -0
- package/cli/health.js +477 -0
- package/cli/init.js +84 -13
- package/cli/legal.js +107 -95
- package/cli/log.js +2 -2
- package/cli/loop.js +976 -73
- package/cli/manager.js +711 -0
- package/cli/metrics.js +480 -0
- package/cli/monitor.js +812 -0
- package/cli/onboard.js +521 -0
- package/cli/orchestrator.js +12 -24
- package/cli/prd.js +594 -0
- package/cli/preseed-start.js +1483 -0
- package/cli/preseed.js +2302 -0
- package/cli/project.js +436 -0
- package/cli/quality.js +233 -0
- package/cli/security.js +913 -0
- package/cli/seed.js +1441 -5
- package/cli/skill.js +273 -211
- package/cli/suggest.js +989 -0
- package/cli/switch.js +453 -0
- package/cli/visualize.js +527 -0
- package/cli/watch.js +769 -0
- package/cli/workspace.js +607 -0
- package/core/analyze-workflow.js +1134 -0
- package/core/api-client.js +535 -22
- package/core/audit-workflow.js +1350 -0
- package/core/build-orchestrator.js +480 -0
- package/core/build-state.js +577 -0
- package/core/checkpoint-engine.js +408 -0
- package/core/config.js +1109 -26
- package/core/context-loader.js +21 -1
- package/core/deploy-workflow.js +836 -0
- package/core/entitlements.js +93 -22
- package/core/github-sync.js +610 -0
- package/core/index.js +8 -1
- package/core/ingest.js +1111 -0
- package/core/metrics-engine.js +768 -0
- package/core/onboard-workflow.js +1007 -0
- package/core/preseed-workflow.js +934 -0
- package/core/preseed.js +1617 -0
- package/core/project-context.js +325 -0
- package/core/project-state.js +694 -0
- package/core/r2-sync.js +583 -0
- package/core/scaffold.js +525 -7
- package/core/session.js +258 -0
- package/core/task-extractor.js +758 -0
- package/core/telemetry.js +28 -6
- package/core/tier-enforcement.js +737 -0
- package/core/utils.js +38 -14
- package/generators/questionnaire.js +15 -12
- package/generators/sections/ai.js +7 -7
- package/generators/sections/content.js +300 -0
- package/generators/sections/index.js +3 -0
- package/generators/sections/plugins.js +7 -6
- package/generators/templates/build-planning.template.js +596 -0
- package/generators/templates/content.template.js +819 -0
- package/generators/templates/index.js +2 -1
- package/hooks/git-autopilot.js +1250 -0
- package/hooks/index.js +9 -0
- package/intelligence/agent-collab.js +2057 -0
- package/intelligence/auto-suggest.js +634 -0
- package/intelligence/content-gen.js +1589 -0
- package/intelligence/cross-project.js +1647 -0
- package/intelligence/index.js +184 -0
- package/intelligence/learning/insights.json +517 -7
- package/intelligence/learning/pattern-learner.js +1008 -14
- package/intelligence/memory/decision-tracker.js +1431 -31
- package/intelligence/memory/decisions.jsonl +0 -0
- package/intelligence/orchestrator.js +2896 -1
- package/intelligence/prd.js +92 -1
- package/intelligence/recommendation-weights.json +14 -2
- package/intelligence/recommendations.js +463 -9
- package/intelligence/workflow-composer.js +1451 -0
- package/marketplace/index.d.ts +324 -0
- package/marketplace/index.js +1921 -0
- package/mcp/contracts/mcp-contract.v1.json +342 -4
- package/mcp/registry.js +680 -3
- package/mcp/response-formatter.js +23 -0
- package/mcp/tools/assist-tool.js +78 -4
- package/mcp/tools/autopilot-tool.js +408 -0
- package/mcp/tools/content-tool.js +571 -0
- package/mcp/tools/dashboard-tool.js +251 -5
- package/mcp/tools/mvp-tool.js +344 -0
- package/mcp/tools/plugin-tool.js +23 -1
- package/mcp/tools/prd-tool.js +579 -0
- package/mcp/tools/seed-tool.js +447 -0
- package/mcp/tools/skill-tool.js +43 -14
- package/mcp/tools/suggest-tool.js +147 -0
- package/package.json +15 -6
- package/agents/README.md +0 -93
- package/agents/ai-integration-expert/context.md +0 -386
- package/agents/api-expert/context.md +0 -416
- package/agents/architecture-expert/context.md +0 -454
- package/agents/auth-expert/context.md +0 -399
- package/agents/backend-expert/context.md +0 -483
- package/agents/business-strategy-expert/context.md +0 -180
- package/agents/code-review-expert/context.md +0 -365
- package/agents/competitive-analysis-expert/context.md +0 -239
- package/agents/data-modeling-expert/context.md +0 -352
- package/agents/database-expert/context.md +0 -250
- package/agents/devops-expert/context.md +0 -446
- package/agents/email-expert/context.md +0 -379
- package/agents/financial-expert/context.md +0 -213
- package/agents/frontend-expert/context.md +0 -364
- package/agents/fundraising-expert/context.md +0 -257
- package/agents/growth-expert/context.md +0 -249
- package/agents/index.js +0 -140
- package/agents/investor-relations-expert/context.md +0 -266
- package/agents/legal-expert/context.md +0 -284
- package/agents/marketing-expert/context.md +0 -236
- package/agents/monitoring-expert/context.md +0 -362
- package/agents/operations-expert/context.md +0 -279
- package/agents/partnerships-expert/context.md +0 -286
- package/agents/payment-expert/context.md +0 -340
- package/agents/performance-expert/context.md +0 -377
- package/agents/private-equity-expert/context.md +0 -246
- package/agents/railway-expert/context.md +0 -284
- package/agents/research-expert/context.md +0 -245
- package/agents/sales-expert/context.md +0 -241
- package/agents/security-expert/context.md +0 -343
- package/agents/testing-expert/context.md +0 -414
- package/agents/ui-ux-expert/context.md +0 -448
- package/agents/vercel-expert/context.md +0 -426
- package/skills/index.js +0 -787
- package/skills/patterns/README.md +0 -163
- package/skills/patterns/ai/agents.md +0 -281
- package/skills/patterns/ai/claude.md +0 -138
- package/skills/patterns/ai/embeddings.md +0 -150
- package/skills/patterns/ai/rag.md +0 -266
- package/skills/patterns/ai/streaming.md +0 -170
- package/skills/patterns/ai/structured-output.md +0 -162
- package/skills/patterns/ai/tools.md +0 -154
- package/skills/patterns/analytics/tracking.md +0 -220
- package/skills/patterns/api/errors.md +0 -296
- package/skills/patterns/api/graphql.md +0 -440
- package/skills/patterns/api/middleware.md +0 -279
- package/skills/patterns/api/openapi.md +0 -285
- package/skills/patterns/api/rate-limiting.md +0 -231
- package/skills/patterns/api/route-handler.md +0 -217
- package/skills/patterns/api/server-action.md +0 -249
- package/skills/patterns/api/versioning.md +0 -443
- package/skills/patterns/api/webhooks.md +0 -247
- package/skills/patterns/auth/clerk.md +0 -132
- package/skills/patterns/auth/mfa.md +0 -313
- package/skills/patterns/auth/nextauth.md +0 -140
- package/skills/patterns/auth/oauth.md +0 -237
- package/skills/patterns/auth/rbac.md +0 -152
- package/skills/patterns/auth/session-management.md +0 -367
- package/skills/patterns/auth/session.md +0 -120
- package/skills/patterns/database/audit.md +0 -177
- package/skills/patterns/database/migrations.md +0 -177
- package/skills/patterns/database/pagination.md +0 -230
- package/skills/patterns/database/pooling.md +0 -357
- package/skills/patterns/database/prisma.md +0 -180
- package/skills/patterns/database/relations.md +0 -187
- package/skills/patterns/database/seeding.md +0 -246
- package/skills/patterns/database/soft-delete.md +0 -153
- package/skills/patterns/database/transactions.md +0 -162
- package/skills/patterns/deployment/ci-cd.md +0 -231
- package/skills/patterns/deployment/docker.md +0 -188
- package/skills/patterns/deployment/monitoring.md +0 -387
- package/skills/patterns/deployment/vercel.md +0 -160
- package/skills/patterns/email/resend.md +0 -143
- package/skills/patterns/email/templates.md +0 -245
- package/skills/patterns/email/transactional.md +0 -503
- package/skills/patterns/email/verification.md +0 -176
- package/skills/patterns/files/download.md +0 -243
- package/skills/patterns/files/upload.md +0 -239
- package/skills/patterns/i18n/nextintl.md +0 -188
- package/skills/patterns/logging/structured.md +0 -292
- package/skills/patterns/notifications/email-queue.md +0 -248
- package/skills/patterns/notifications/push.md +0 -279
- package/skills/patterns/payments/checkout.md +0 -303
- package/skills/patterns/payments/invoices.md +0 -287
- package/skills/patterns/payments/portal.md +0 -245
- package/skills/patterns/payments/stripe.md +0 -272
- package/skills/patterns/payments/subscriptions.md +0 -300
- package/skills/patterns/payments/usage.md +0 -279
- package/skills/patterns/performance/caching.md +0 -276
- package/skills/patterns/performance/code-splitting.md +0 -233
- package/skills/patterns/performance/edge.md +0 -254
- package/skills/patterns/performance/isr.md +0 -266
- package/skills/patterns/performance/lazy-loading.md +0 -281
- package/skills/patterns/realtime/sse.md +0 -327
- package/skills/patterns/realtime/websockets.md +0 -336
- package/skills/patterns/search/filtering.md +0 -329
- package/skills/patterns/search/fulltext.md +0 -260
- package/skills/patterns/security/audit-logging.md +0 -444
- package/skills/patterns/security/csrf.md +0 -234
- package/skills/patterns/security/headers.md +0 -252
- package/skills/patterns/security/sanitization.md +0 -258
- package/skills/patterns/security/secrets.md +0 -261
- package/skills/patterns/security/validation.md +0 -268
- package/skills/patterns/security/xss.md +0 -229
- package/skills/patterns/seo/metadata.md +0 -252
- package/skills/patterns/state/context.md +0 -349
- package/skills/patterns/state/react-query.md +0 -313
- package/skills/patterns/state/url-state.md +0 -482
- package/skills/patterns/state/zustand.md +0 -262
- package/skills/patterns/testing/api.md +0 -259
- package/skills/patterns/testing/component.md +0 -233
- package/skills/patterns/testing/coverage.md +0 -207
- package/skills/patterns/testing/fixtures.md +0 -225
- package/skills/patterns/testing/integration.md +0 -436
- package/skills/patterns/testing/mocking.md +0 -177
- package/skills/patterns/testing/playwright.md +0 -162
- package/skills/patterns/testing/snapshot.md +0 -175
- package/skills/patterns/testing/vitest.md +0 -307
- package/skills/patterns/ui/accordions.md +0 -395
- package/skills/patterns/ui/cards.md +0 -299
- package/skills/patterns/ui/dropdowns.md +0 -476
- package/skills/patterns/ui/empty-states.md +0 -320
- package/skills/patterns/ui/forms.md +0 -405
- package/skills/patterns/ui/inputs.md +0 -319
- package/skills/patterns/ui/layouts.md +0 -282
- package/skills/patterns/ui/loading.md +0 -291
- package/skills/patterns/ui/modals.md +0 -338
- package/skills/patterns/ui/navigation.md +0 -374
- package/skills/patterns/ui/tables.md +0 -407
- package/skills/patterns/ui/toasts.md +0 -300
- package/skills/patterns/ui/tooltips.md +0 -396
- package/skills/patterns/utils/dates.md +0 -435
- package/skills/patterns/utils/errors.md +0 -451
- package/skills/patterns/utils/formatting.md +0 -345
- package/skills/patterns/utils/validation.md +0 -434
- package/templates/bootspring.config.js +0 -83
- package/templates/business/business-model-canvas.md +0 -246
- package/templates/business/business-plan.md +0 -266
- package/templates/business/competitive-analysis.md +0 -312
- package/templates/fundraising/data-room-checklist.md +0 -300
- package/templates/fundraising/investor-research.md +0 -243
- package/templates/fundraising/pitch-deck-outline.md +0 -253
- package/templates/legal/gdpr-checklist.md +0 -339
- package/templates/legal/privacy-policy.md +0 -285
- package/templates/legal/terms-of-service.md +0 -222
- 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
|
+
};
|