@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/workspace.js
ADDED
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring Workspace Command
|
|
3
|
+
* Multi-project workspace management and cross-project workflows
|
|
4
|
+
*
|
|
5
|
+
* @package bootspring
|
|
6
|
+
* @command workspace
|
|
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
|
+
const telemetry = require('../core/telemetry');
|
|
15
|
+
|
|
16
|
+
// Colors
|
|
17
|
+
const c = {
|
|
18
|
+
reset: '\x1b[0m',
|
|
19
|
+
bold: '\x1b[1m',
|
|
20
|
+
dim: '\x1b[2m',
|
|
21
|
+
green: '\x1b[32m',
|
|
22
|
+
blue: '\x1b[34m',
|
|
23
|
+
yellow: '\x1b[33m',
|
|
24
|
+
cyan: '\x1b[36m',
|
|
25
|
+
red: '\x1b[31m',
|
|
26
|
+
magenta: '\x1b[35m'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Workspace config location
|
|
30
|
+
const WORKSPACE_DIR = '.bootspring-workspace';
|
|
31
|
+
const WORKSPACE_FILE = 'workspace.json';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Workspace class for managing multiple projects
|
|
35
|
+
*/
|
|
36
|
+
class Workspace {
|
|
37
|
+
constructor(workspaceRoot) {
|
|
38
|
+
this.workspaceRoot = workspaceRoot;
|
|
39
|
+
this.configPath = path.join(workspaceRoot, WORKSPACE_DIR, WORKSPACE_FILE);
|
|
40
|
+
this.config = this.loadConfig();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
loadConfig() {
|
|
44
|
+
try {
|
|
45
|
+
if (fs.existsSync(this.configPath)) {
|
|
46
|
+
return JSON.parse(fs.readFileSync(this.configPath, 'utf-8'));
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
// Return defaults
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
name: path.basename(this.workspaceRoot),
|
|
54
|
+
version: '1.0.0',
|
|
55
|
+
projects: [],
|
|
56
|
+
settings: {
|
|
57
|
+
parallelWorkflows: true,
|
|
58
|
+
sharedConfig: false,
|
|
59
|
+
crossProjectDeps: true
|
|
60
|
+
},
|
|
61
|
+
createdAt: new Date().toISOString(),
|
|
62
|
+
lastUpdated: new Date().toISOString()
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
save() {
|
|
67
|
+
const dir = path.dirname(this.configPath);
|
|
68
|
+
if (!fs.existsSync(dir)) {
|
|
69
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.config.lastUpdated = new Date().toISOString();
|
|
73
|
+
fs.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Initialize workspace
|
|
78
|
+
*/
|
|
79
|
+
init(name) {
|
|
80
|
+
this.config.name = name || this.config.name;
|
|
81
|
+
this.config.createdAt = new Date().toISOString();
|
|
82
|
+
this.save();
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
path: this.configPath,
|
|
86
|
+
name: this.config.name
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Add a project to the workspace
|
|
92
|
+
*/
|
|
93
|
+
addProject(projectPath, options = {}) {
|
|
94
|
+
const absolutePath = path.resolve(this.workspaceRoot, projectPath);
|
|
95
|
+
|
|
96
|
+
// Validate project exists
|
|
97
|
+
if (!fs.existsSync(absolutePath)) {
|
|
98
|
+
throw new Error(`Project path does not exist: ${absolutePath}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check if it's a valid bootspring project
|
|
102
|
+
const bootspringConfig = path.join(absolutePath, 'bootspring.config.js');
|
|
103
|
+
const packageJson = path.join(absolutePath, 'package.json');
|
|
104
|
+
|
|
105
|
+
const isBootspring = fs.existsSync(bootspringConfig);
|
|
106
|
+
const hasPackageJson = fs.existsSync(packageJson);
|
|
107
|
+
|
|
108
|
+
// Get project name
|
|
109
|
+
let projectName = options.name;
|
|
110
|
+
if (!projectName && hasPackageJson) {
|
|
111
|
+
try {
|
|
112
|
+
const pkg = JSON.parse(fs.readFileSync(packageJson, 'utf-8'));
|
|
113
|
+
projectName = pkg.name;
|
|
114
|
+
} catch {
|
|
115
|
+
projectName = path.basename(absolutePath);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
projectName = projectName || path.basename(absolutePath);
|
|
119
|
+
|
|
120
|
+
// Check for duplicates
|
|
121
|
+
const existing = this.config.projects.find(
|
|
122
|
+
p => p.path === projectPath || p.name === projectName
|
|
123
|
+
);
|
|
124
|
+
if (existing) {
|
|
125
|
+
throw new Error(`Project already in workspace: ${existing.name}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Add project
|
|
129
|
+
const project = {
|
|
130
|
+
name: projectName,
|
|
131
|
+
path: projectPath,
|
|
132
|
+
absolutePath,
|
|
133
|
+
isBootspring,
|
|
134
|
+
tags: options.tags || [],
|
|
135
|
+
addedAt: new Date().toISOString()
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
this.config.projects.push(project);
|
|
139
|
+
this.save();
|
|
140
|
+
|
|
141
|
+
return project;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Remove a project from workspace
|
|
146
|
+
*/
|
|
147
|
+
removeProject(nameOrPath) {
|
|
148
|
+
const index = this.config.projects.findIndex(
|
|
149
|
+
p => p.name === nameOrPath || p.path === nameOrPath
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
if (index === -1) {
|
|
153
|
+
throw new Error(`Project not found: ${nameOrPath}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const removed = this.config.projects.splice(index, 1)[0];
|
|
157
|
+
this.save();
|
|
158
|
+
|
|
159
|
+
return removed;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* List all projects
|
|
164
|
+
*/
|
|
165
|
+
listProjects() {
|
|
166
|
+
return this.config.projects.map(p => ({
|
|
167
|
+
...p,
|
|
168
|
+
exists: fs.existsSync(p.absolutePath),
|
|
169
|
+
health: this.getProjectHealth(p)
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get project health summary
|
|
175
|
+
*/
|
|
176
|
+
getProjectHealth(project) {
|
|
177
|
+
try {
|
|
178
|
+
const healthPath = path.join(project.absolutePath, '.bootspring', 'health.json');
|
|
179
|
+
if (fs.existsSync(healthPath)) {
|
|
180
|
+
const health = JSON.parse(fs.readFileSync(healthPath, 'utf-8'));
|
|
181
|
+
return health.score || 0;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Calculate basic health
|
|
185
|
+
const todoPath = path.join(project.absolutePath, 'todo.md');
|
|
186
|
+
const claudePath = path.join(project.absolutePath, 'CLAUDE.md');
|
|
187
|
+
|
|
188
|
+
let score = 50; // Base score
|
|
189
|
+
if (fs.existsSync(claudePath)) score += 20;
|
|
190
|
+
if (fs.existsSync(todoPath)) score += 10;
|
|
191
|
+
if (project.isBootspring) score += 20;
|
|
192
|
+
|
|
193
|
+
return Math.min(100, score);
|
|
194
|
+
} catch {
|
|
195
|
+
return 0;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Run a workflow across all projects
|
|
201
|
+
*/
|
|
202
|
+
async runWorkflow(workflowName, options = {}) {
|
|
203
|
+
const projects = this.listProjects().filter(p => p.exists);
|
|
204
|
+
const results = [];
|
|
205
|
+
|
|
206
|
+
if (projects.length === 0) {
|
|
207
|
+
throw new Error('No valid projects in workspace');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const filteredProjects = options.filter
|
|
211
|
+
? projects.filter(p => p.tags?.includes(options.filter) || p.name.includes(options.filter))
|
|
212
|
+
: projects;
|
|
213
|
+
|
|
214
|
+
if (this.config.settings.parallelWorkflows && !options.sequential) {
|
|
215
|
+
// Run in parallel
|
|
216
|
+
const promises = filteredProjects.map(project =>
|
|
217
|
+
this.runWorkflowForProject(project, workflowName, options)
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const settled = await Promise.allSettled(promises);
|
|
221
|
+
|
|
222
|
+
for (let i = 0; i < settled.length; i++) {
|
|
223
|
+
results.push({
|
|
224
|
+
project: filteredProjects[i].name,
|
|
225
|
+
...settled[i]
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
// Run sequentially
|
|
230
|
+
for (const project of filteredProjects) {
|
|
231
|
+
try {
|
|
232
|
+
const result = await this.runWorkflowForProject(project, workflowName, options);
|
|
233
|
+
results.push({ project: project.name, status: 'fulfilled', value: result });
|
|
234
|
+
} catch (error) {
|
|
235
|
+
results.push({ project: project.name, status: 'rejected', reason: error });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return results;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Run workflow for a single project
|
|
245
|
+
*/
|
|
246
|
+
async runWorkflowForProject(project, workflowName, options = {}) {
|
|
247
|
+
const command = `bootspring ${workflowName}`;
|
|
248
|
+
|
|
249
|
+
return new Promise((resolve, reject) => {
|
|
250
|
+
try {
|
|
251
|
+
const output = execSync(command, {
|
|
252
|
+
cwd: project.absolutePath,
|
|
253
|
+
encoding: 'utf-8',
|
|
254
|
+
timeout: options.timeout || 300000, // 5 minute default
|
|
255
|
+
stdio: options.verbose ? 'inherit' : 'pipe'
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
resolve({ success: true, output });
|
|
259
|
+
} catch (error) {
|
|
260
|
+
reject(error);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get workspace summary
|
|
267
|
+
*/
|
|
268
|
+
getSummary() {
|
|
269
|
+
const projects = this.listProjects();
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
name: this.config.name,
|
|
273
|
+
projectCount: projects.length,
|
|
274
|
+
healthyProjects: projects.filter(p => p.health >= 70).length,
|
|
275
|
+
bootspringProjects: projects.filter(p => p.isBootspring).length,
|
|
276
|
+
missingProjects: projects.filter(p => !p.exists).length,
|
|
277
|
+
averageHealth: projects.length > 0
|
|
278
|
+
? Math.round(projects.reduce((sum, p) => sum + p.health, 0) / projects.length)
|
|
279
|
+
: 0,
|
|
280
|
+
lastUpdated: this.config.lastUpdated
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Export workspace config
|
|
286
|
+
*/
|
|
287
|
+
export() {
|
|
288
|
+
return {
|
|
289
|
+
...this.config,
|
|
290
|
+
projects: this.listProjects()
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Find workspace root
|
|
297
|
+
*/
|
|
298
|
+
function findWorkspaceRoot(startPath = process.cwd()) {
|
|
299
|
+
let current = startPath;
|
|
300
|
+
|
|
301
|
+
while (current !== path.dirname(current)) {
|
|
302
|
+
const workspaceDir = path.join(current, WORKSPACE_DIR);
|
|
303
|
+
if (fs.existsSync(workspaceDir)) {
|
|
304
|
+
return current;
|
|
305
|
+
}
|
|
306
|
+
current = path.dirname(current);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return startPath; // Use start path if no workspace found
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Initialize workspace
|
|
314
|
+
*/
|
|
315
|
+
function initWorkspace(name, options = {}) {
|
|
316
|
+
const workspaceRoot = options.path || process.cwd();
|
|
317
|
+
const workspace = new Workspace(workspaceRoot);
|
|
318
|
+
const result = workspace.init(name);
|
|
319
|
+
|
|
320
|
+
console.log(`
|
|
321
|
+
${c.green}✓${c.reset} Workspace initialized: ${c.bold}${result.name}${c.reset}
|
|
322
|
+
${c.dim}Config: ${result.path}${c.reset}
|
|
323
|
+
|
|
324
|
+
${c.bold}Next steps:${c.reset}
|
|
325
|
+
1. Add projects: ${c.cyan}bootspring workspace add ./project-a${c.reset}
|
|
326
|
+
2. List projects: ${c.cyan}bootspring workspace list${c.reset}
|
|
327
|
+
3. Run workflows: ${c.cyan}bootspring workspace run analyze${c.reset}
|
|
328
|
+
`);
|
|
329
|
+
|
|
330
|
+
telemetry.emitEvent('workspace:init', { name: result.name });
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Add project to workspace
|
|
335
|
+
*/
|
|
336
|
+
function addProject(projectPath, options = {}) {
|
|
337
|
+
const workspaceRoot = findWorkspaceRoot();
|
|
338
|
+
const workspace = new Workspace(workspaceRoot);
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
const project = workspace.addProject(projectPath, options);
|
|
342
|
+
|
|
343
|
+
console.log(`
|
|
344
|
+
${c.green}✓${c.reset} Added project: ${c.bold}${project.name}${c.reset}
|
|
345
|
+
${c.dim}Path: ${project.absolutePath}${c.reset}
|
|
346
|
+
${c.dim}Bootspring: ${project.isBootspring ? 'Yes' : 'No'}${c.reset}
|
|
347
|
+
`);
|
|
348
|
+
|
|
349
|
+
telemetry.emitEvent('workspace:add', { project: project.name });
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.log(`${c.red}Error:${c.reset} ${error.message}`);
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Remove project from workspace
|
|
358
|
+
*/
|
|
359
|
+
function removeProject(nameOrPath) {
|
|
360
|
+
const workspaceRoot = findWorkspaceRoot();
|
|
361
|
+
const workspace = new Workspace(workspaceRoot);
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
const removed = workspace.removeProject(nameOrPath);
|
|
365
|
+
console.log(`${c.green}✓${c.reset} Removed project: ${c.bold}${removed.name}${c.reset}`);
|
|
366
|
+
|
|
367
|
+
telemetry.emitEvent('workspace:remove', { project: removed.name });
|
|
368
|
+
} catch (error) {
|
|
369
|
+
console.log(`${c.red}Error:${c.reset} ${error.message}`);
|
|
370
|
+
process.exit(1);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* List projects in workspace
|
|
376
|
+
*/
|
|
377
|
+
function listProjects(options = {}) {
|
|
378
|
+
const workspaceRoot = findWorkspaceRoot();
|
|
379
|
+
const workspace = new Workspace(workspaceRoot);
|
|
380
|
+
const summary = workspace.getSummary();
|
|
381
|
+
const projects = workspace.listProjects();
|
|
382
|
+
|
|
383
|
+
if (options.json) {
|
|
384
|
+
console.log(JSON.stringify(workspace.export(), null, 2));
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
console.log(`
|
|
389
|
+
${c.cyan}${c.bold}⚡ Workspace: ${summary.name}${c.reset}
|
|
390
|
+
${c.dim}Projects: ${summary.projectCount} | Healthy: ${summary.healthyProjects} | Avg Health: ${summary.averageHealth}%${c.reset}
|
|
391
|
+
`);
|
|
392
|
+
|
|
393
|
+
if (projects.length === 0) {
|
|
394
|
+
console.log(`${c.dim}No projects in workspace. Add one with:${c.reset}`);
|
|
395
|
+
console.log(' bootspring workspace add ./path-to-project');
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
console.log(`${c.bold}Projects${c.reset}\n`);
|
|
400
|
+
|
|
401
|
+
for (const project of projects) {
|
|
402
|
+
const statusIcon = !project.exists ? `${c.red}✗${c.reset}` :
|
|
403
|
+
project.health >= 70 ? `${c.green}●${c.reset}` :
|
|
404
|
+
project.health >= 50 ? `${c.yellow}●${c.reset}` :
|
|
405
|
+
`${c.red}●${c.reset}`;
|
|
406
|
+
|
|
407
|
+
const bootspringBadge = project.isBootspring ? `${c.cyan}[bs]${c.reset}` : '';
|
|
408
|
+
|
|
409
|
+
console.log(` ${statusIcon} ${c.bold}${project.name}${c.reset} ${bootspringBadge}`);
|
|
410
|
+
console.log(` ${c.dim}${project.path}${c.reset}`);
|
|
411
|
+
console.log(` ${c.dim}Health: ${project.health}%${c.reset}`);
|
|
412
|
+
|
|
413
|
+
if (project.tags?.length > 0) {
|
|
414
|
+
console.log(` ${c.dim}Tags: ${project.tags.join(', ')}${c.reset}`);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
console.log();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Run workflow across workspace
|
|
423
|
+
*/
|
|
424
|
+
async function runWorkflow(workflowName, options = {}) {
|
|
425
|
+
const workspaceRoot = findWorkspaceRoot();
|
|
426
|
+
const workspace = new Workspace(workspaceRoot);
|
|
427
|
+
|
|
428
|
+
console.log(`
|
|
429
|
+
${c.cyan}${c.bold}⚡ Running: ${workflowName}${c.reset}
|
|
430
|
+
${c.dim}Workspace: ${workspace.config.name}${c.reset}
|
|
431
|
+
`);
|
|
432
|
+
|
|
433
|
+
try {
|
|
434
|
+
const results = await workspace.runWorkflow(workflowName, options);
|
|
435
|
+
|
|
436
|
+
console.log(`\n${c.bold}Results${c.reset}\n`);
|
|
437
|
+
|
|
438
|
+
let successCount = 0;
|
|
439
|
+
let failCount = 0;
|
|
440
|
+
|
|
441
|
+
for (const result of results) {
|
|
442
|
+
if (result.status === 'fulfilled') {
|
|
443
|
+
console.log(` ${c.green}✓${c.reset} ${result.project}`);
|
|
444
|
+
successCount++;
|
|
445
|
+
} else {
|
|
446
|
+
console.log(` ${c.red}✗${c.reset} ${result.project}`);
|
|
447
|
+
if (options.verbose) {
|
|
448
|
+
console.log(` ${c.dim}${result.reason?.message || 'Unknown error'}${c.reset}`);
|
|
449
|
+
}
|
|
450
|
+
failCount++;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
console.log(`
|
|
455
|
+
${c.bold}Summary${c.reset}
|
|
456
|
+
Success: ${c.green}${successCount}${c.reset}
|
|
457
|
+
Failed: ${c.red}${failCount}${c.reset}
|
|
458
|
+
`);
|
|
459
|
+
|
|
460
|
+
telemetry.emitEvent('workspace:run', {
|
|
461
|
+
workflow: workflowName,
|
|
462
|
+
success: successCount,
|
|
463
|
+
failed: failCount
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
} catch (error) {
|
|
467
|
+
console.log(`${c.red}Error:${c.reset} ${error.message}`);
|
|
468
|
+
process.exit(1);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Show workspace status
|
|
474
|
+
*/
|
|
475
|
+
function showStatus() {
|
|
476
|
+
const workspaceRoot = findWorkspaceRoot();
|
|
477
|
+
const workspace = new Workspace(workspaceRoot);
|
|
478
|
+
const summary = workspace.getSummary();
|
|
479
|
+
|
|
480
|
+
console.log(`
|
|
481
|
+
${c.cyan}${c.bold}⚡ Workspace Status${c.reset}
|
|
482
|
+
|
|
483
|
+
${c.bold}Name${c.reset}: ${summary.name}
|
|
484
|
+
${c.bold}Projects${c.reset}: ${summary.projectCount}
|
|
485
|
+
${c.bold}Healthy${c.reset}: ${summary.healthyProjects}/${summary.projectCount}
|
|
486
|
+
${c.bold}Health${c.reset}: ${summary.averageHealth}%
|
|
487
|
+
${c.bold}Updated${c.reset}: ${summary.lastUpdated}
|
|
488
|
+
|
|
489
|
+
${c.bold}Settings${c.reset}
|
|
490
|
+
Parallel Workflows: ${workspace.config.settings.parallelWorkflows ? 'Yes' : 'No'}
|
|
491
|
+
Shared Config: ${workspace.config.settings.sharedConfig ? 'Yes' : 'No'}
|
|
492
|
+
Cross-Project Deps: ${workspace.config.settings.crossProjectDeps ? 'Yes' : 'No'}
|
|
493
|
+
`);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Show help
|
|
498
|
+
*/
|
|
499
|
+
function showHelp() {
|
|
500
|
+
console.log(`
|
|
501
|
+
${c.cyan}${c.bold}⚡ Bootspring Workspace${c.reset}
|
|
502
|
+
${c.dim}Multi-project workspace management${c.reset}
|
|
503
|
+
|
|
504
|
+
${c.bold}Usage:${c.reset}
|
|
505
|
+
bootspring workspace <command> [options]
|
|
506
|
+
|
|
507
|
+
${c.bold}Commands:${c.reset}
|
|
508
|
+
${c.cyan}init [name]${c.reset} Initialize workspace in current directory
|
|
509
|
+
${c.cyan}add <path>${c.reset} Add a project to workspace
|
|
510
|
+
${c.cyan}remove <name>${c.reset} Remove a project from workspace
|
|
511
|
+
${c.cyan}list${c.reset} List all projects
|
|
512
|
+
${c.cyan}status${c.reset} Show workspace status
|
|
513
|
+
${c.cyan}run <workflow>${c.reset} Run workflow across all projects
|
|
514
|
+
|
|
515
|
+
${c.bold}Options:${c.reset}
|
|
516
|
+
--name <name> Project name (for add)
|
|
517
|
+
--tags <tags> Comma-separated tags (for add)
|
|
518
|
+
--filter <pattern> Filter projects (for run)
|
|
519
|
+
--sequential Run workflows sequentially
|
|
520
|
+
--verbose Show detailed output
|
|
521
|
+
--json Output as JSON
|
|
522
|
+
|
|
523
|
+
${c.bold}Workflows:${c.reset}
|
|
524
|
+
analyze, audit, health, generate, deploy
|
|
525
|
+
|
|
526
|
+
${c.bold}Examples:${c.reset}
|
|
527
|
+
bootspring workspace init my-workspace
|
|
528
|
+
bootspring workspace add ../project-a --tags=frontend
|
|
529
|
+
bootspring workspace add ../project-b --tags=backend
|
|
530
|
+
bootspring workspace list
|
|
531
|
+
bootspring workspace run analyze
|
|
532
|
+
bootspring workspace run health --filter=frontend
|
|
533
|
+
`);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Run workspace command
|
|
538
|
+
*/
|
|
539
|
+
async function run(args) {
|
|
540
|
+
const parsedArgs = utils.parseArgs(args);
|
|
541
|
+
const subcommand = parsedArgs._[0];
|
|
542
|
+
|
|
543
|
+
if (!subcommand || subcommand === 'help' || parsedArgs.help || parsedArgs.h) {
|
|
544
|
+
showHelp();
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
switch (subcommand) {
|
|
549
|
+
case 'init':
|
|
550
|
+
initWorkspace(parsedArgs._[1], { path: parsedArgs.path });
|
|
551
|
+
break;
|
|
552
|
+
|
|
553
|
+
case 'add':
|
|
554
|
+
if (!parsedArgs._[1]) {
|
|
555
|
+
console.log(`${c.red}Error:${c.reset} Project path required`);
|
|
556
|
+
process.exit(1);
|
|
557
|
+
}
|
|
558
|
+
addProject(parsedArgs._[1], {
|
|
559
|
+
name: parsedArgs.name,
|
|
560
|
+
tags: parsedArgs.tags?.split(',').map(t => t.trim())
|
|
561
|
+
});
|
|
562
|
+
break;
|
|
563
|
+
|
|
564
|
+
case 'remove':
|
|
565
|
+
case 'rm':
|
|
566
|
+
if (!parsedArgs._[1]) {
|
|
567
|
+
console.log(`${c.red}Error:${c.reset} Project name or path required`);
|
|
568
|
+
process.exit(1);
|
|
569
|
+
}
|
|
570
|
+
removeProject(parsedArgs._[1]);
|
|
571
|
+
break;
|
|
572
|
+
|
|
573
|
+
case 'list':
|
|
574
|
+
case 'ls':
|
|
575
|
+
listProjects({ json: parsedArgs.json });
|
|
576
|
+
break;
|
|
577
|
+
|
|
578
|
+
case 'status':
|
|
579
|
+
showStatus();
|
|
580
|
+
break;
|
|
581
|
+
|
|
582
|
+
case 'run':
|
|
583
|
+
case 'exec':
|
|
584
|
+
if (!parsedArgs._[1]) {
|
|
585
|
+
console.log(`${c.red}Error:${c.reset} Workflow name required`);
|
|
586
|
+
process.exit(1);
|
|
587
|
+
}
|
|
588
|
+
await runWorkflow(parsedArgs._[1], {
|
|
589
|
+
filter: parsedArgs.filter,
|
|
590
|
+
sequential: parsedArgs.sequential,
|
|
591
|
+
verbose: parsedArgs.verbose,
|
|
592
|
+
timeout: parseInt(parsedArgs.timeout, 10) || 300000
|
|
593
|
+
});
|
|
594
|
+
break;
|
|
595
|
+
|
|
596
|
+
default:
|
|
597
|
+
console.log(`${c.red}Error:${c.reset} Unknown command: ${subcommand}`);
|
|
598
|
+
showHelp();
|
|
599
|
+
process.exit(1);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
module.exports = {
|
|
604
|
+
run,
|
|
605
|
+
Workspace,
|
|
606
|
+
findWorkspaceRoot
|
|
607
|
+
};
|