@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
package/cli/monitor.js
ADDED
|
@@ -0,0 +1,812 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring Monitor Command
|
|
3
|
+
* Production monitoring, hooks, and alerting
|
|
4
|
+
*
|
|
5
|
+
* @package bootspring
|
|
6
|
+
* @command monitor
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { execSync } = require('child_process');
|
|
12
|
+
const config = require('../core/config');
|
|
13
|
+
const utils = require('../core/utils');
|
|
14
|
+
|
|
15
|
+
// Supported monitoring providers
|
|
16
|
+
const MONITORING_PROVIDERS = {
|
|
17
|
+
sentry: {
|
|
18
|
+
name: 'Sentry',
|
|
19
|
+
description: 'Error tracking and performance monitoring',
|
|
20
|
+
envVars: ['SENTRY_DSN', 'SENTRY_AUTH_TOKEN'],
|
|
21
|
+
docs: 'https://docs.sentry.io/'
|
|
22
|
+
},
|
|
23
|
+
datadog: {
|
|
24
|
+
name: 'Datadog',
|
|
25
|
+
description: 'Infrastructure and application monitoring',
|
|
26
|
+
envVars: ['DD_API_KEY', 'DD_APP_KEY'],
|
|
27
|
+
docs: 'https://docs.datadoghq.com/'
|
|
28
|
+
},
|
|
29
|
+
newrelic: {
|
|
30
|
+
name: 'New Relic',
|
|
31
|
+
description: 'Full-stack observability platform',
|
|
32
|
+
envVars: ['NEW_RELIC_LICENSE_KEY', 'NEW_RELIC_APP_NAME'],
|
|
33
|
+
docs: 'https://docs.newrelic.com/'
|
|
34
|
+
},
|
|
35
|
+
logrocket: {
|
|
36
|
+
name: 'LogRocket',
|
|
37
|
+
description: 'Session replay and error tracking',
|
|
38
|
+
envVars: ['LOGROCKET_APP_ID'],
|
|
39
|
+
docs: 'https://docs.logrocket.com/'
|
|
40
|
+
},
|
|
41
|
+
axiom: {
|
|
42
|
+
name: 'Axiom',
|
|
43
|
+
description: 'Log management and analytics',
|
|
44
|
+
envVars: ['AXIOM_TOKEN', 'AXIOM_DATASET'],
|
|
45
|
+
docs: 'https://axiom.co/docs'
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Supported alert providers
|
|
50
|
+
const ALERT_PROVIDERS = {
|
|
51
|
+
slack: {
|
|
52
|
+
name: 'Slack',
|
|
53
|
+
description: 'Team messaging and notifications',
|
|
54
|
+
envVars: ['SLACK_WEBHOOK_URL'],
|
|
55
|
+
docs: 'https://api.slack.com/messaging/webhooks'
|
|
56
|
+
},
|
|
57
|
+
discord: {
|
|
58
|
+
name: 'Discord',
|
|
59
|
+
description: 'Community chat notifications',
|
|
60
|
+
envVars: ['DISCORD_WEBHOOK_URL'],
|
|
61
|
+
docs: 'https://discord.com/developers/docs/resources/webhook'
|
|
62
|
+
},
|
|
63
|
+
pagerduty: {
|
|
64
|
+
name: 'PagerDuty',
|
|
65
|
+
description: 'On-call management and incident response',
|
|
66
|
+
envVars: ['PAGERDUTY_ROUTING_KEY'],
|
|
67
|
+
docs: 'https://developer.pagerduty.com/'
|
|
68
|
+
},
|
|
69
|
+
email: {
|
|
70
|
+
name: 'Email',
|
|
71
|
+
description: 'Email notifications',
|
|
72
|
+
envVars: ['ALERT_EMAIL_TO', 'SMTP_HOST'],
|
|
73
|
+
docs: 'Configure SMTP settings for email alerts'
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Performance metrics to track
|
|
78
|
+
const PERFORMANCE_METRICS = {
|
|
79
|
+
bundleSize: {
|
|
80
|
+
name: 'Bundle Size',
|
|
81
|
+
description: 'JavaScript bundle size in KB',
|
|
82
|
+
command: 'du -sk .next 2>/dev/null || du -sk dist 2>/dev/null || echo "0"',
|
|
83
|
+
unit: 'KB'
|
|
84
|
+
},
|
|
85
|
+
buildTime: {
|
|
86
|
+
name: 'Build Time',
|
|
87
|
+
description: 'Time to build in seconds',
|
|
88
|
+
unit: 'seconds'
|
|
89
|
+
},
|
|
90
|
+
testCoverage: {
|
|
91
|
+
name: 'Test Coverage',
|
|
92
|
+
description: 'Code coverage percentage',
|
|
93
|
+
unit: '%'
|
|
94
|
+
},
|
|
95
|
+
dependencies: {
|
|
96
|
+
name: 'Dependencies',
|
|
97
|
+
description: 'Number of production dependencies',
|
|
98
|
+
unit: 'count'
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get monitor state directory
|
|
104
|
+
*/
|
|
105
|
+
function getStateDir(projectRoot) {
|
|
106
|
+
return path.join(projectRoot, '.bootspring', 'monitor');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Load monitor configuration
|
|
111
|
+
*/
|
|
112
|
+
function loadMonitorConfig(projectRoot) {
|
|
113
|
+
const stateDir = getStateDir(projectRoot);
|
|
114
|
+
const configPath = path.join(stateDir, 'config.json');
|
|
115
|
+
|
|
116
|
+
if (fs.existsSync(configPath)) {
|
|
117
|
+
try {
|
|
118
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
119
|
+
} catch {
|
|
120
|
+
return createDefaultConfig();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return createDefaultConfig();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Create default monitor configuration
|
|
129
|
+
*/
|
|
130
|
+
function createDefaultConfig() {
|
|
131
|
+
return {
|
|
132
|
+
version: '1.0.0',
|
|
133
|
+
monitoring: {
|
|
134
|
+
enabled: false,
|
|
135
|
+
providers: []
|
|
136
|
+
},
|
|
137
|
+
alerts: {
|
|
138
|
+
enabled: false,
|
|
139
|
+
providers: [],
|
|
140
|
+
thresholds: {
|
|
141
|
+
errorRatePercent: 1,
|
|
142
|
+
bundleSizeKB: 500,
|
|
143
|
+
buildTimeSeconds: 120
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
metrics: {
|
|
147
|
+
tracking: true,
|
|
148
|
+
history: []
|
|
149
|
+
},
|
|
150
|
+
hooks: {
|
|
151
|
+
postDeploy: [],
|
|
152
|
+
onError: [],
|
|
153
|
+
onAlert: []
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Save monitor configuration
|
|
160
|
+
*/
|
|
161
|
+
function saveMonitorConfig(projectRoot, monitorConfig) {
|
|
162
|
+
const stateDir = getStateDir(projectRoot);
|
|
163
|
+
if (!fs.existsSync(stateDir)) {
|
|
164
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const configPath = path.join(stateDir, 'config.json');
|
|
168
|
+
fs.writeFileSync(configPath, JSON.stringify(monitorConfig, null, 2));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Initialize monitoring
|
|
173
|
+
*/
|
|
174
|
+
function initMonitoring(projectRoot) {
|
|
175
|
+
const monitorConfig = loadMonitorConfig(projectRoot);
|
|
176
|
+
const stateDir = getStateDir(projectRoot);
|
|
177
|
+
|
|
178
|
+
if (!fs.existsSync(stateDir)) {
|
|
179
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Create subdirectories
|
|
183
|
+
const dirs = ['metrics', 'snapshots', 'alerts'];
|
|
184
|
+
for (const dir of dirs) {
|
|
185
|
+
const dirPath = path.join(stateDir, dir);
|
|
186
|
+
if (!fs.existsSync(dirPath)) {
|
|
187
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
saveMonitorConfig(projectRoot, monitorConfig);
|
|
192
|
+
|
|
193
|
+
utils.print.success('Monitoring initialized');
|
|
194
|
+
console.log(`${utils.COLORS.dim}State directory: ${stateDir}${utils.COLORS.reset}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Show monitoring status
|
|
199
|
+
*/
|
|
200
|
+
function showStatus(projectRoot) {
|
|
201
|
+
const monitorConfig = loadMonitorConfig(projectRoot);
|
|
202
|
+
|
|
203
|
+
console.log(`
|
|
204
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}📊 Monitoring Status${utils.COLORS.reset}
|
|
205
|
+
${utils.COLORS.dim}Project: ${projectRoot}${utils.COLORS.reset}
|
|
206
|
+
`);
|
|
207
|
+
|
|
208
|
+
// Monitoring providers
|
|
209
|
+
console.log(`${utils.COLORS.bold}Monitoring Providers${utils.COLORS.reset}`);
|
|
210
|
+
if (monitorConfig.monitoring.providers.length === 0) {
|
|
211
|
+
console.log(` ${utils.COLORS.dim}No providers configured${utils.COLORS.reset}`);
|
|
212
|
+
} else {
|
|
213
|
+
for (const provider of monitorConfig.monitoring.providers) {
|
|
214
|
+
const info = MONITORING_PROVIDERS[provider.id];
|
|
215
|
+
const status = provider.enabled ?
|
|
216
|
+
`${utils.COLORS.green}✓ enabled${utils.COLORS.reset}` :
|
|
217
|
+
`${utils.COLORS.yellow}○ disabled${utils.COLORS.reset}`;
|
|
218
|
+
console.log(` ${info?.name || provider.id}: ${status}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
console.log();
|
|
222
|
+
|
|
223
|
+
// Alert providers
|
|
224
|
+
console.log(`${utils.COLORS.bold}Alert Providers${utils.COLORS.reset}`);
|
|
225
|
+
if (monitorConfig.alerts.providers.length === 0) {
|
|
226
|
+
console.log(` ${utils.COLORS.dim}No alert providers configured${utils.COLORS.reset}`);
|
|
227
|
+
} else {
|
|
228
|
+
for (const provider of monitorConfig.alerts.providers) {
|
|
229
|
+
const info = ALERT_PROVIDERS[provider.id];
|
|
230
|
+
const status = provider.enabled ?
|
|
231
|
+
`${utils.COLORS.green}✓ enabled${utils.COLORS.reset}` :
|
|
232
|
+
`${utils.COLORS.yellow}○ disabled${utils.COLORS.reset}`;
|
|
233
|
+
console.log(` ${info?.name || provider.id}: ${status}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
console.log();
|
|
237
|
+
|
|
238
|
+
// Thresholds
|
|
239
|
+
console.log(`${utils.COLORS.bold}Alert Thresholds${utils.COLORS.reset}`);
|
|
240
|
+
const thresholds = monitorConfig.alerts.thresholds;
|
|
241
|
+
console.log(` Error rate: ${thresholds.errorRatePercent}%`);
|
|
242
|
+
console.log(` Bundle size: ${thresholds.bundleSizeKB} KB`);
|
|
243
|
+
console.log(` Build time: ${thresholds.buildTimeSeconds}s`);
|
|
244
|
+
console.log();
|
|
245
|
+
|
|
246
|
+
// Recent metrics
|
|
247
|
+
const metricsDir = path.join(getStateDir(projectRoot), 'metrics');
|
|
248
|
+
if (fs.existsSync(metricsDir)) {
|
|
249
|
+
const files = fs.readdirSync(metricsDir).sort().slice(-5);
|
|
250
|
+
if (files.length > 0) {
|
|
251
|
+
console.log(`${utils.COLORS.bold}Recent Metrics${utils.COLORS.reset}`);
|
|
252
|
+
for (const file of files) {
|
|
253
|
+
try {
|
|
254
|
+
const metrics = JSON.parse(fs.readFileSync(path.join(metricsDir, file), 'utf-8'));
|
|
255
|
+
const date = new Date(metrics.timestamp).toLocaleDateString();
|
|
256
|
+
console.log(` ${date}: bundle=${metrics.bundleSize || 'N/A'}KB, deps=${metrics.dependencies || 'N/A'}`);
|
|
257
|
+
} catch {
|
|
258
|
+
// Skip invalid files
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Collect current metrics
|
|
267
|
+
*/
|
|
268
|
+
function collectMetrics(projectRoot) {
|
|
269
|
+
const spinner = utils.createSpinner('Collecting metrics...').start();
|
|
270
|
+
|
|
271
|
+
const metrics = {
|
|
272
|
+
timestamp: new Date().toISOString(),
|
|
273
|
+
bundleSize: null,
|
|
274
|
+
buildTime: null,
|
|
275
|
+
testCoverage: null,
|
|
276
|
+
dependencies: null,
|
|
277
|
+
devDependencies: null
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
// Get bundle size
|
|
282
|
+
const buildDirs = ['.next', 'dist', 'build', 'out'];
|
|
283
|
+
for (const dir of buildDirs) {
|
|
284
|
+
const dirPath = path.join(projectRoot, dir);
|
|
285
|
+
if (fs.existsSync(dirPath)) {
|
|
286
|
+
try {
|
|
287
|
+
const output = execSync(`du -sk "${dirPath}"`, {
|
|
288
|
+
cwd: projectRoot,
|
|
289
|
+
encoding: 'utf-8',
|
|
290
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
291
|
+
});
|
|
292
|
+
metrics.bundleSize = parseInt(output.split('\t')[0], 10);
|
|
293
|
+
break;
|
|
294
|
+
} catch {
|
|
295
|
+
// Continue to next directory
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Get dependency counts from package.json
|
|
301
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
302
|
+
if (fs.existsSync(pkgPath)) {
|
|
303
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
304
|
+
metrics.dependencies = Object.keys(pkg.dependencies || {}).length;
|
|
305
|
+
metrics.devDependencies = Object.keys(pkg.devDependencies || {}).length;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Get test coverage if available
|
|
309
|
+
const coveragePath = path.join(projectRoot, 'coverage', 'coverage-summary.json');
|
|
310
|
+
if (fs.existsSync(coveragePath)) {
|
|
311
|
+
try {
|
|
312
|
+
const coverage = JSON.parse(fs.readFileSync(coveragePath, 'utf-8'));
|
|
313
|
+
metrics.testCoverage = coverage.total?.lines?.pct || null;
|
|
314
|
+
} catch {
|
|
315
|
+
// Coverage file invalid
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
spinner.succeed('Metrics collected');
|
|
320
|
+
} catch (error) {
|
|
321
|
+
spinner.fail(`Failed to collect metrics: ${error.message}`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Save metrics
|
|
325
|
+
const metricsDir = path.join(getStateDir(projectRoot), 'metrics');
|
|
326
|
+
if (!fs.existsSync(metricsDir)) {
|
|
327
|
+
fs.mkdirSync(metricsDir, { recursive: true });
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const filename = `metrics-${new Date().toISOString().split('T')[0]}.json`;
|
|
331
|
+
fs.writeFileSync(path.join(metricsDir, filename), JSON.stringify(metrics, null, 2));
|
|
332
|
+
|
|
333
|
+
// Display metrics
|
|
334
|
+
console.log();
|
|
335
|
+
console.log(`${utils.COLORS.bold}Current Metrics${utils.COLORS.reset}`);
|
|
336
|
+
console.log(` Bundle size: ${metrics.bundleSize !== null ? `${metrics.bundleSize} KB` : 'N/A'}`);
|
|
337
|
+
console.log(` Dependencies: ${metrics.dependencies !== null ? metrics.dependencies : 'N/A'}`);
|
|
338
|
+
console.log(` Dev deps: ${metrics.devDependencies !== null ? metrics.devDependencies : 'N/A'}`);
|
|
339
|
+
console.log(` Test coverage: ${metrics.testCoverage !== null ? `${metrics.testCoverage}%` : 'N/A'}`);
|
|
340
|
+
|
|
341
|
+
return metrics;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Add monitoring provider
|
|
346
|
+
*/
|
|
347
|
+
function addProvider(projectRoot, type, providerId) {
|
|
348
|
+
const providers = type === 'monitoring' ? MONITORING_PROVIDERS : ALERT_PROVIDERS;
|
|
349
|
+
|
|
350
|
+
if (!providers[providerId]) {
|
|
351
|
+
utils.print.error(`Unknown ${type} provider: ${providerId}`);
|
|
352
|
+
console.log(`${utils.COLORS.dim}Available: ${Object.keys(providers).join(', ')}${utils.COLORS.reset}`);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const provider = providers[providerId];
|
|
357
|
+
const monitorConfig = loadMonitorConfig(projectRoot);
|
|
358
|
+
|
|
359
|
+
// Check if already added
|
|
360
|
+
const existing = monitorConfig[type].providers.find(p => p.id === providerId);
|
|
361
|
+
if (existing) {
|
|
362
|
+
utils.print.warn(`${provider.name} is already configured`);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Add provider
|
|
367
|
+
monitorConfig[type].providers.push({
|
|
368
|
+
id: providerId,
|
|
369
|
+
enabled: true,
|
|
370
|
+
addedAt: new Date().toISOString()
|
|
371
|
+
});
|
|
372
|
+
monitorConfig[type].enabled = true;
|
|
373
|
+
|
|
374
|
+
saveMonitorConfig(projectRoot, monitorConfig);
|
|
375
|
+
|
|
376
|
+
utils.print.success(`Added ${provider.name}`);
|
|
377
|
+
console.log();
|
|
378
|
+
console.log(`${utils.COLORS.bold}Required Environment Variables${utils.COLORS.reset}`);
|
|
379
|
+
for (const envVar of provider.envVars) {
|
|
380
|
+
const hasVar = process.env[envVar] ?
|
|
381
|
+
`${utils.COLORS.green}✓${utils.COLORS.reset}` :
|
|
382
|
+
`${utils.COLORS.yellow}○${utils.COLORS.reset}`;
|
|
383
|
+
console.log(` ${hasVar} ${envVar}`);
|
|
384
|
+
}
|
|
385
|
+
console.log();
|
|
386
|
+
console.log(`${utils.COLORS.dim}Documentation: ${provider.docs}${utils.COLORS.reset}`);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Remove monitoring provider
|
|
391
|
+
*/
|
|
392
|
+
function removeProvider(projectRoot, type, providerId) {
|
|
393
|
+
const monitorConfig = loadMonitorConfig(projectRoot);
|
|
394
|
+
|
|
395
|
+
const index = monitorConfig[type].providers.findIndex(p => p.id === providerId);
|
|
396
|
+
if (index === -1) {
|
|
397
|
+
utils.print.error(`Provider ${providerId} is not configured`);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
monitorConfig[type].providers.splice(index, 1);
|
|
402
|
+
if (monitorConfig[type].providers.length === 0) {
|
|
403
|
+
monitorConfig[type].enabled = false;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
saveMonitorConfig(projectRoot, monitorConfig);
|
|
407
|
+
utils.print.success(`Removed ${providerId}`);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Run post-deploy hooks
|
|
412
|
+
*/
|
|
413
|
+
async function runPostDeploy(projectRoot, options = {}) {
|
|
414
|
+
const monitorConfig = loadMonitorConfig(projectRoot);
|
|
415
|
+
|
|
416
|
+
console.log(`
|
|
417
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}🚀 Post-Deploy Hooks${utils.COLORS.reset}
|
|
418
|
+
`);
|
|
419
|
+
|
|
420
|
+
const results = {
|
|
421
|
+
timestamp: new Date().toISOString(),
|
|
422
|
+
deployTarget: options.target || 'unknown',
|
|
423
|
+
steps: []
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
// Step 1: Collect metrics
|
|
427
|
+
console.log(`${utils.COLORS.bold}1. Collecting Metrics${utils.COLORS.reset}`);
|
|
428
|
+
const metrics = collectMetrics(projectRoot);
|
|
429
|
+
results.steps.push({ name: 'metrics', status: 'success', data: metrics });
|
|
430
|
+
console.log();
|
|
431
|
+
|
|
432
|
+
// Step 2: Check thresholds
|
|
433
|
+
console.log(`${utils.COLORS.bold}2. Checking Thresholds${utils.COLORS.reset}`);
|
|
434
|
+
const thresholds = monitorConfig.alerts.thresholds;
|
|
435
|
+
let hasWarnings = false;
|
|
436
|
+
|
|
437
|
+
if (metrics.bundleSize && metrics.bundleSize > thresholds.bundleSizeKB) {
|
|
438
|
+
console.log(` ${utils.COLORS.yellow}⚠${utils.COLORS.reset} Bundle size (${metrics.bundleSize}KB) exceeds threshold (${thresholds.bundleSizeKB}KB)`);
|
|
439
|
+
hasWarnings = true;
|
|
440
|
+
} else if (metrics.bundleSize) {
|
|
441
|
+
console.log(` ${utils.COLORS.green}✓${utils.COLORS.reset} Bundle size within threshold`);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (hasWarnings) {
|
|
445
|
+
results.steps.push({ name: 'thresholds', status: 'warning' });
|
|
446
|
+
} else {
|
|
447
|
+
results.steps.push({ name: 'thresholds', status: 'success' });
|
|
448
|
+
}
|
|
449
|
+
console.log();
|
|
450
|
+
|
|
451
|
+
// Step 3: Regenerate context
|
|
452
|
+
console.log(`${utils.COLORS.bold}3. Regenerating Context${utils.COLORS.reset}`);
|
|
453
|
+
try {
|
|
454
|
+
// Update CLAUDE.md with production status
|
|
455
|
+
await regenerateContext(projectRoot, options);
|
|
456
|
+
results.steps.push({ name: 'context', status: 'success' });
|
|
457
|
+
console.log(` ${utils.COLORS.green}✓${utils.COLORS.reset} Context updated`);
|
|
458
|
+
} catch (error) {
|
|
459
|
+
results.steps.push({ name: 'context', status: 'error', error: error.message });
|
|
460
|
+
console.log(` ${utils.COLORS.yellow}⚠${utils.COLORS.reset} Context update failed: ${error.message}`);
|
|
461
|
+
}
|
|
462
|
+
console.log();
|
|
463
|
+
|
|
464
|
+
// Step 4: Send alerts if configured
|
|
465
|
+
if (monitorConfig.alerts.enabled && monitorConfig.alerts.providers.length > 0) {
|
|
466
|
+
console.log(`${utils.COLORS.bold}4. Sending Notifications${utils.COLORS.reset}`);
|
|
467
|
+
for (const provider of monitorConfig.alerts.providers) {
|
|
468
|
+
if (provider.enabled) {
|
|
469
|
+
await sendDeployNotification(provider.id, {
|
|
470
|
+
target: options.target,
|
|
471
|
+
metrics,
|
|
472
|
+
warnings: hasWarnings
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
console.log();
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Save post-deploy snapshot
|
|
480
|
+
const snapshotsDir = path.join(getStateDir(projectRoot), 'snapshots');
|
|
481
|
+
if (!fs.existsSync(snapshotsDir)) {
|
|
482
|
+
fs.mkdirSync(snapshotsDir, { recursive: true });
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const snapshotFile = `deploy-${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
|
|
486
|
+
fs.writeFileSync(path.join(snapshotsDir, snapshotFile), JSON.stringify(results, null, 2));
|
|
487
|
+
|
|
488
|
+
utils.print.success('Post-deploy hooks completed');
|
|
489
|
+
console.log(`${utils.COLORS.dim}Snapshot saved: ${snapshotFile}${utils.COLORS.reset}`);
|
|
490
|
+
|
|
491
|
+
return results;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Regenerate production context
|
|
496
|
+
*/
|
|
497
|
+
async function regenerateContext(projectRoot, options = {}) {
|
|
498
|
+
// Update the CLAUDE.md with production deployment info
|
|
499
|
+
const claudeMdPath = path.join(projectRoot, 'CLAUDE.md');
|
|
500
|
+
|
|
501
|
+
if (!fs.existsSync(claudeMdPath)) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
let content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
506
|
+
|
|
507
|
+
// Update status
|
|
508
|
+
content = content.replace(
|
|
509
|
+
/\*\*Status\*\*: \w+/,
|
|
510
|
+
'**Status**: production'
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
// Update last deployed
|
|
514
|
+
const deployDate = new Date().toISOString().split('T')[0];
|
|
515
|
+
if (content.includes('**Last Deployed**:')) {
|
|
516
|
+
content = content.replace(
|
|
517
|
+
/\*\*Last Deployed\*\*: .*/,
|
|
518
|
+
`**Last Deployed**: ${deployDate}`
|
|
519
|
+
);
|
|
520
|
+
} else if (content.includes('**Last Updated**:')) {
|
|
521
|
+
content = content.replace(
|
|
522
|
+
/\*\*Last Updated\*\*: .*/,
|
|
523
|
+
`**Last Updated**: ${deployDate}\n**Last Deployed**: ${deployDate}`
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Add deployment target if specified
|
|
528
|
+
if (options.target) {
|
|
529
|
+
if (content.includes('**Deployed To**:')) {
|
|
530
|
+
content = content.replace(
|
|
531
|
+
/\*\*Deployed To\*\*: .*/,
|
|
532
|
+
`**Deployed To**: ${options.target}`
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
fs.writeFileSync(claudeMdPath, content);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Send deploy notification to provider
|
|
542
|
+
*/
|
|
543
|
+
async function sendDeployNotification(providerId, data) {
|
|
544
|
+
const { target, metrics, warnings } = data;
|
|
545
|
+
|
|
546
|
+
switch (providerId) {
|
|
547
|
+
case 'slack': {
|
|
548
|
+
const webhookUrl = process.env.SLACK_WEBHOOK_URL;
|
|
549
|
+
if (!webhookUrl) {
|
|
550
|
+
console.log(` ${utils.COLORS.yellow}○${utils.COLORS.reset} Slack: SLACK_WEBHOOK_URL not set`);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const message = {
|
|
555
|
+
text: '🚀 Deployment Complete',
|
|
556
|
+
blocks: [
|
|
557
|
+
{
|
|
558
|
+
type: 'section',
|
|
559
|
+
text: {
|
|
560
|
+
type: 'mrkdwn',
|
|
561
|
+
text: `*Deployment Complete*\nTarget: ${target}\nBundle: ${metrics.bundleSize || 'N/A'} KB\n${warnings ? '⚠️ Warnings detected' : '✅ All checks passed'}`
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
]
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
try {
|
|
568
|
+
execSync(`curl -s -X POST -H 'Content-type: application/json' --data '${JSON.stringify(message)}' "${webhookUrl}"`, {
|
|
569
|
+
stdio: 'pipe'
|
|
570
|
+
});
|
|
571
|
+
console.log(` ${utils.COLORS.green}✓${utils.COLORS.reset} Slack notification sent`);
|
|
572
|
+
} catch {
|
|
573
|
+
console.log(` ${utils.COLORS.yellow}○${utils.COLORS.reset} Slack notification failed`);
|
|
574
|
+
}
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
case 'discord': {
|
|
579
|
+
const webhookUrl = process.env.DISCORD_WEBHOOK_URL;
|
|
580
|
+
if (!webhookUrl) {
|
|
581
|
+
console.log(` ${utils.COLORS.yellow}○${utils.COLORS.reset} Discord: DISCORD_WEBHOOK_URL not set`);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const message = {
|
|
586
|
+
content: `🚀 **Deployment Complete**\nTarget: ${target}\nBundle: ${metrics.bundleSize || 'N/A'} KB\n${warnings ? '⚠️ Warnings detected' : '✅ All checks passed'}`
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
try {
|
|
590
|
+
execSync(`curl -s -X POST -H 'Content-type: application/json' --data '${JSON.stringify(message)}' "${webhookUrl}"`, {
|
|
591
|
+
stdio: 'pipe'
|
|
592
|
+
});
|
|
593
|
+
console.log(` ${utils.COLORS.green}✓${utils.COLORS.reset} Discord notification sent`);
|
|
594
|
+
} catch {
|
|
595
|
+
console.log(` ${utils.COLORS.yellow}○${utils.COLORS.reset} Discord notification failed`);
|
|
596
|
+
}
|
|
597
|
+
break;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
default:
|
|
601
|
+
console.log(` ${utils.COLORS.dim}${providerId}: Notification not implemented${utils.COLORS.reset}`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* List available providers
|
|
607
|
+
*/
|
|
608
|
+
function listProviders() {
|
|
609
|
+
console.log(`
|
|
610
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}📊 Available Providers${utils.COLORS.reset}
|
|
611
|
+
|
|
612
|
+
${utils.COLORS.bold}Monitoring${utils.COLORS.reset}
|
|
613
|
+
`);
|
|
614
|
+
|
|
615
|
+
for (const [id, provider] of Object.entries(MONITORING_PROVIDERS)) {
|
|
616
|
+
console.log(` ${utils.COLORS.cyan}${id}${utils.COLORS.reset}`);
|
|
617
|
+
console.log(` ${provider.description}`);
|
|
618
|
+
console.log(` ${utils.COLORS.dim}Env: ${provider.envVars.join(', ')}${utils.COLORS.reset}`);
|
|
619
|
+
console.log();
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
console.log(`${utils.COLORS.bold}Alerts${utils.COLORS.reset}
|
|
623
|
+
`);
|
|
624
|
+
|
|
625
|
+
for (const [id, provider] of Object.entries(ALERT_PROVIDERS)) {
|
|
626
|
+
console.log(` ${utils.COLORS.cyan}${id}${utils.COLORS.reset}`);
|
|
627
|
+
console.log(` ${provider.description}`);
|
|
628
|
+
console.log(` ${utils.COLORS.dim}Env: ${provider.envVars.join(', ')}${utils.COLORS.reset}`);
|
|
629
|
+
console.log();
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Set alert threshold
|
|
635
|
+
*/
|
|
636
|
+
function setThreshold(projectRoot, name, value) {
|
|
637
|
+
const validThresholds = ['errorRatePercent', 'bundleSizeKB', 'buildTimeSeconds'];
|
|
638
|
+
|
|
639
|
+
if (!validThresholds.includes(name)) {
|
|
640
|
+
utils.print.error(`Unknown threshold: ${name}`);
|
|
641
|
+
console.log(`${utils.COLORS.dim}Available: ${validThresholds.join(', ')}${utils.COLORS.reset}`);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const numValue = parseFloat(value);
|
|
646
|
+
if (isNaN(numValue)) {
|
|
647
|
+
utils.print.error('Value must be a number');
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const monitorConfig = loadMonitorConfig(projectRoot);
|
|
652
|
+
monitorConfig.alerts.thresholds[name] = numValue;
|
|
653
|
+
saveMonitorConfig(projectRoot, monitorConfig);
|
|
654
|
+
|
|
655
|
+
utils.print.success(`Set ${name} to ${numValue}`);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Show help
|
|
660
|
+
*/
|
|
661
|
+
function showHelp() {
|
|
662
|
+
console.log(`
|
|
663
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}📊 Bootspring Monitor${utils.COLORS.reset}
|
|
664
|
+
${utils.COLORS.dim}Production monitoring, hooks, and alerting${utils.COLORS.reset}
|
|
665
|
+
|
|
666
|
+
${utils.COLORS.bold}Usage:${utils.COLORS.reset}
|
|
667
|
+
bootspring monitor <command> [options]
|
|
668
|
+
|
|
669
|
+
${utils.COLORS.bold}Commands:${utils.COLORS.reset}
|
|
670
|
+
${utils.COLORS.cyan}init${utils.COLORS.reset} Initialize monitoring
|
|
671
|
+
${utils.COLORS.cyan}status${utils.COLORS.reset} Show monitoring status
|
|
672
|
+
${utils.COLORS.cyan}metrics${utils.COLORS.reset} Collect current metrics
|
|
673
|
+
${utils.COLORS.cyan}providers${utils.COLORS.reset} List available providers
|
|
674
|
+
${utils.COLORS.cyan}post-deploy${utils.COLORS.reset} Run post-deploy hooks
|
|
675
|
+
|
|
676
|
+
${utils.COLORS.bold}Provider Management:${utils.COLORS.reset}
|
|
677
|
+
${utils.COLORS.cyan}add-monitoring${utils.COLORS.reset} <id> Add monitoring provider
|
|
678
|
+
${utils.COLORS.cyan}add-alert${utils.COLORS.reset} <id> Add alert provider
|
|
679
|
+
${utils.COLORS.cyan}remove-monitoring${utils.COLORS.reset} <id> Remove monitoring provider
|
|
680
|
+
${utils.COLORS.cyan}remove-alert${utils.COLORS.reset} <id> Remove alert provider
|
|
681
|
+
|
|
682
|
+
${utils.COLORS.bold}Configuration:${utils.COLORS.reset}
|
|
683
|
+
${utils.COLORS.cyan}threshold${utils.COLORS.reset} <name> <val> Set alert threshold
|
|
684
|
+
|
|
685
|
+
${utils.COLORS.bold}Monitoring Providers:${utils.COLORS.reset}
|
|
686
|
+
sentry, datadog, newrelic, logrocket, axiom
|
|
687
|
+
|
|
688
|
+
${utils.COLORS.bold}Alert Providers:${utils.COLORS.reset}
|
|
689
|
+
slack, discord, pagerduty, email
|
|
690
|
+
|
|
691
|
+
${utils.COLORS.bold}Examples:${utils.COLORS.reset}
|
|
692
|
+
bootspring monitor init
|
|
693
|
+
bootspring monitor status
|
|
694
|
+
bootspring monitor metrics
|
|
695
|
+
bootspring monitor add-alert slack
|
|
696
|
+
bootspring monitor add-monitoring sentry
|
|
697
|
+
bootspring monitor threshold bundleSizeKB 1000
|
|
698
|
+
bootspring monitor post-deploy --target=vercel
|
|
699
|
+
`);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Run monitor command
|
|
704
|
+
*/
|
|
705
|
+
async function run(args) {
|
|
706
|
+
const parsedArgs = utils.parseArgs(args);
|
|
707
|
+
const subcommand = parsedArgs._[0];
|
|
708
|
+
|
|
709
|
+
if (!subcommand) {
|
|
710
|
+
showHelp();
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const cfg = config.load();
|
|
715
|
+
const projectRoot = cfg._projectRoot;
|
|
716
|
+
|
|
717
|
+
switch (subcommand) {
|
|
718
|
+
case 'init':
|
|
719
|
+
initMonitoring(projectRoot);
|
|
720
|
+
break;
|
|
721
|
+
|
|
722
|
+
case 'status':
|
|
723
|
+
showStatus(projectRoot);
|
|
724
|
+
break;
|
|
725
|
+
|
|
726
|
+
case 'metrics':
|
|
727
|
+
collectMetrics(projectRoot);
|
|
728
|
+
break;
|
|
729
|
+
|
|
730
|
+
case 'providers':
|
|
731
|
+
listProviders();
|
|
732
|
+
break;
|
|
733
|
+
|
|
734
|
+
case 'add-monitoring': {
|
|
735
|
+
const providerId = parsedArgs._[1];
|
|
736
|
+
if (!providerId) {
|
|
737
|
+
utils.print.error('Provider ID required');
|
|
738
|
+
console.log(`${utils.COLORS.dim}Usage: bootspring monitor add-monitoring <provider>${utils.COLORS.reset}`);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
addProvider(projectRoot, 'monitoring', providerId);
|
|
742
|
+
break;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
case 'add-alert': {
|
|
746
|
+
const providerId = parsedArgs._[1];
|
|
747
|
+
if (!providerId) {
|
|
748
|
+
utils.print.error('Provider ID required');
|
|
749
|
+
console.log(`${utils.COLORS.dim}Usage: bootspring monitor add-alert <provider>${utils.COLORS.reset}`);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
addProvider(projectRoot, 'alerts', providerId);
|
|
753
|
+
break;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
case 'remove-monitoring': {
|
|
757
|
+
const providerId = parsedArgs._[1];
|
|
758
|
+
if (!providerId) {
|
|
759
|
+
utils.print.error('Provider ID required');
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
removeProvider(projectRoot, 'monitoring', providerId);
|
|
763
|
+
break;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
case 'remove-alert': {
|
|
767
|
+
const providerId = parsedArgs._[1];
|
|
768
|
+
if (!providerId) {
|
|
769
|
+
utils.print.error('Provider ID required');
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
removeProvider(projectRoot, 'alerts', providerId);
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
case 'threshold': {
|
|
777
|
+
const name = parsedArgs._[1];
|
|
778
|
+
const value = parsedArgs._[2];
|
|
779
|
+
if (!name || !value) {
|
|
780
|
+
utils.print.error('Name and value required');
|
|
781
|
+
console.log(`${utils.COLORS.dim}Usage: bootspring monitor threshold <name> <value>${utils.COLORS.reset}`);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
setThreshold(projectRoot, name, value);
|
|
785
|
+
break;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
case 'post-deploy':
|
|
789
|
+
await runPostDeploy(projectRoot, {
|
|
790
|
+
target: parsedArgs.target
|
|
791
|
+
});
|
|
792
|
+
break;
|
|
793
|
+
|
|
794
|
+
case 'help':
|
|
795
|
+
case '--help':
|
|
796
|
+
case '-h':
|
|
797
|
+
showHelp();
|
|
798
|
+
break;
|
|
799
|
+
|
|
800
|
+
default:
|
|
801
|
+
utils.print.error(`Unknown command: ${subcommand}`);
|
|
802
|
+
showHelp();
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
module.exports = {
|
|
807
|
+
run,
|
|
808
|
+
MONITORING_PROVIDERS,
|
|
809
|
+
ALERT_PROVIDERS,
|
|
810
|
+
collectMetrics,
|
|
811
|
+
runPostDeploy
|
|
812
|
+
};
|