feat: Design Space installer — optional module with SQLite/Supabase choice
Installer now asks: 1. "Install Design Space?" (yes/no) 2. "Name your space:" (default: design-space) 3. "Connect to existing or create new?" 4. "Which database?" (SQLite local / Supabase cloud) Creates _bmad/<space-name>/ with: - config.yaml (backend, credentials, connection info) - SKILL.md + bmad-manifest.json (copied from src/skills/design-space/) - For SQLite: server.js + package.json (lite-server) - For Supabase create: SETUP.md with deployment instructions - WDS config.yaml gets design_space reference appended Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8b73573fcb
commit
754736750b
|
|
@ -22,7 +22,7 @@ module.exports = {
|
|||
const result = await installer.install(config);
|
||||
|
||||
if (result && result.success) {
|
||||
ui.displaySuccess(config.wdsFolder, config.ides);
|
||||
ui.displaySuccess(config.wdsFolder, config.ides, config.install_design_space, config.ds_name, config.ds_backend);
|
||||
process.exit(0);
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -161,7 +161,19 @@ class Installer {
|
|||
throw error;
|
||||
}
|
||||
|
||||
// Step 5: Copy learning & reference material (optional)
|
||||
// Step 5: Install Design Space (optional)
|
||||
if (config.install_design_space) {
|
||||
const dsSpinner = ora(`Setting up ${config.ds_name || 'design-space'}...`).start();
|
||||
try {
|
||||
await this.installDesignSpace(projectDir, config);
|
||||
dsSpinner.succeed(`${config.ds_name || 'Design Space'} configured at _bmad/${config.ds_name || 'design-space'}/`);
|
||||
} catch (error) {
|
||||
dsSpinner.fail('Failed to set up Design Space');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6: Copy learning & reference material (optional)
|
||||
if (config.install_learning !== false) {
|
||||
const learnSpinner = ora('Copying learning & reference material...').start();
|
||||
try {
|
||||
|
|
@ -279,6 +291,108 @@ class Installer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install Design Space module
|
||||
*/
|
||||
async installDesignSpace(projectDir, config) {
|
||||
const dsName = config.ds_name || 'design-space';
|
||||
const dsDir = path.join(projectDir, '_bmad', dsName);
|
||||
await fs.ensureDir(dsDir);
|
||||
|
||||
// Write Design Space config
|
||||
const dsConfig = {
|
||||
name: dsName,
|
||||
backend: config.ds_backend || 'sqlite',
|
||||
};
|
||||
|
||||
if (config.ds_backend === 'supabase') {
|
||||
if (config.ds_mode === 'connect') {
|
||||
dsConfig.supabase_url = config.ds_url;
|
||||
dsConfig.supabase_anon_key = config.ds_key;
|
||||
} else {
|
||||
dsConfig.supabase_url = '# Run: npx supabase init && npx supabase start';
|
||||
dsConfig.supabase_anon_key = '# Get from Supabase dashboard → Settings → API';
|
||||
}
|
||||
dsConfig.base_url = config.ds_url
|
||||
? `${config.ds_url}/functions/v1`
|
||||
: '# Set after Supabase project is created';
|
||||
} else {
|
||||
// SQLite
|
||||
dsConfig.db_path = config.ds_db_path || `./${dsName}.db`;
|
||||
dsConfig.base_url = 'http://localhost:3141';
|
||||
dsConfig.note = 'Run: cd _bmad/' + dsName + ' && npm install && node server.js';
|
||||
}
|
||||
|
||||
const yamlStr = yaml.dump(dsConfig, { lineWidth: -1 });
|
||||
await fs.writeFile(
|
||||
path.join(dsDir, 'config.yaml'),
|
||||
`# Design Space Configuration - Generated by WDS installer\n${yamlStr}`,
|
||||
'utf8'
|
||||
);
|
||||
|
||||
// Copy the Design Space skill into the module
|
||||
const dsSkillSrc = path.join(this.srcDir, 'skills', 'design-space');
|
||||
if (await fs.pathExists(dsSkillSrc)) {
|
||||
await fs.copy(dsSkillSrc, path.join(dsDir, 'skill'));
|
||||
}
|
||||
|
||||
// For SQLite: copy the lite-server files
|
||||
if (config.ds_backend === 'sqlite') {
|
||||
const liteServerSrc = path.resolve(this.srcDir, '..', '..', 'design-space', 'lite-server');
|
||||
// If lite-server exists in the design-space repo (dev environment), copy it
|
||||
if (await fs.pathExists(liteServerSrc)) {
|
||||
const filesToCopy = ['server.js', 'package.json'];
|
||||
for (const file of filesToCopy) {
|
||||
const src = path.join(liteServerSrc, file);
|
||||
if (await fs.pathExists(src)) {
|
||||
await fs.copy(src, path.join(dsDir, file));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Create a minimal package.json that points to the npm package
|
||||
await fs.writeFile(
|
||||
path.join(dsDir, 'package.json'),
|
||||
JSON.stringify(
|
||||
{
|
||||
name: dsName,
|
||||
private: true,
|
||||
type: 'module',
|
||||
dependencies: { 'better-sqlite3': '^11.0.0', 'sqlite-vec': '^0.1.7' },
|
||||
scripts: { start: 'node server.js' },
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
'utf8'
|
||||
);
|
||||
|
||||
// Write a note about getting the server
|
||||
await fs.writeFile(
|
||||
path.join(dsDir, 'README.md'),
|
||||
`# ${dsName}\n\nDesign Space — local SQLite backend.\n\nGet the server:\n\`\`\`bash\nnpm install\n# Copy server.js from https://github.com/whiteport-collective/design-space/tree/master/lite-server\nnpm start\n\`\`\`\n`,
|
||||
'utf8'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// For Supabase with create mode: write setup instructions
|
||||
if (config.ds_backend === 'supabase' && config.ds_mode === 'create') {
|
||||
await fs.writeFile(
|
||||
path.join(dsDir, 'SETUP.md'),
|
||||
`# ${dsName} — Supabase Setup\n\n1. Create a project at https://supabase.com\n2. Get your project URL and anon key from Settings → API\n3. Update config.yaml with the URL and key\n4. Deploy edge functions:\n \`\`\`bash\n git clone https://github.com/whiteport-collective/design-space.git /tmp/ds\n cd /tmp/ds && ./setup.sh YOUR-PROJECT-REF\n \`\`\`\n5. Set OPENROUTER_API_KEY in Supabase → Edge Functions → Secrets (for semantic search)\n`,
|
||||
'utf8'
|
||||
);
|
||||
}
|
||||
|
||||
// Store DS config reference in WDS config
|
||||
const wdsConfigPath = path.join(projectDir, config.wdsFolder, 'config.yaml');
|
||||
if (await fs.pathExists(wdsConfigPath)) {
|
||||
let wdsConfig = await fs.readFile(wdsConfigPath, 'utf8');
|
||||
wdsConfig += `\n# Design Space\ndesign_space:\n name: ${dsName}\n path: _bmad/${dsName}\n backend: ${config.ds_backend}\n`;
|
||||
await fs.writeFile(wdsConfigPath, wdsConfig, 'utf8');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the WDS work products folder structure
|
||||
* @param {string} projectDir - Project root directory
|
||||
|
|
|
|||
|
|
@ -191,9 +191,30 @@ class UI {
|
|||
},
|
||||
]);
|
||||
|
||||
// --- Design Space (optional) ---
|
||||
console.log('');
|
||||
console.log(chalk.white.bold(' Design Space'));
|
||||
console.log(chalk.dim(' Shared knowledge base and agent communication layer.'));
|
||||
console.log(chalk.dim(' Agents can search design knowledge, message each other, and track work.\n'));
|
||||
|
||||
const dsAnswers = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'install_design_space',
|
||||
message: 'Install Design Space?',
|
||||
default: true,
|
||||
},
|
||||
]);
|
||||
|
||||
let dsConfig = {};
|
||||
if (dsAnswers.install_design_space) {
|
||||
dsConfig = await this.promptDesignSpace();
|
||||
}
|
||||
|
||||
return {
|
||||
projectDir,
|
||||
...answers,
|
||||
...dsConfig,
|
||||
wdsFolder,
|
||||
_detection: detection,
|
||||
_action: action,
|
||||
|
|
@ -201,10 +222,98 @@ class UI {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt for Design Space configuration
|
||||
*/
|
||||
async promptDesignSpace() {
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'ds_name',
|
||||
message: 'Name your space:',
|
||||
default: 'design-space',
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'ds_mode',
|
||||
message: 'Connect to an existing space or create a new one?',
|
||||
choices: [
|
||||
{ name: 'Create new (set up fresh)', value: 'create' },
|
||||
{ name: 'Connect to existing (I have credentials)', value: 'connect' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
if (answers.ds_mode === 'connect') {
|
||||
const connectAnswers = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'ds_backend',
|
||||
message: 'What backend does it use?',
|
||||
choices: [
|
||||
{ name: 'Supabase (team/cloud)', value: 'supabase' },
|
||||
{ name: 'SQLite (local file)', value: 'sqlite' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'ds_url',
|
||||
message: 'Base URL:',
|
||||
when: (a) => a.ds_backend === 'supabase',
|
||||
validate: (v) => (v.includes('supabase.co') || v.startsWith('http') ? true : 'Enter a valid URL'),
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'ds_key',
|
||||
message: 'Anon key:',
|
||||
when: (a) => a.ds_backend === 'supabase',
|
||||
validate: (v) => (v.length > 20 ? true : 'Enter the Supabase anon key'),
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'ds_db_path',
|
||||
message: 'Path to .db file:',
|
||||
when: (a) => a.ds_backend === 'sqlite',
|
||||
default: './design-space.db',
|
||||
},
|
||||
]);
|
||||
|
||||
return {
|
||||
install_design_space: true,
|
||||
ds_name: answers.ds_name,
|
||||
ds_mode: 'connect',
|
||||
ds_backend: connectAnswers.ds_backend,
|
||||
ds_url: connectAnswers.ds_url,
|
||||
ds_key: connectAnswers.ds_key,
|
||||
ds_db_path: connectAnswers.ds_db_path,
|
||||
};
|
||||
}
|
||||
|
||||
// Create new
|
||||
const createAnswers = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'ds_backend',
|
||||
message: 'Which database?',
|
||||
choices: [
|
||||
{ name: 'SQLite — local file, zero infrastructure, data stays on your machine', value: 'sqlite' },
|
||||
{ name: 'Supabase — cloud database, team collaboration, multi-machine', value: 'supabase' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
return {
|
||||
install_design_space: true,
|
||||
ds_name: answers.ds_name,
|
||||
ds_mode: 'create',
|
||||
ds_backend: createAnswers.ds_backend,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Display success message with next steps
|
||||
*/
|
||||
displaySuccess(wdsFolder, ides = ['windsurf']) {
|
||||
displaySuccess(wdsFolder, ides = ['windsurf'], dsInstalled = false, dsName = '', dsBackend = '') {
|
||||
const ideNames = {
|
||||
'rovo-dev': 'Atlassian Rovo Dev',
|
||||
auggie: 'Auggie CLI',
|
||||
|
|
@ -256,6 +365,9 @@ class UI {
|
|||
console.log(chalk.dim(' ─────────────────────────────────────────────────'));
|
||||
console.log('');
|
||||
console.log(chalk.dim(` Available agents: Saga (Analyst), Freya (Designer)`));
|
||||
if (dsInstalled) {
|
||||
console.log(chalk.dim(` Design Space: ${dsName} (${dsBackend})`));
|
||||
}
|
||||
console.log(chalk.dim(` Need development? Install BMM: npx bmad-builder install`));
|
||||
console.log(chalk.dim(' Docs: https://github.com/whiteport-collective/whiteport-design-studio'));
|
||||
console.log('');
|
||||
|
|
|
|||
Loading…
Reference in New Issue