@girardmedia/bootspring 1.1.0
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/LICENSE +21 -0
- package/README.md +255 -0
- package/agents/README.md +93 -0
- package/agents/api-expert/context.md +416 -0
- package/agents/architecture-expert/context.md +454 -0
- package/agents/backend-expert/context.md +483 -0
- package/agents/code-review-expert/context.md +365 -0
- package/agents/database-expert/context.md +250 -0
- package/agents/devops-expert/context.md +446 -0
- package/agents/frontend-expert/context.md +364 -0
- package/agents/index.js +140 -0
- package/agents/performance-expert/context.md +377 -0
- package/agents/security-expert/context.md +343 -0
- package/agents/testing-expert/context.md +414 -0
- package/agents/ui-ux-expert/context.md +448 -0
- package/agents/vercel-expert/context.md +426 -0
- package/bin/bootspring.js +310 -0
- package/cli/agent.js +337 -0
- package/cli/context.js +194 -0
- package/cli/dashboard.js +150 -0
- package/cli/generate.js +294 -0
- package/cli/init.js +410 -0
- package/cli/loop.js +421 -0
- package/cli/mcp.js +241 -0
- package/cli/memory.js +303 -0
- package/cli/orchestrator.js +400 -0
- package/cli/plugin.js +451 -0
- package/cli/quality.js +332 -0
- package/cli/skill.js +369 -0
- package/cli/task.js +628 -0
- package/cli/telemetry.js +114 -0
- package/cli/todo.js +614 -0
- package/cli/update.js +312 -0
- package/core/config.js +245 -0
- package/core/context.js +329 -0
- package/core/entitlements.js +209 -0
- package/core/index.js +43 -0
- package/core/policies.js +68 -0
- package/core/telemetry.js +247 -0
- package/core/utils.js +380 -0
- package/dashboard/server.js +818 -0
- package/docs/integrations/claude-code.md +42 -0
- package/docs/integrations/codex.md +42 -0
- package/docs/mcp-api-platform.md +102 -0
- package/generators/generate.js +598 -0
- package/generators/index.js +18 -0
- package/hooks/context-detector.js +177 -0
- package/hooks/index.js +35 -0
- package/hooks/prompt-enhancer.js +289 -0
- package/intelligence/git-memory.js +551 -0
- package/intelligence/index.js +59 -0
- package/intelligence/orchestrator.js +964 -0
- package/intelligence/prd.js +447 -0
- package/intelligence/recommendation-weights.json +18 -0
- package/intelligence/recommendations.js +234 -0
- package/mcp/capabilities.js +71 -0
- package/mcp/contracts/mcp-contract.v1.json +497 -0
- package/mcp/registry.js +213 -0
- package/mcp/response-formatter.js +462 -0
- package/mcp/server.js +99 -0
- package/mcp/tools/agent-tool.js +137 -0
- package/mcp/tools/capabilities-tool.js +54 -0
- package/mcp/tools/context-tool.js +49 -0
- package/mcp/tools/dashboard-tool.js +58 -0
- package/mcp/tools/generate-tool.js +46 -0
- package/mcp/tools/loop-tool.js +134 -0
- package/mcp/tools/memory-tool.js +180 -0
- package/mcp/tools/orchestrator-tool.js +232 -0
- package/mcp/tools/plugin-tool.js +76 -0
- package/mcp/tools/quality-tool.js +47 -0
- package/mcp/tools/skill-tool.js +233 -0
- package/mcp/tools/telemetry-tool.js +95 -0
- package/mcp/tools/todo-tool.js +133 -0
- package/package.json +98 -0
- package/plugins/index.js +141 -0
- package/quality/index.js +380 -0
- package/quality/lint-budgets.json +19 -0
- package/skills/index.js +787 -0
- package/skills/patterns/README.md +163 -0
- package/skills/patterns/api/route-handler.md +217 -0
- package/skills/patterns/api/server-action.md +249 -0
- package/skills/patterns/auth/clerk.md +132 -0
- package/skills/patterns/database/prisma.md +180 -0
- package/skills/patterns/payments/stripe.md +272 -0
- package/skills/patterns/security/validation.md +268 -0
- package/skills/patterns/testing/vitest.md +307 -0
- package/templates/bootspring.config.js +83 -0
- package/templates/mcp.json +9 -0
package/cli/update.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring Update Command
|
|
3
|
+
* Check for updates and manage versions
|
|
4
|
+
*
|
|
5
|
+
* @package bootspring
|
|
6
|
+
* @command update
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { execSync } = require('child_process');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const config = require('../core/config');
|
|
13
|
+
const utils = require('../core/utils');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get current installed version
|
|
17
|
+
*/
|
|
18
|
+
function getCurrentVersion() {
|
|
19
|
+
const packagePath = path.join(__dirname, '..', 'package.json');
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
|
|
23
|
+
return pkg.version || '0.0.0';
|
|
24
|
+
} catch (error) {
|
|
25
|
+
return '0.0.0';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get latest version from npm
|
|
31
|
+
*/
|
|
32
|
+
async function getLatestVersion() {
|
|
33
|
+
try {
|
|
34
|
+
const result = execSync('npm view bootspring version 2>/dev/null', {
|
|
35
|
+
encoding: 'utf-8',
|
|
36
|
+
timeout: 10000
|
|
37
|
+
}).trim();
|
|
38
|
+
return result || null;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Compare semantic versions
|
|
46
|
+
* Returns: -1 if a < b, 0 if a === b, 1 if a > b
|
|
47
|
+
*/
|
|
48
|
+
function compareVersions(a, b) {
|
|
49
|
+
const partsA = a.split('.').map(Number);
|
|
50
|
+
const partsB = b.split('.').map(Number);
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < 3; i++) {
|
|
53
|
+
const partA = partsA[i] || 0;
|
|
54
|
+
const partB = partsB[i] || 0;
|
|
55
|
+
|
|
56
|
+
if (partA < partB) return -1;
|
|
57
|
+
if (partA > partB) return 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Check for updates
|
|
65
|
+
*/
|
|
66
|
+
async function checkForUpdates(options = {}) {
|
|
67
|
+
console.log(`
|
|
68
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Update Check${utils.COLORS.reset}
|
|
69
|
+
`);
|
|
70
|
+
|
|
71
|
+
const spinner = utils.createSpinner('Checking for updates...').start();
|
|
72
|
+
|
|
73
|
+
const currentVersion = getCurrentVersion();
|
|
74
|
+
const latestVersion = await getLatestVersion();
|
|
75
|
+
|
|
76
|
+
if (!latestVersion) {
|
|
77
|
+
spinner.warn('Could not fetch latest version from npm');
|
|
78
|
+
console.log(`${utils.COLORS.dim}Current version: ${currentVersion}${utils.COLORS.reset}`);
|
|
79
|
+
console.log(`${utils.COLORS.dim}Package may not be published yet${utils.COLORS.reset}`);
|
|
80
|
+
return { current: currentVersion, latest: null, updateAvailable: false };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const comparison = compareVersions(currentVersion, latestVersion);
|
|
84
|
+
|
|
85
|
+
if (comparison < 0) {
|
|
86
|
+
spinner.succeed(`Update available: ${currentVersion} → ${latestVersion}`);
|
|
87
|
+
console.log(`
|
|
88
|
+
${utils.COLORS.bold}To update:${utils.COLORS.reset}
|
|
89
|
+
${utils.COLORS.cyan}npm update -g bootspring${utils.COLORS.reset}
|
|
90
|
+
|
|
91
|
+
${utils.COLORS.bold}Or for local projects:${utils.COLORS.reset}
|
|
92
|
+
${utils.COLORS.cyan}npm update bootspring${utils.COLORS.reset}
|
|
93
|
+
`);
|
|
94
|
+
return { current: currentVersion, latest: latestVersion, updateAvailable: true };
|
|
95
|
+
} else if (comparison === 0) {
|
|
96
|
+
spinner.succeed(`You're on the latest version (${currentVersion})`);
|
|
97
|
+
return { current: currentVersion, latest: latestVersion, updateAvailable: false };
|
|
98
|
+
} else {
|
|
99
|
+
spinner.info(`You're ahead of npm (${currentVersion} > ${latestVersion})`);
|
|
100
|
+
console.log(`${utils.COLORS.dim}You may be running a development version${utils.COLORS.reset}`);
|
|
101
|
+
return { current: currentVersion, latest: latestVersion, updateAvailable: false };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Apply update via npm
|
|
107
|
+
*/
|
|
108
|
+
async function applyUpdate(options = {}) {
|
|
109
|
+
console.log(`
|
|
110
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Update${utils.COLORS.reset}
|
|
111
|
+
`);
|
|
112
|
+
|
|
113
|
+
const checkResult = await checkForUpdates();
|
|
114
|
+
|
|
115
|
+
if (!checkResult.updateAvailable) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
console.log(`${utils.COLORS.bold}Starting update...${utils.COLORS.reset}`);
|
|
120
|
+
|
|
121
|
+
const spinner = utils.createSpinner('Updating bootspring...').start();
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
// Detect if installed globally or locally
|
|
125
|
+
const isGlobal = options.global || process.argv[1].includes('node_modules/.bin');
|
|
126
|
+
|
|
127
|
+
const command = isGlobal
|
|
128
|
+
? 'npm update -g bootspring'
|
|
129
|
+
: 'npm update bootspring';
|
|
130
|
+
|
|
131
|
+
execSync(command, {
|
|
132
|
+
stdio: 'pipe',
|
|
133
|
+
encoding: 'utf-8',
|
|
134
|
+
timeout: 120000 // 2 minutes
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
spinner.succeed(`Updated to ${checkResult.latest}`);
|
|
138
|
+
|
|
139
|
+
console.log(`
|
|
140
|
+
${utils.COLORS.green}Update complete!${utils.COLORS.reset}
|
|
141
|
+
${utils.COLORS.dim}Run "bootspring --version" to verify${utils.COLORS.reset}
|
|
142
|
+
`);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
spinner.fail('Update failed');
|
|
145
|
+
console.log(`${utils.COLORS.red}${error.message}${utils.COLORS.reset}`);
|
|
146
|
+
console.log(`
|
|
147
|
+
${utils.COLORS.dim}Try updating manually:${utils.COLORS.reset}
|
|
148
|
+
npm update -g bootspring
|
|
149
|
+
`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Show version information
|
|
155
|
+
*/
|
|
156
|
+
function showVersion() {
|
|
157
|
+
const currentVersion = getCurrentVersion();
|
|
158
|
+
const packagePath = path.join(__dirname, '..', 'package.json');
|
|
159
|
+
|
|
160
|
+
let packageInfo = {};
|
|
161
|
+
try {
|
|
162
|
+
packageInfo = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
|
|
163
|
+
} catch (error) {
|
|
164
|
+
// Ignore
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log(`
|
|
168
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring${utils.COLORS.reset}
|
|
169
|
+
${utils.COLORS.dim}Development scaffolding with intelligence${utils.COLORS.reset}
|
|
170
|
+
|
|
171
|
+
${utils.COLORS.bold}Version Information${utils.COLORS.reset}
|
|
172
|
+
Version: ${utils.COLORS.cyan}${currentVersion}${utils.COLORS.reset}
|
|
173
|
+
${packageInfo.description ? `Description: ${packageInfo.description}` : ''}
|
|
174
|
+
${packageInfo.homepage ? `Homepage: ${packageInfo.homepage}` : ''}
|
|
175
|
+
${packageInfo.repository?.url ? `Repository: ${packageInfo.repository.url}` : ''}
|
|
176
|
+
|
|
177
|
+
${utils.COLORS.bold}Environment${utils.COLORS.reset}
|
|
178
|
+
Node.js: ${process.version}
|
|
179
|
+
Platform: ${process.platform}
|
|
180
|
+
Arch: ${process.arch}
|
|
181
|
+
|
|
182
|
+
${utils.COLORS.dim}Run "bootspring update check" to check for updates${utils.COLORS.reset}
|
|
183
|
+
`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Show changelog/release notes
|
|
188
|
+
*/
|
|
189
|
+
async function showChangelog() {
|
|
190
|
+
console.log(`
|
|
191
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Changelog${utils.COLORS.reset}
|
|
192
|
+
`);
|
|
193
|
+
|
|
194
|
+
const spinner = utils.createSpinner('Fetching changelog...').start();
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
// Try to fetch from npm
|
|
198
|
+
const result = execSync('npm view bootspring --json 2>/dev/null', {
|
|
199
|
+
encoding: 'utf-8',
|
|
200
|
+
timeout: 10000
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const npmInfo = JSON.parse(result);
|
|
204
|
+
|
|
205
|
+
spinner.succeed('Latest release info');
|
|
206
|
+
|
|
207
|
+
console.log(`
|
|
208
|
+
${utils.COLORS.bold}Latest Version${utils.COLORS.reset}
|
|
209
|
+
Version: ${npmInfo.version}
|
|
210
|
+
Published: ${npmInfo.time?.[npmInfo.version] || 'unknown'}
|
|
211
|
+
|
|
212
|
+
${utils.COLORS.bold}Recent Versions${utils.COLORS.reset}`);
|
|
213
|
+
|
|
214
|
+
const versions = Object.keys(npmInfo.time || {})
|
|
215
|
+
.filter(v => v !== 'created' && v !== 'modified')
|
|
216
|
+
.slice(-5)
|
|
217
|
+
.reverse();
|
|
218
|
+
|
|
219
|
+
for (const version of versions) {
|
|
220
|
+
const date = new Date(npmInfo.time[version]);
|
|
221
|
+
console.log(` ${utils.COLORS.cyan}${version}${utils.COLORS.reset} - ${utils.formatDate(date)}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
console.log(`
|
|
225
|
+
${utils.COLORS.dim}For full changelog, visit: ${npmInfo.homepage || 'https://github.com/bootspring/bootspring'}${utils.COLORS.reset}
|
|
226
|
+
`);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
spinner.warn('Could not fetch changelog');
|
|
229
|
+
console.log(`
|
|
230
|
+
${utils.COLORS.dim}Package may not be published yet.
|
|
231
|
+
Check the repository for changelog information.${utils.COLORS.reset}
|
|
232
|
+
`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Show help
|
|
238
|
+
*/
|
|
239
|
+
function showHelp() {
|
|
240
|
+
console.log(`
|
|
241
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Update${utils.COLORS.reset}
|
|
242
|
+
${utils.COLORS.dim}Check for updates and manage versions${utils.COLORS.reset}
|
|
243
|
+
|
|
244
|
+
${utils.COLORS.bold}Usage:${utils.COLORS.reset}
|
|
245
|
+
bootspring update [command]
|
|
246
|
+
|
|
247
|
+
${utils.COLORS.bold}Commands:${utils.COLORS.reset}
|
|
248
|
+
${utils.COLORS.cyan}check${utils.COLORS.reset} Check for available updates
|
|
249
|
+
${utils.COLORS.cyan}apply${utils.COLORS.reset} Download and apply updates
|
|
250
|
+
${utils.COLORS.cyan}version${utils.COLORS.reset} Show version information
|
|
251
|
+
${utils.COLORS.cyan}changelog${utils.COLORS.reset} Show recent changes
|
|
252
|
+
|
|
253
|
+
${utils.COLORS.bold}Options:${utils.COLORS.reset}
|
|
254
|
+
--global Update global installation
|
|
255
|
+
|
|
256
|
+
${utils.COLORS.bold}Examples:${utils.COLORS.reset}
|
|
257
|
+
bootspring update check
|
|
258
|
+
bootspring update apply
|
|
259
|
+
bootspring update version
|
|
260
|
+
`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Run update command
|
|
265
|
+
*/
|
|
266
|
+
async function run(args) {
|
|
267
|
+
const parsedArgs = utils.parseArgs(args);
|
|
268
|
+
const subcommand = parsedArgs._[0] || 'check';
|
|
269
|
+
|
|
270
|
+
switch (subcommand) {
|
|
271
|
+
case 'check':
|
|
272
|
+
await checkForUpdates();
|
|
273
|
+
break;
|
|
274
|
+
|
|
275
|
+
case 'apply':
|
|
276
|
+
case 'upgrade':
|
|
277
|
+
case 'install':
|
|
278
|
+
await applyUpdate({ global: parsedArgs.global });
|
|
279
|
+
break;
|
|
280
|
+
|
|
281
|
+
case 'version':
|
|
282
|
+
case '-v':
|
|
283
|
+
case '--version':
|
|
284
|
+
showVersion();
|
|
285
|
+
break;
|
|
286
|
+
|
|
287
|
+
case 'changelog':
|
|
288
|
+
case 'changes':
|
|
289
|
+
case 'releases':
|
|
290
|
+
await showChangelog();
|
|
291
|
+
break;
|
|
292
|
+
|
|
293
|
+
case 'help':
|
|
294
|
+
case '-h':
|
|
295
|
+
case '--help':
|
|
296
|
+
showHelp();
|
|
297
|
+
break;
|
|
298
|
+
|
|
299
|
+
default:
|
|
300
|
+
utils.print.error(`Unknown subcommand: ${subcommand}`);
|
|
301
|
+
showHelp();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
module.exports = {
|
|
306
|
+
run,
|
|
307
|
+
checkForUpdates,
|
|
308
|
+
applyUpdate,
|
|
309
|
+
getCurrentVersion,
|
|
310
|
+
getLatestVersion,
|
|
311
|
+
compareVersions
|
|
312
|
+
};
|
package/core/config.js
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring Configuration
|
|
3
|
+
* Handles loading and validating bootspring.config.js
|
|
4
|
+
*
|
|
5
|
+
* @package bootspring
|
|
6
|
+
* @module core/config
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
// Default configuration
|
|
13
|
+
const DEFAULT_CONFIG = {
|
|
14
|
+
project: {
|
|
15
|
+
name: 'My Project',
|
|
16
|
+
description: '',
|
|
17
|
+
version: '1.0.0'
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
stack: {
|
|
21
|
+
framework: 'nextjs',
|
|
22
|
+
language: 'typescript',
|
|
23
|
+
database: 'postgresql',
|
|
24
|
+
hosting: 'vercel'
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
plugins: {
|
|
28
|
+
auth: { enabled: false, provider: 'clerk' },
|
|
29
|
+
payments: { enabled: false, provider: 'stripe' },
|
|
30
|
+
database: { enabled: true, provider: 'prisma' },
|
|
31
|
+
testing: { enabled: true, provider: 'vitest' },
|
|
32
|
+
security: { enabled: true },
|
|
33
|
+
ai: { enabled: false, provider: 'anthropic' }
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
dashboard: {
|
|
37
|
+
port: 3456,
|
|
38
|
+
autoOpen: false
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
quality: {
|
|
42
|
+
preCommit: true,
|
|
43
|
+
prePush: false,
|
|
44
|
+
strictMode: false
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
paths: {
|
|
48
|
+
context: 'CLAUDE.md',
|
|
49
|
+
config: 'bootspring.config.js',
|
|
50
|
+
todo: 'todo.md'
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Config file names to search for
|
|
55
|
+
const CONFIG_FILES = [
|
|
56
|
+
'bootspring.config.js',
|
|
57
|
+
'bootspring.config.mjs',
|
|
58
|
+
'bootspring.config.json',
|
|
59
|
+
'.bootspringrc',
|
|
60
|
+
'.bootspringrc.js',
|
|
61
|
+
'.bootspringrc.json'
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Find the project root directory
|
|
66
|
+
* @returns {string|null} Project root path or null
|
|
67
|
+
*/
|
|
68
|
+
function findProjectRoot() {
|
|
69
|
+
let dir = process.cwd();
|
|
70
|
+
const root = path.parse(dir).root;
|
|
71
|
+
|
|
72
|
+
while (dir !== root) {
|
|
73
|
+
// Check for common project indicators
|
|
74
|
+
if (
|
|
75
|
+
fs.existsSync(path.join(dir, 'package.json')) ||
|
|
76
|
+
fs.existsSync(path.join(dir, 'bootspring.config.js')) ||
|
|
77
|
+
fs.existsSync(path.join(dir, '.git'))
|
|
78
|
+
) {
|
|
79
|
+
return dir;
|
|
80
|
+
}
|
|
81
|
+
dir = path.dirname(dir);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return process.cwd();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Find config file in project
|
|
89
|
+
* @param {string} projectRoot - Project root directory
|
|
90
|
+
* @returns {string|null} Config file path or null
|
|
91
|
+
*/
|
|
92
|
+
function findConfigFile(projectRoot) {
|
|
93
|
+
for (const filename of CONFIG_FILES) {
|
|
94
|
+
const filepath = path.join(projectRoot, filename);
|
|
95
|
+
if (fs.existsSync(filepath)) {
|
|
96
|
+
return filepath;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Deep merge two objects
|
|
104
|
+
* @param {object} target - Target object
|
|
105
|
+
* @param {object} source - Source object
|
|
106
|
+
* @returns {object} Merged object
|
|
107
|
+
*/
|
|
108
|
+
function deepMerge(target, source) {
|
|
109
|
+
const result = { ...target };
|
|
110
|
+
|
|
111
|
+
for (const key of Object.keys(source)) {
|
|
112
|
+
if (source[key] instanceof Object && key in target && target[key] instanceof Object) {
|
|
113
|
+
result[key] = deepMerge(target[key], source[key]);
|
|
114
|
+
} else {
|
|
115
|
+
result[key] = source[key];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Load configuration from file
|
|
124
|
+
* @param {string} [projectRoot] - Optional project root path
|
|
125
|
+
* @returns {object} Configuration object
|
|
126
|
+
*/
|
|
127
|
+
function load(projectRoot = null) {
|
|
128
|
+
const root = projectRoot || findProjectRoot();
|
|
129
|
+
const configPath = findConfigFile(root);
|
|
130
|
+
|
|
131
|
+
let userConfig = {};
|
|
132
|
+
|
|
133
|
+
if (configPath) {
|
|
134
|
+
try {
|
|
135
|
+
if (configPath.endsWith('.json') || configPath.endsWith('.bootspringrc')) {
|
|
136
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
137
|
+
userConfig = JSON.parse(content);
|
|
138
|
+
} else {
|
|
139
|
+
// Clear require cache to get fresh config
|
|
140
|
+
delete require.cache[require.resolve(configPath)];
|
|
141
|
+
userConfig = require(configPath);
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.warn(`Warning: Could not load config from ${configPath}: ${error.message}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Merge with defaults
|
|
149
|
+
const config = deepMerge(DEFAULT_CONFIG, userConfig);
|
|
150
|
+
|
|
151
|
+
// Add computed paths
|
|
152
|
+
config._projectRoot = root;
|
|
153
|
+
config._configPath = configPath;
|
|
154
|
+
config._bootspringDir = path.join(root, '.bootspring');
|
|
155
|
+
|
|
156
|
+
return config;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Save configuration to file
|
|
161
|
+
* @param {object} config - Configuration object
|
|
162
|
+
* @param {string} [filepath] - Optional file path
|
|
163
|
+
* @returns {boolean} Success status
|
|
164
|
+
*/
|
|
165
|
+
function save(config, filepath = null) {
|
|
166
|
+
const root = config._projectRoot || findProjectRoot();
|
|
167
|
+
const targetPath = filepath || path.join(root, 'bootspring.config.js');
|
|
168
|
+
|
|
169
|
+
// Remove internal properties
|
|
170
|
+
const cleanConfig = { ...config };
|
|
171
|
+
delete cleanConfig._projectRoot;
|
|
172
|
+
delete cleanConfig._configPath;
|
|
173
|
+
delete cleanConfig._bootspringDir;
|
|
174
|
+
|
|
175
|
+
const content = `/**
|
|
176
|
+
* Bootspring Configuration
|
|
177
|
+
* https://bootspring.com/docs/configuration
|
|
178
|
+
*/
|
|
179
|
+
|
|
180
|
+
module.exports = ${JSON.stringify(cleanConfig, null, 2)};
|
|
181
|
+
`;
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
fs.writeFileSync(targetPath, content, 'utf-8');
|
|
185
|
+
return true;
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.error(`Error saving config: ${error.message}`);
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Validate configuration
|
|
194
|
+
* @param {object} config - Configuration object
|
|
195
|
+
* @returns {object} Validation result { valid, errors }
|
|
196
|
+
*/
|
|
197
|
+
function validate(config) {
|
|
198
|
+
const errors = [];
|
|
199
|
+
|
|
200
|
+
// Required fields
|
|
201
|
+
if (!config.project?.name) {
|
|
202
|
+
errors.push('project.name is required');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Valid framework
|
|
206
|
+
const validFrameworks = ['nextjs', 'remix', 'nuxt', 'sveltekit', 'express', 'fastify'];
|
|
207
|
+
if (config.stack?.framework && !validFrameworks.includes(config.stack.framework)) {
|
|
208
|
+
errors.push(`Invalid framework: ${config.stack.framework}. Valid: ${validFrameworks.join(', ')}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Valid language
|
|
212
|
+
const validLanguages = ['typescript', 'javascript'];
|
|
213
|
+
if (config.stack?.language && !validLanguages.includes(config.stack.language)) {
|
|
214
|
+
errors.push(`Invalid language: ${config.stack.language}. Valid: ${validLanguages.join(', ')}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Port range
|
|
218
|
+
if (config.dashboard?.port && (config.dashboard.port < 1024 || config.dashboard.port > 65535)) {
|
|
219
|
+
errors.push('dashboard.port must be between 1024 and 65535');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
valid: errors.length === 0,
|
|
224
|
+
errors
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Get default configuration
|
|
230
|
+
* @returns {object} Default configuration
|
|
231
|
+
*/
|
|
232
|
+
function getDefaults() {
|
|
233
|
+
return { ...DEFAULT_CONFIG };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
module.exports = {
|
|
237
|
+
load,
|
|
238
|
+
save,
|
|
239
|
+
validate,
|
|
240
|
+
getDefaults,
|
|
241
|
+
findProjectRoot,
|
|
242
|
+
findConfigFile,
|
|
243
|
+
DEFAULT_CONFIG,
|
|
244
|
+
CONFIG_FILES
|
|
245
|
+
};
|