@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/project.js
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring Project Command
|
|
3
|
+
*
|
|
4
|
+
* Manage projects from the CLI.
|
|
5
|
+
*
|
|
6
|
+
* @package bootspring
|
|
7
|
+
* @module cli/project
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const https = require('https');
|
|
11
|
+
const http = require('http');
|
|
12
|
+
const auth = require('../core/auth');
|
|
13
|
+
const session = require('../core/session');
|
|
14
|
+
const readline = require('readline');
|
|
15
|
+
|
|
16
|
+
const API_BASE = process.env.BOOTSPRING_API_URL || 'https://www.bootspring.com';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Make direct API request (without v1 prefix)
|
|
20
|
+
*/
|
|
21
|
+
async function apiRequest(method, path, data = null) {
|
|
22
|
+
const token = auth.getToken();
|
|
23
|
+
const apiKey = auth.getApiKey();
|
|
24
|
+
const url = new URL(`/api${path}`, API_BASE);
|
|
25
|
+
const isHttps = url.protocol === 'https:';
|
|
26
|
+
const httpModule = isHttps ? https : http;
|
|
27
|
+
|
|
28
|
+
const headers = {
|
|
29
|
+
'Content-Type': 'application/json',
|
|
30
|
+
'User-Agent': `bootspring-cli/${require('../package.json').version}`,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Use API key if available, otherwise use JWT token
|
|
34
|
+
if (apiKey) {
|
|
35
|
+
headers['X-API-Key'] = apiKey;
|
|
36
|
+
} else if (token) {
|
|
37
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const req = httpModule.request(url, {
|
|
42
|
+
method,
|
|
43
|
+
headers,
|
|
44
|
+
timeout: 30000
|
|
45
|
+
}, (res) => {
|
|
46
|
+
let body = '';
|
|
47
|
+
res.on('data', chunk => body += chunk);
|
|
48
|
+
res.on('end', () => {
|
|
49
|
+
try {
|
|
50
|
+
const json = JSON.parse(body);
|
|
51
|
+
if (res.statusCode >= 400) {
|
|
52
|
+
const error = new Error(json.message || json.error || 'API Error');
|
|
53
|
+
error.status = res.statusCode;
|
|
54
|
+
error.code = json.error || json.code;
|
|
55
|
+
reject(error);
|
|
56
|
+
} else {
|
|
57
|
+
resolve(json);
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
if (res.statusCode >= 400) {
|
|
61
|
+
const error = new Error(body || 'API Error');
|
|
62
|
+
error.status = res.statusCode;
|
|
63
|
+
reject(error);
|
|
64
|
+
} else {
|
|
65
|
+
resolve(body);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
req.on('error', reject);
|
|
72
|
+
req.on('timeout', () => {
|
|
73
|
+
req.destroy();
|
|
74
|
+
reject(new Error('Request timeout'));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (data) {
|
|
78
|
+
req.write(JSON.stringify(data));
|
|
79
|
+
}
|
|
80
|
+
req.end();
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const BRAND = {
|
|
85
|
+
name: 'Bootspring',
|
|
86
|
+
color: '\x1b[36m',
|
|
87
|
+
reset: '\x1b[0m',
|
|
88
|
+
bold: '\x1b[1m',
|
|
89
|
+
dim: '\x1b[2m',
|
|
90
|
+
green: '\x1b[32m',
|
|
91
|
+
yellow: '\x1b[33m',
|
|
92
|
+
red: '\x1b[31m'
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Prompt for input
|
|
97
|
+
*/
|
|
98
|
+
function prompt(question) {
|
|
99
|
+
const rl = readline.createInterface({
|
|
100
|
+
input: process.stdin,
|
|
101
|
+
output: process.stdout
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
rl.question(question, (answer) => {
|
|
106
|
+
rl.close();
|
|
107
|
+
resolve(answer.trim());
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Print a single project
|
|
114
|
+
*/
|
|
115
|
+
function printProject(project, currentProject, showOwner = false) {
|
|
116
|
+
const isCurrent = currentProject && currentProject.id === project.id;
|
|
117
|
+
const marker = isCurrent ? `${BRAND.green}→${BRAND.reset} ` : ' ';
|
|
118
|
+
const activeTag = project.isActive ? '' : ` ${BRAND.dim}(inactive)${BRAND.reset}`;
|
|
119
|
+
const roleTag = project.role && project.role !== 'owner' ? ` ${BRAND.dim}(${project.role})${BRAND.reset}` : '';
|
|
120
|
+
|
|
121
|
+
console.log(`${marker}${BRAND.color}${project.name}${BRAND.reset}${roleTag}${activeTag}`);
|
|
122
|
+
console.log(` ${BRAND.dim}ID: ${project.id}${BRAND.reset}`);
|
|
123
|
+
console.log(` ${BRAND.dim}Slug: ${project.slug}${BRAND.reset}`);
|
|
124
|
+
if (showOwner && project.owner) {
|
|
125
|
+
console.log(` ${BRAND.dim}Owner: ${project.owner.email}${BRAND.reset}`);
|
|
126
|
+
}
|
|
127
|
+
if (project.description) {
|
|
128
|
+
console.log(` ${BRAND.dim}${project.description}${BRAND.reset}`);
|
|
129
|
+
}
|
|
130
|
+
console.log(` ${BRAND.dim}API Keys: ${project.apiKeyCount}${BRAND.reset}`);
|
|
131
|
+
if (project.memberCount > 1) {
|
|
132
|
+
console.log(` ${BRAND.dim}Members: ${project.memberCount}${BRAND.reset}`);
|
|
133
|
+
}
|
|
134
|
+
console.log();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* List all projects (owned and shared)
|
|
139
|
+
*/
|
|
140
|
+
async function listProjects() {
|
|
141
|
+
if (!auth.isAuthenticated()) {
|
|
142
|
+
console.log(`${BRAND.red}Not logged in.${BRAND.reset} Run: bootspring auth login`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const response = await apiRequest('GET', '/projects?grouped=true');
|
|
148
|
+
const owned = response.owned || [];
|
|
149
|
+
const shared = response.shared || [];
|
|
150
|
+
|
|
151
|
+
if (owned.length === 0 && shared.length === 0) {
|
|
152
|
+
console.log(`\n${BRAND.dim}No projects found.${BRAND.reset}`);
|
|
153
|
+
console.log(`Create one with: ${BRAND.color}bootspring project create${BRAND.reset}\n`);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const currentProject = session.getEffectiveProject();
|
|
158
|
+
|
|
159
|
+
// Show owned projects
|
|
160
|
+
if (owned.length > 0) {
|
|
161
|
+
console.log(`\n${BRAND.bold}Your Projects${BRAND.reset}\n`);
|
|
162
|
+
for (const project of owned) {
|
|
163
|
+
printProject(project, currentProject, false);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Show shared projects
|
|
168
|
+
if (shared.length > 0) {
|
|
169
|
+
console.log(`${BRAND.bold}Shared With You${BRAND.reset}\n`);
|
|
170
|
+
for (const project of shared) {
|
|
171
|
+
printProject(project, currentProject, true);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
console.log(`${BRAND.dim}Limits: ${response.limits.current}/${response.limits.limit} projects${BRAND.reset}\n`);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.log(`${BRAND.red}Error: ${error.message}${BRAND.reset}`);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Parse flags from args
|
|
184
|
+
*/
|
|
185
|
+
function parseFlags(args) {
|
|
186
|
+
const flags = {};
|
|
187
|
+
const positional = [];
|
|
188
|
+
|
|
189
|
+
for (let i = 0; i < args.length; i++) {
|
|
190
|
+
const arg = args[i];
|
|
191
|
+
if (arg.startsWith('--')) {
|
|
192
|
+
const [key, ...valueParts] = arg.slice(2).split('=');
|
|
193
|
+
flags[key] = valueParts.length > 0 ? valueParts.join('=') : (args[i + 1] && !args[i + 1].startsWith('-') ? args[++i] : true);
|
|
194
|
+
} else if (arg.startsWith('-')) {
|
|
195
|
+
flags[arg.slice(1)] = args[i + 1] && !args[i + 1].startsWith('-') ? args[++i] : true;
|
|
196
|
+
} else {
|
|
197
|
+
positional.push(arg);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return { flags, positional };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Check for similar projects (duplicate detection)
|
|
206
|
+
*/
|
|
207
|
+
async function checkSimilarProjects(name, repoUrl) {
|
|
208
|
+
try {
|
|
209
|
+
const params = new URLSearchParams();
|
|
210
|
+
if (name) params.set('name', name);
|
|
211
|
+
if (repoUrl) params.set('repo', repoUrl);
|
|
212
|
+
|
|
213
|
+
const response = await apiRequest('GET', `/projects/similar?${params.toString()}`);
|
|
214
|
+
return response.matches || [];
|
|
215
|
+
} catch (error) {
|
|
216
|
+
// Don't block creation if similar check fails
|
|
217
|
+
console.log(`${BRAND.dim}(Could not check for similar projects)${BRAND.reset}`);
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Join an existing project as a member
|
|
224
|
+
*/
|
|
225
|
+
async function joinProject(projectId) {
|
|
226
|
+
try {
|
|
227
|
+
// Get current user's email to add themselves
|
|
228
|
+
// For now, we'll inform the user to request access
|
|
229
|
+
console.log(`\n${BRAND.yellow}To join this project, ask the owner to add you as a member.${BRAND.reset}`);
|
|
230
|
+
console.log(`${BRAND.dim}They can do this from the project settings in the dashboard.${BRAND.reset}\n`);
|
|
231
|
+
return false;
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.log(`${BRAND.red}Error joining project: ${error.message}${BRAND.reset}`);
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Create a new project
|
|
240
|
+
*/
|
|
241
|
+
async function createProject(args) {
|
|
242
|
+
if (!auth.isAuthenticated()) {
|
|
243
|
+
console.log(`${BRAND.red}Not logged in.${BRAND.reset} Run: bootspring auth login`);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const { flags, positional } = parseFlags(args);
|
|
248
|
+
const isNonInteractive = flags.y || flags.yes || !process.stdin.isTTY;
|
|
249
|
+
const skipDuplicateCheck = flags['skip-duplicate-check'] || flags.force;
|
|
250
|
+
|
|
251
|
+
console.log(`\n${BRAND.bold}Create New Project${BRAND.reset}\n`);
|
|
252
|
+
|
|
253
|
+
// Get project name from args or prompt
|
|
254
|
+
let name = positional[0] || flags.name;
|
|
255
|
+
if (!name && !isNonInteractive) {
|
|
256
|
+
name = await prompt(`${BRAND.color}Project name:${BRAND.reset} `);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!name || name.trim().length === 0) {
|
|
260
|
+
console.log(`${BRAND.red}Project name is required.${BRAND.reset}`);
|
|
261
|
+
console.log(`\n${BRAND.dim}Usage: bootspring project create <name> [--description "..."] [--framework nodejs] [-y]${BRAND.reset}\n`);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Get repository URL from flag
|
|
266
|
+
const repoUrl = flags.repo || flags.repository;
|
|
267
|
+
|
|
268
|
+
// Check for similar projects (duplicate detection)
|
|
269
|
+
if (!skipDuplicateCheck && !isNonInteractive) {
|
|
270
|
+
console.log(`${BRAND.dim}Checking for similar projects...${BRAND.reset}`);
|
|
271
|
+
const similar = await checkSimilarProjects(name.trim(), repoUrl);
|
|
272
|
+
|
|
273
|
+
if (similar.length > 0) {
|
|
274
|
+
console.log(`\n${BRAND.yellow}Similar project(s) found:${BRAND.reset}\n`);
|
|
275
|
+
|
|
276
|
+
for (const match of similar) {
|
|
277
|
+
const reasonMap = {
|
|
278
|
+
github_repo: 'GitHub repo match',
|
|
279
|
+
slug_match: 'Same slug',
|
|
280
|
+
name_similar: 'Similar name'
|
|
281
|
+
};
|
|
282
|
+
const reason = reasonMap[match.matchReason] || match.matchReason;
|
|
283
|
+
const ownerInfo = match.owner ? ` (owned by ${match.owner.email})` : '';
|
|
284
|
+
console.log(` ${BRAND.color}${match.name}${BRAND.reset}${ownerInfo}`);
|
|
285
|
+
console.log(` ${BRAND.dim}[${reason}]${BRAND.reset}\n`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const proceed = await prompt(`${BRAND.color}Create a new project anyway? (y/N):${BRAND.reset} `);
|
|
289
|
+
if (proceed.toLowerCase() !== 'y') {
|
|
290
|
+
console.log(`\n${BRAND.dim}To join an existing project, ask the owner to add you.${BRAND.reset}\n`);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
console.log();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Get description from flag or prompt
|
|
298
|
+
let description = flags.description || flags.d;
|
|
299
|
+
if (!description && !isNonInteractive) {
|
|
300
|
+
description = await prompt(`${BRAND.dim}Description (optional):${BRAND.reset} `);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Get framework from flag or prompt
|
|
304
|
+
let framework = flags.framework || flags.f;
|
|
305
|
+
if (!framework && !isNonInteractive) {
|
|
306
|
+
framework = await prompt(`${BRAND.dim}Framework (e.g., nextjs, react, nodejs):${BRAND.reset} `);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
console.log(`\n${BRAND.dim}Creating project...${BRAND.reset}`);
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const project = await apiRequest('POST', '/projects', {
|
|
313
|
+
name: name.trim(),
|
|
314
|
+
description: description || undefined,
|
|
315
|
+
framework: framework || undefined,
|
|
316
|
+
repositoryUrl: repoUrl || undefined
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
console.log(`\n${BRAND.green}✓${BRAND.reset} Project created successfully!\n`);
|
|
320
|
+
console.log(` ${BRAND.bold}Name:${BRAND.reset} ${project.name}`);
|
|
321
|
+
console.log(` ${BRAND.bold}ID:${BRAND.reset} ${project.id}`);
|
|
322
|
+
console.log(` ${BRAND.bold}Slug:${BRAND.reset} ${project.slug}`);
|
|
323
|
+
|
|
324
|
+
// Ask if user wants to switch to this project (auto-yes in non-interactive mode)
|
|
325
|
+
let switchTo = 'y';
|
|
326
|
+
if (!isNonInteractive) {
|
|
327
|
+
switchTo = await prompt(`\n${BRAND.color}Switch to this project now? (Y/n):${BRAND.reset} `);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (switchTo.toLowerCase() !== 'n') {
|
|
331
|
+
session.setCurrentProject({
|
|
332
|
+
id: project.id,
|
|
333
|
+
name: project.name,
|
|
334
|
+
slug: project.slug
|
|
335
|
+
});
|
|
336
|
+
console.log(`\n${BRAND.green}✓${BRAND.reset} Switched to ${BRAND.color}${project.name}${BRAND.reset}\n`);
|
|
337
|
+
} else {
|
|
338
|
+
console.log(`\n${BRAND.dim}Run 'bootspring switch ${project.slug}' to switch to this project.${BRAND.reset}\n`);
|
|
339
|
+
}
|
|
340
|
+
} catch (error) {
|
|
341
|
+
if (error.status === 403 && error.message.includes('limit')) {
|
|
342
|
+
console.log(`\n${BRAND.yellow}Project limit reached.${BRAND.reset}`);
|
|
343
|
+
console.log(`${BRAND.dim}Upgrade your plan at: https://bootspring.com/dashboard/billing${BRAND.reset}\n`);
|
|
344
|
+
} else {
|
|
345
|
+
console.log(`\n${BRAND.red}Error: ${error.message}${BRAND.reset}`);
|
|
346
|
+
}
|
|
347
|
+
process.exit(1);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Show current project info
|
|
353
|
+
*/
|
|
354
|
+
async function showProjectInfo() {
|
|
355
|
+
const project = session.getEffectiveProject();
|
|
356
|
+
|
|
357
|
+
if (!project) {
|
|
358
|
+
console.log(`\n${BRAND.yellow}No project context set.${BRAND.reset}`);
|
|
359
|
+
console.log(`${BRAND.dim}Run 'bootspring switch' to select a project.${BRAND.reset}\n`);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
console.log(`\n${BRAND.bold}Current Project${BRAND.reset}\n`);
|
|
364
|
+
console.log(` ${BRAND.bold}Name:${BRAND.reset} ${BRAND.color}${project.name}${BRAND.reset}`);
|
|
365
|
+
console.log(` ${BRAND.bold}ID:${BRAND.reset} ${project.id}`);
|
|
366
|
+
if (project.slug) {
|
|
367
|
+
console.log(` ${BRAND.bold}Slug:${BRAND.reset} ${project.slug}`);
|
|
368
|
+
}
|
|
369
|
+
console.log(` ${BRAND.bold}Source:${BRAND.reset} ${project.source || 'session'}`);
|
|
370
|
+
console.log();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Show usage for this command
|
|
375
|
+
*/
|
|
376
|
+
function showUsage() {
|
|
377
|
+
console.log(`
|
|
378
|
+
${BRAND.bold}Usage:${BRAND.reset} bootspring project <command> [args]
|
|
379
|
+
|
|
380
|
+
${BRAND.bold}Commands:${BRAND.reset}
|
|
381
|
+
list List all projects (owned and shared)
|
|
382
|
+
create [name] Create a new project
|
|
383
|
+
info Show current project info
|
|
384
|
+
|
|
385
|
+
${BRAND.bold}Options for create:${BRAND.reset}
|
|
386
|
+
--description, -d Project description
|
|
387
|
+
--framework, -f Project framework (e.g., nextjs, nodejs)
|
|
388
|
+
--repo Repository URL
|
|
389
|
+
--force Skip duplicate project check
|
|
390
|
+
-y, --yes Non-interactive mode
|
|
391
|
+
|
|
392
|
+
${BRAND.bold}Examples:${BRAND.reset}
|
|
393
|
+
bootspring project list
|
|
394
|
+
bootspring project create "My App"
|
|
395
|
+
bootspring project create "My App" --framework nextjs --repo https://github.com/user/repo
|
|
396
|
+
bootspring project info
|
|
397
|
+
`);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Main entry point
|
|
402
|
+
*/
|
|
403
|
+
async function run(args) {
|
|
404
|
+
const subcommand = args[0];
|
|
405
|
+
|
|
406
|
+
switch (subcommand) {
|
|
407
|
+
case 'list':
|
|
408
|
+
case 'ls':
|
|
409
|
+
await listProjects();
|
|
410
|
+
break;
|
|
411
|
+
|
|
412
|
+
case 'create':
|
|
413
|
+
case 'new':
|
|
414
|
+
await createProject(args.slice(1));
|
|
415
|
+
break;
|
|
416
|
+
|
|
417
|
+
case 'info':
|
|
418
|
+
case 'current':
|
|
419
|
+
await showProjectInfo();
|
|
420
|
+
break;
|
|
421
|
+
|
|
422
|
+
case undefined:
|
|
423
|
+
case 'help':
|
|
424
|
+
case '--help':
|
|
425
|
+
case '-h':
|
|
426
|
+
showUsage();
|
|
427
|
+
break;
|
|
428
|
+
|
|
429
|
+
default:
|
|
430
|
+
console.log(`${BRAND.red}Unknown subcommand: ${subcommand}${BRAND.reset}`);
|
|
431
|
+
showUsage();
|
|
432
|
+
process.exit(1);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
module.exports = { run };
|
package/cli/quality.js
CHANGED
|
@@ -12,6 +12,15 @@ const path = require('path');
|
|
|
12
12
|
const config = require('../core/config');
|
|
13
13
|
const utils = require('../core/utils');
|
|
14
14
|
|
|
15
|
+
// Lazy load audit workflow
|
|
16
|
+
let AuditWorkflowEngine = null;
|
|
17
|
+
function getAuditEngine() {
|
|
18
|
+
if (!AuditWorkflowEngine) {
|
|
19
|
+
AuditWorkflowEngine = require('../core/audit-workflow').AuditWorkflowEngine;
|
|
20
|
+
}
|
|
21
|
+
return AuditWorkflowEngine;
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
/**
|
|
16
25
|
* Detect project type and available tooling
|
|
17
26
|
* @param {string} projectRoot - Project root path
|
|
@@ -422,6 +431,217 @@ ${utils.COLORS.bold}Available Gates${utils.COLORS.reset}
|
|
|
422
431
|
console.log(` ${utils.COLORS.dim}npx husky add .husky/pre-commit "bootspring quality pre-commit"${utils.COLORS.reset}`);
|
|
423
432
|
}
|
|
424
433
|
|
|
434
|
+
/**
|
|
435
|
+
* Run full quality pipeline (quality gates + audit)
|
|
436
|
+
*/
|
|
437
|
+
async function runFullPipeline(options = {}) {
|
|
438
|
+
const cfg = config.load();
|
|
439
|
+
const projectRoot = cfg._projectRoot;
|
|
440
|
+
|
|
441
|
+
console.log(`
|
|
442
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Full Quality Pipeline${utils.COLORS.reset}
|
|
443
|
+
${utils.COLORS.dim}Quality gates + codebase audit${utils.COLORS.reset}
|
|
444
|
+
`);
|
|
445
|
+
|
|
446
|
+
const results = {
|
|
447
|
+
qualityGate: null,
|
|
448
|
+
audit: null,
|
|
449
|
+
timestamp: new Date().toISOString(),
|
|
450
|
+
passed: false
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
// Phase 1: Pre-deploy quality gate
|
|
454
|
+
utils.print.header('Phase 1: Quality Gates');
|
|
455
|
+
results.qualityGate = await runGate('pre-deploy', {
|
|
456
|
+
strict: false,
|
|
457
|
+
skip: options.skip ? [options.skip] : []
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
if (results.qualityGate.failed > 0 && options.strict) {
|
|
461
|
+
utils.print.error('Quality gates failed. Stopping pipeline.');
|
|
462
|
+
return results;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
console.log('');
|
|
466
|
+
|
|
467
|
+
// Phase 2: Bootspring audit
|
|
468
|
+
utils.print.header('Phase 2: Codebase Audit');
|
|
469
|
+
|
|
470
|
+
try {
|
|
471
|
+
const Engine = getAuditEngine();
|
|
472
|
+
const workflow = new Engine(projectRoot, { ciMode: true });
|
|
473
|
+
|
|
474
|
+
if (workflow.hasWorkflow()) {
|
|
475
|
+
workflow.loadState();
|
|
476
|
+
const progress = workflow.getProgress();
|
|
477
|
+
if (progress.isComplete) {
|
|
478
|
+
console.log(`${utils.COLORS.dim}Using existing audit results${utils.COLORS.reset}`);
|
|
479
|
+
results.audit = {
|
|
480
|
+
findings: progress.findings,
|
|
481
|
+
passed: progress.findings.critical === 0 && progress.findings.high === 0
|
|
482
|
+
};
|
|
483
|
+
} else {
|
|
484
|
+
// Reset and run fresh
|
|
485
|
+
workflow.resetWorkflow();
|
|
486
|
+
workflow.initializeWorkflow();
|
|
487
|
+
results.audit = await runAuditPhases(workflow);
|
|
488
|
+
}
|
|
489
|
+
} else {
|
|
490
|
+
workflow.initializeWorkflow();
|
|
491
|
+
results.audit = await runAuditPhases(workflow);
|
|
492
|
+
}
|
|
493
|
+
} catch (error) {
|
|
494
|
+
console.log(`${utils.COLORS.yellow}⚠${utils.COLORS.reset} Audit skipped: ${error.message}`);
|
|
495
|
+
results.audit = { skipped: true, error: error.message };
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
console.log('');
|
|
499
|
+
|
|
500
|
+
// Phase 3: Generate combined report
|
|
501
|
+
utils.print.header('Combined Quality Report');
|
|
502
|
+
results.passed = generateCombinedReport(results, projectRoot);
|
|
503
|
+
|
|
504
|
+
return results;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Run audit phases
|
|
509
|
+
*/
|
|
510
|
+
async function runAuditPhases(workflow) {
|
|
511
|
+
const phases = ['quality', 'security', 'practices'];
|
|
512
|
+
const findings = { critical: 0, high: 0, medium: 0, low: 0, total: 0 };
|
|
513
|
+
|
|
514
|
+
for (const phaseId of phases) {
|
|
515
|
+
workflow.startPhase(phaseId);
|
|
516
|
+
const spinner = utils.createSpinner(`Auditing: ${phaseId}`).start();
|
|
517
|
+
|
|
518
|
+
try {
|
|
519
|
+
let result;
|
|
520
|
+
switch (phaseId) {
|
|
521
|
+
case 'quality':
|
|
522
|
+
result = await workflow.runQualityMetrics();
|
|
523
|
+
spinner.succeed(`Quality: Score ${result.score}/100`);
|
|
524
|
+
break;
|
|
525
|
+
case 'security':
|
|
526
|
+
result = await workflow.runSecurityScan();
|
|
527
|
+
if (result.critical > 0) {
|
|
528
|
+
spinner.fail(`Security: ${result.critical} critical issues`);
|
|
529
|
+
findings.critical += result.critical;
|
|
530
|
+
} else if (result.high > 0) {
|
|
531
|
+
spinner.warn(`Security: ${result.high} high issues`);
|
|
532
|
+
findings.high += result.high;
|
|
533
|
+
} else {
|
|
534
|
+
spinner.succeed('Security: Passed');
|
|
535
|
+
}
|
|
536
|
+
findings.medium += result.medium || 0;
|
|
537
|
+
findings.low += result.low || 0;
|
|
538
|
+
break;
|
|
539
|
+
case 'practices':
|
|
540
|
+
result = await workflow.runBestPractices();
|
|
541
|
+
spinner.succeed(`Best practices: ${result.passed}/${result.total} passed`);
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
workflow.completePhase(phaseId, result);
|
|
545
|
+
} catch (error) {
|
|
546
|
+
spinner.fail(`${phaseId}: ${error.message}`);
|
|
547
|
+
workflow.failPhase(phaseId, error.message);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
findings.total = findings.critical + findings.high + findings.medium + findings.low;
|
|
552
|
+
|
|
553
|
+
return {
|
|
554
|
+
findings,
|
|
555
|
+
passed: findings.critical === 0 && findings.high === 0
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Generate combined quality report
|
|
561
|
+
*/
|
|
562
|
+
function generateCombinedReport(results, projectRoot) {
|
|
563
|
+
const reportDir = path.join(projectRoot, '.bootspring', 'quality');
|
|
564
|
+
if (!fs.existsSync(reportDir)) {
|
|
565
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Calculate overall status
|
|
569
|
+
const qualityPassed = results.qualityGate?.failed === 0;
|
|
570
|
+
const auditPassed = results.audit?.passed !== false;
|
|
571
|
+
const overallPassed = qualityPassed && auditPassed;
|
|
572
|
+
|
|
573
|
+
// Display summary
|
|
574
|
+
console.log('');
|
|
575
|
+
console.log(`${utils.COLORS.bold}Summary${utils.COLORS.reset}`);
|
|
576
|
+
console.log('');
|
|
577
|
+
|
|
578
|
+
// Quality gates
|
|
579
|
+
if (results.qualityGate) {
|
|
580
|
+
const qIcon = qualityPassed ?
|
|
581
|
+
`${utils.COLORS.green}✓${utils.COLORS.reset}` :
|
|
582
|
+
`${utils.COLORS.red}✗${utils.COLORS.reset}`;
|
|
583
|
+
console.log(` ${qIcon} Quality Gates: ${results.qualityGate.passed} passed, ${results.qualityGate.failed} failed`);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Audit
|
|
587
|
+
if (results.audit && !results.audit.skipped) {
|
|
588
|
+
const aIcon = auditPassed ?
|
|
589
|
+
`${utils.COLORS.green}✓${utils.COLORS.reset}` :
|
|
590
|
+
`${utils.COLORS.red}✗${utils.COLORS.reset}`;
|
|
591
|
+
const f = results.audit.findings;
|
|
592
|
+
console.log(` ${aIcon} Codebase Audit: ${f.critical} critical, ${f.high} high, ${f.medium} medium, ${f.low} low`);
|
|
593
|
+
} else if (results.audit?.skipped) {
|
|
594
|
+
console.log(` ${utils.COLORS.yellow}○${utils.COLORS.reset} Codebase Audit: Skipped`);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
console.log('');
|
|
598
|
+
|
|
599
|
+
// Overall result
|
|
600
|
+
if (overallPassed) {
|
|
601
|
+
utils.print.success('Quality pipeline PASSED');
|
|
602
|
+
} else {
|
|
603
|
+
utils.print.error('Quality pipeline FAILED');
|
|
604
|
+
if (!qualityPassed) {
|
|
605
|
+
console.log(` ${utils.COLORS.dim}Fix quality gate issues before proceeding${utils.COLORS.reset}`);
|
|
606
|
+
}
|
|
607
|
+
if (!auditPassed && results.audit?.findings) {
|
|
608
|
+
if (results.audit.findings.critical > 0) {
|
|
609
|
+
console.log(` ${utils.COLORS.red}● ${results.audit.findings.critical} critical issues require immediate attention${utils.COLORS.reset}`);
|
|
610
|
+
}
|
|
611
|
+
if (results.audit.findings.high > 0) {
|
|
612
|
+
console.log(` ${utils.COLORS.yellow}● ${results.audit.findings.high} high priority issues found${utils.COLORS.reset}`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Generate report file
|
|
618
|
+
const report = {
|
|
619
|
+
timestamp: results.timestamp,
|
|
620
|
+
passed: overallPassed,
|
|
621
|
+
qualityGates: {
|
|
622
|
+
passed: qualityPassed,
|
|
623
|
+
checks: results.qualityGate?.results || []
|
|
624
|
+
},
|
|
625
|
+
audit: results.audit?.skipped ? { skipped: true } : {
|
|
626
|
+
passed: auditPassed,
|
|
627
|
+
findings: results.audit?.findings || {}
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
const reportPath = path.join(reportDir, 'pipeline-report.json');
|
|
632
|
+
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
|
633
|
+
|
|
634
|
+
console.log('');
|
|
635
|
+
console.log(`${utils.COLORS.dim}Report saved: ${reportPath}${utils.COLORS.reset}`);
|
|
636
|
+
|
|
637
|
+
// Set exit code for CI
|
|
638
|
+
if (!overallPassed) {
|
|
639
|
+
process.exitCode = 1;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return overallPassed;
|
|
643
|
+
}
|
|
644
|
+
|
|
425
645
|
/**
|
|
426
646
|
* Show quality help
|
|
427
647
|
*/
|
|
@@ -437,10 +657,12 @@ ${utils.COLORS.bold}Gates:${utils.COLORS.reset}
|
|
|
437
657
|
${utils.COLORS.cyan}pre-commit${utils.COLORS.reset} Quick checks (types, lint, format)
|
|
438
658
|
${utils.COLORS.cyan}pre-push${utils.COLORS.reset} Thorough checks (tests, build)
|
|
439
659
|
${utils.COLORS.cyan}pre-deploy${utils.COLORS.reset} Full audit (security, everything)
|
|
660
|
+
${utils.COLORS.cyan}full${utils.COLORS.reset} Combined: quality gates + codebase audit
|
|
440
661
|
|
|
441
662
|
${utils.COLORS.bold}Options:${utils.COLORS.reset}
|
|
442
663
|
--strict Stop on first failure
|
|
443
664
|
--skip <check> Skip specific check
|
|
665
|
+
--ci CI mode with exit codes
|
|
444
666
|
|
|
445
667
|
${utils.COLORS.bold}Commands:${utils.COLORS.reset}
|
|
446
668
|
${utils.COLORS.cyan}quick${utils.COLORS.reset} Run quick type check only
|
|
@@ -450,6 +672,8 @@ ${utils.COLORS.bold}Examples:${utils.COLORS.reset}
|
|
|
450
672
|
bootspring quality pre-commit
|
|
451
673
|
bootspring quality pre-push --strict
|
|
452
674
|
bootspring quality pre-deploy --skip security
|
|
675
|
+
bootspring quality full
|
|
676
|
+
bootspring quality full --ci
|
|
453
677
|
bootspring quality quick
|
|
454
678
|
bootspring quality status
|
|
455
679
|
`);
|
|
@@ -477,6 +701,15 @@ async function run(args) {
|
|
|
477
701
|
});
|
|
478
702
|
break;
|
|
479
703
|
|
|
704
|
+
case 'full':
|
|
705
|
+
case 'pipeline':
|
|
706
|
+
await runFullPipeline({
|
|
707
|
+
strict: parsedArgs.strict,
|
|
708
|
+
skip: parsedArgs.skip,
|
|
709
|
+
ci: parsedArgs.ci
|
|
710
|
+
});
|
|
711
|
+
break;
|
|
712
|
+
|
|
480
713
|
case 'quick':
|
|
481
714
|
case 'check':
|
|
482
715
|
runQuickCheck();
|