Compare commits
No commits in common. "46c54c0313a762eab02a1dbe370fa6e48284efec" and "020226ec14c641252ed5513774571d67cc244094" have entirely different histories.
46c54c0313
...
020226ec14
|
|
@ -61,7 +61,7 @@
|
||||||
| -------------- | -------------- | ------------------------- | ----------------------------- |
|
| -------------- | -------------- | ------------------------- | ----------------------------- |
|
||||||
| claude-code | Claude Code | .claude/commands/ | .md with frontmatter |
|
| claude-code | Claude Code | .claude/commands/ | .md with frontmatter |
|
||||||
| codex | Codex | (varies) | .md |
|
| codex | Codex | (varies) | .md |
|
||||||
| cursor | Cursor | .cursor/commands/bmad/ | .md with YAML frontmatter |
|
| cursor | Cursor | .cursor/rules/bmad/ | .mdc with MDC frontmatter |
|
||||||
| github-copilot | GitHub Copilot | .github/ | .md |
|
| github-copilot | GitHub Copilot | .github/ | .md |
|
||||||
| opencode | OpenCode | .opencode/ | .md |
|
| opencode | OpenCode | .opencode/ | .md |
|
||||||
| windsurf | Windsurf | .windsurf/workflows/bmad/ | .md with workflow frontmatter |
|
| windsurf | Windsurf | .windsurf/workflows/bmad/ | .md with workflow frontmatter |
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ module.exports = { NewIdeSetup };
|
||||||
| IDE | Config Pattern | File Extension |
|
| IDE | Config Pattern | File Extension |
|
||||||
| -------------- | ------------------------- | -------------- |
|
| -------------- | ------------------------- | -------------- |
|
||||||
| Claude Code | .claude/commands/bmad/ | .md |
|
| Claude Code | .claude/commands/bmad/ | .md |
|
||||||
| Cursor | .cursor/commands/bmad/ | .md |
|
| Cursor | .cursor/rules/bmad/ | .mdc |
|
||||||
| Windsurf | .windsurf/workflows/bmad/ | .md |
|
| Windsurf | .windsurf/workflows/bmad/ | .md |
|
||||||
| GitHub Copilot | .github/ | .md |
|
| GitHub Copilot | .github/ | .md |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
|
const inquirer = require('inquirer').default || require('inquirer');
|
||||||
const { Installer } = require('../installers/lib/core/installer');
|
const { Installer } = require('../installers/lib/core/installer');
|
||||||
const { UI } = require('../lib/ui');
|
const { UI } = require('../lib/ui');
|
||||||
|
|
||||||
|
|
@ -71,7 +72,6 @@ module.exports = {
|
||||||
console.log(chalk.dim(' • ElevenLabs AI (150+ premium voices)'));
|
console.log(chalk.dim(' • ElevenLabs AI (150+ premium voices)'));
|
||||||
console.log(chalk.dim(' • Piper TTS (50+ free voices)\n'));
|
console.log(chalk.dim(' • Piper TTS (50+ free voices)\n'));
|
||||||
|
|
||||||
const { default: inquirer } = await import('inquirer');
|
|
||||||
await inquirer.prompt([
|
await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,10 @@ const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
|
const inquirer = require('inquirer').default || require('inquirer');
|
||||||
const { getProjectRoot, getModulePath } = require('../../../lib/project-root');
|
const { getProjectRoot, getModulePath } = require('../../../lib/project-root');
|
||||||
const { CLIUtils } = require('../../../lib/cli-utils');
|
const { CLIUtils } = require('../../../lib/cli-utils');
|
||||||
|
|
||||||
// Lazy-load inquirer (ESM module) to avoid ERR_REQUIRE_ESM
|
|
||||||
let _inquirer = null;
|
|
||||||
async function getInquirer() {
|
|
||||||
if (!_inquirer) {
|
|
||||||
_inquirer = (await import('inquirer')).default;
|
|
||||||
}
|
|
||||||
return _inquirer;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConfigCollector {
|
class ConfigCollector {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.collectedConfig = {};
|
this.collectedConfig = {};
|
||||||
|
|
@ -183,7 +175,6 @@ class ConfigCollector {
|
||||||
* @returns {boolean} True if new fields were prompted, false if all fields existed
|
* @returns {boolean} True if new fields were prompted, false if all fields existed
|
||||||
*/
|
*/
|
||||||
async collectModuleConfigQuick(moduleName, projectDir, silentMode = true) {
|
async collectModuleConfigQuick(moduleName, projectDir, silentMode = true) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
this.currentProjectDir = projectDir;
|
this.currentProjectDir = projectDir;
|
||||||
|
|
||||||
// Load existing config if not already loaded
|
// Load existing config if not already loaded
|
||||||
|
|
@ -502,7 +493,6 @@ class ConfigCollector {
|
||||||
* @param {boolean} skipCompletion - Skip showing completion message (for early core collection)
|
* @param {boolean} skipCompletion - Skip showing completion message (for early core collection)
|
||||||
*/
|
*/
|
||||||
async collectModuleConfig(moduleName, projectDir, skipLoadExisting = false, skipCompletion = false) {
|
async collectModuleConfig(moduleName, projectDir, skipLoadExisting = false, skipCompletion = false) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
this.currentProjectDir = projectDir;
|
this.currentProjectDir = projectDir;
|
||||||
// Load existing config if needed and not already loaded
|
// Load existing config if needed and not already loaded
|
||||||
if (!skipLoadExisting && !this.existingConfig) {
|
if (!skipLoadExisting && !this.existingConfig) {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const ora = require('ora');
|
const ora = require('ora');
|
||||||
|
const inquirer = require('inquirer').default || require('inquirer');
|
||||||
const { Detector } = require('./detector');
|
const { Detector } = require('./detector');
|
||||||
const { Manifest } = require('./manifest');
|
const { Manifest } = require('./manifest');
|
||||||
const { ModuleManager } = require('../modules/manager');
|
const { ModuleManager } = require('../modules/manager');
|
||||||
|
|
@ -2139,7 +2140,7 @@ class Installer {
|
||||||
* Private: Prompt for update action
|
* Private: Prompt for update action
|
||||||
*/
|
*/
|
||||||
async promptUpdateAction() {
|
async promptUpdateAction() {
|
||||||
const { default: inquirer } = await import('inquirer');
|
const inquirer = require('inquirer').default || require('inquirer');
|
||||||
return await inquirer.prompt([
|
return await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'list',
|
type: 'list',
|
||||||
|
|
@ -2156,7 +2157,7 @@ class Installer {
|
||||||
* @param {Object} _legacyV4 - Legacy V4 detection result (unused in simplified version)
|
* @param {Object} _legacyV4 - Legacy V4 detection result (unused in simplified version)
|
||||||
*/
|
*/
|
||||||
async handleLegacyV4Migration(_projectDir, _legacyV4) {
|
async handleLegacyV4Migration(_projectDir, _legacyV4) {
|
||||||
const { default: inquirer } = await import('inquirer');
|
const inquirer = require('inquirer').default || require('inquirer');
|
||||||
|
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(chalk.yellow.bold('⚠️ Legacy BMAD v4 detected'));
|
console.log(chalk.yellow.bold('⚠️ Legacy BMAD v4 detected'));
|
||||||
|
|
@ -2437,7 +2438,7 @@ class Installer {
|
||||||
|
|
||||||
console.log(chalk.yellow(`\n⚠️ Found ${customModulesWithMissingSources.length} custom module(s) with missing sources:`));
|
console.log(chalk.yellow(`\n⚠️ Found ${customModulesWithMissingSources.length} custom module(s) with missing sources:`));
|
||||||
|
|
||||||
const { default: inquirer } = await import('inquirer');
|
const inquirer = require('inquirer').default || require('inquirer');
|
||||||
let keptCount = 0;
|
let keptCount = 0;
|
||||||
let updatedCount = 0;
|
let updatedCount = 0;
|
||||||
let removedCount = 0;
|
let removedCount = 0;
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ class AntigravitySetup extends BaseIdeSetup {
|
||||||
|
|
||||||
if (config.subagentChoices.install !== 'none') {
|
if (config.subagentChoices.install !== 'none') {
|
||||||
// Ask for installation location
|
// Ask for installation location
|
||||||
const { default: inquirer } = await import('inquirer');
|
const inquirer = require('inquirer').default || require('inquirer');
|
||||||
const locationAnswer = await inquirer.prompt([
|
const locationAnswer = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'list',
|
type: 'list',
|
||||||
|
|
@ -297,7 +297,7 @@ class AntigravitySetup extends BaseIdeSetup {
|
||||||
choices = await this.promptSubagentInstallation(config.subagents);
|
choices = await this.promptSubagentInstallation(config.subagents);
|
||||||
|
|
||||||
if (choices.install !== 'none') {
|
if (choices.install !== 'none') {
|
||||||
const { default: inquirer } = await import('inquirer');
|
const inquirer = require('inquirer').default || require('inquirer');
|
||||||
const locationAnswer = await inquirer.prompt([
|
const locationAnswer = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'list',
|
type: 'list',
|
||||||
|
|
@ -334,7 +334,7 @@ class AntigravitySetup extends BaseIdeSetup {
|
||||||
* Prompt user for subagent installation preferences
|
* Prompt user for subagent installation preferences
|
||||||
*/
|
*/
|
||||||
async promptSubagentInstallation(subagentConfig) {
|
async promptSubagentInstallation(subagentConfig) {
|
||||||
const { default: inquirer } = await import('inquirer');
|
const inquirer = require('inquirer').default || require('inquirer');
|
||||||
|
|
||||||
// First ask if they want to install subagents
|
// First ask if they want to install subagents
|
||||||
const { install } = await inquirer.prompt([
|
const { install } = await inquirer.prompt([
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
||||||
|
|
||||||
if (config.subagentChoices.install !== 'none') {
|
if (config.subagentChoices.install !== 'none') {
|
||||||
// Ask for installation location
|
// Ask for installation location
|
||||||
const { default: inquirer } = await import('inquirer');
|
const inquirer = require('inquirer').default || require('inquirer');
|
||||||
const locationAnswer = await inquirer.prompt([
|
const locationAnswer = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'list',
|
type: 'list',
|
||||||
|
|
@ -305,7 +305,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
||||||
choices = await this.promptSubagentInstallation(config.subagents);
|
choices = await this.promptSubagentInstallation(config.subagents);
|
||||||
|
|
||||||
if (choices.install !== 'none') {
|
if (choices.install !== 'none') {
|
||||||
const { default: inquirer } = await import('inquirer');
|
const inquirer = require('inquirer').default || require('inquirer');
|
||||||
const locationAnswer = await inquirer.prompt([
|
const locationAnswer = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'list',
|
type: 'list',
|
||||||
|
|
@ -342,7 +342,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
||||||
* Prompt user for subagent installation preferences
|
* Prompt user for subagent installation preferences
|
||||||
*/
|
*/
|
||||||
async promptSubagentInstallation(subagentConfig) {
|
async promptSubagentInstallation(subagentConfig) {
|
||||||
const { default: inquirer } = await import('inquirer');
|
const inquirer = require('inquirer').default || require('inquirer');
|
||||||
|
|
||||||
// First ask if they want to install subagents
|
// First ask if they want to install subagents
|
||||||
const { install } = await inquirer.prompt([
|
const { install } = await inquirer.prompt([
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ class CodexSetup extends BaseIdeSetup {
|
||||||
* @returns {Object} Collected configuration
|
* @returns {Object} Collected configuration
|
||||||
*/
|
*/
|
||||||
async collectConfiguration(options = {}) {
|
async collectConfiguration(options = {}) {
|
||||||
const { default: inquirer } = await import('inquirer');
|
const inquirer = require('inquirer').default || require('inquirer');
|
||||||
|
|
||||||
let confirmed = false;
|
let confirmed = false;
|
||||||
let installLocation = 'global';
|
let installLocation = 'global';
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ const { BaseIdeSetup } = require('./_base-ide');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||||
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
||||||
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cursor IDE setup handler
|
* Cursor IDE setup handler
|
||||||
|
|
@ -13,7 +12,6 @@ class CursorSetup extends BaseIdeSetup {
|
||||||
super('cursor', 'Cursor', true); // preferred IDE
|
super('cursor', 'Cursor', true); // preferred IDE
|
||||||
this.configDir = '.cursor';
|
this.configDir = '.cursor';
|
||||||
this.rulesDir = 'rules';
|
this.rulesDir = 'rules';
|
||||||
this.commandsDir = 'commands';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -23,17 +21,11 @@ class CursorSetup extends BaseIdeSetup {
|
||||||
async cleanup(projectDir) {
|
async cleanup(projectDir) {
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const bmadRulesDir = path.join(projectDir, this.configDir, this.rulesDir, 'bmad');
|
const bmadRulesDir = path.join(projectDir, this.configDir, this.rulesDir, 'bmad');
|
||||||
const bmadCommandsDir = path.join(projectDir, this.configDir, this.commandsDir, 'bmad');
|
|
||||||
|
|
||||||
if (await fs.pathExists(bmadRulesDir)) {
|
if (await fs.pathExists(bmadRulesDir)) {
|
||||||
await fs.remove(bmadRulesDir);
|
await fs.remove(bmadRulesDir);
|
||||||
console.log(chalk.dim(` Removed old BMAD rules from ${this.name}`));
|
console.log(chalk.dim(` Removed old BMAD rules from ${this.name}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await fs.pathExists(bmadCommandsDir)) {
|
|
||||||
await fs.remove(bmadCommandsDir);
|
|
||||||
console.log(chalk.dim(` Removed old BMAD commands from ${this.name}`));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -48,76 +40,330 @@ class CursorSetup extends BaseIdeSetup {
|
||||||
// Clean up old BMAD installation first
|
// Clean up old BMAD installation first
|
||||||
await this.cleanup(projectDir);
|
await this.cleanup(projectDir);
|
||||||
|
|
||||||
// Create .cursor/commands directory structure
|
// Create .cursor/rules directory structure
|
||||||
const cursorDir = path.join(projectDir, this.configDir);
|
const cursorDir = path.join(projectDir, this.configDir);
|
||||||
const commandsDir = path.join(cursorDir, this.commandsDir);
|
const rulesDir = path.join(cursorDir, this.rulesDir);
|
||||||
const bmadCommandsDir = path.join(commandsDir, 'bmad');
|
const bmadRulesDir = path.join(rulesDir, 'bmad');
|
||||||
|
|
||||||
await this.ensureDir(bmadCommandsDir);
|
await this.ensureDir(bmadRulesDir);
|
||||||
|
|
||||||
// Generate agent launchers using AgentCommandGenerator
|
// Generate agent launchers first
|
||||||
// This creates small launcher files that reference the actual agents in _bmad/
|
|
||||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||||
const { artifacts: agentArtifacts, counts: agentCounts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||||
|
|
||||||
|
// Convert artifacts to agent format for index creation
|
||||||
|
const agents = agentArtifacts.map((a) => ({ module: a.module, name: a.name }));
|
||||||
|
|
||||||
|
// Get tasks, tools, and workflows (ALL workflows now generate commands)
|
||||||
|
const tasks = await this.getTasks(bmadDir, true);
|
||||||
|
const tools = await this.getTools(bmadDir, true);
|
||||||
|
|
||||||
|
// Get ALL workflows using the new workflow command generator
|
||||||
|
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
|
||||||
|
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
|
||||||
|
|
||||||
|
// Convert artifacts to workflow objects for directory creation
|
||||||
|
const workflows = workflowArtifacts
|
||||||
|
.filter((artifact) => artifact.type === 'workflow-command')
|
||||||
|
.map((artifact) => ({
|
||||||
|
module: artifact.module,
|
||||||
|
name: path.basename(artifact.relativePath, '.md'),
|
||||||
|
path: artifact.sourcePath,
|
||||||
|
}));
|
||||||
|
|
||||||
// Create directories for each module
|
// Create directories for each module
|
||||||
const modules = new Set();
|
const modules = new Set();
|
||||||
for (const artifact of agentArtifacts) {
|
for (const item of [...agents, ...tasks, ...tools, ...workflows]) modules.add(item.module);
|
||||||
modules.add(artifact.module);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const module of modules) {
|
for (const module of modules) {
|
||||||
await this.ensureDir(path.join(bmadCommandsDir, module));
|
await this.ensureDir(path.join(bmadRulesDir, module));
|
||||||
await this.ensureDir(path.join(bmadCommandsDir, module, 'agents'));
|
await this.ensureDir(path.join(bmadRulesDir, module, 'agents'));
|
||||||
|
await this.ensureDir(path.join(bmadRulesDir, module, 'tasks'));
|
||||||
|
await this.ensureDir(path.join(bmadRulesDir, module, 'tools'));
|
||||||
|
await this.ensureDir(path.join(bmadRulesDir, module, 'workflows'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write agent launcher files
|
// Process and write agent launchers with MDC format
|
||||||
const agentCount = await agentGen.writeAgentLaunchers(bmadCommandsDir, agentArtifacts);
|
let agentCount = 0;
|
||||||
|
for (const artifact of agentArtifacts) {
|
||||||
|
// Add MDC metadata header to launcher (but don't call processContent which adds activation headers)
|
||||||
|
const content = this.wrapLauncherWithMDC(artifact.content, {
|
||||||
|
module: artifact.module,
|
||||||
|
name: artifact.name,
|
||||||
|
});
|
||||||
|
|
||||||
// Generate workflow commands from manifest (if it exists)
|
const targetPath = path.join(bmadRulesDir, artifact.module, 'agents', `${artifact.name}.mdc`);
|
||||||
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
|
|
||||||
const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
|
|
||||||
|
|
||||||
// Write only workflow-command artifacts, skip workflow-launcher READMEs
|
await this.writeFile(targetPath, content);
|
||||||
let workflowCommandCount = 0;
|
agentCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process and copy tasks
|
||||||
|
let taskCount = 0;
|
||||||
|
for (const task of tasks) {
|
||||||
|
const content = await this.readAndProcess(task.path, {
|
||||||
|
module: task.module,
|
||||||
|
name: task.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
const targetPath = path.join(bmadRulesDir, task.module, 'tasks', `${task.name}.mdc`);
|
||||||
|
|
||||||
|
await this.writeFile(targetPath, content);
|
||||||
|
taskCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process and copy tools
|
||||||
|
let toolCount = 0;
|
||||||
|
for (const tool of tools) {
|
||||||
|
const content = await this.readAndProcess(tool.path, {
|
||||||
|
module: tool.module,
|
||||||
|
name: tool.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
const targetPath = path.join(bmadRulesDir, tool.module, 'tools', `${tool.name}.mdc`);
|
||||||
|
|
||||||
|
await this.writeFile(targetPath, content);
|
||||||
|
toolCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process and copy workflow commands (generated, not raw workflows)
|
||||||
|
let workflowCount = 0;
|
||||||
for (const artifact of workflowArtifacts) {
|
for (const artifact of workflowArtifacts) {
|
||||||
if (artifact.type === 'workflow-command') {
|
if (artifact.type === 'workflow-command') {
|
||||||
const moduleWorkflowsDir = path.join(bmadCommandsDir, artifact.module, 'workflows');
|
// Add MDC metadata header to workflow command
|
||||||
await this.ensureDir(moduleWorkflowsDir);
|
const content = this.wrapLauncherWithMDC(artifact.content, {
|
||||||
const commandPath = path.join(moduleWorkflowsDir, path.basename(artifact.relativePath));
|
module: artifact.module,
|
||||||
await this.writeFile(commandPath, artifact.content);
|
name: path.basename(artifact.relativePath, '.md'),
|
||||||
workflowCommandCount++;
|
});
|
||||||
|
|
||||||
|
const targetPath = path.join(bmadRulesDir, artifact.module, 'workflows', `${path.basename(artifact.relativePath, '.md')}.mdc`);
|
||||||
|
|
||||||
|
await this.writeFile(targetPath, content);
|
||||||
|
workflowCount++;
|
||||||
}
|
}
|
||||||
// Skip workflow-launcher READMEs as they would be treated as slash commands
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate task and tool commands from manifests (if they exist)
|
// Create BMAD index file (but NOT .cursorrules - user manages that)
|
||||||
const taskToolGen = new TaskToolCommandGenerator();
|
await this.createBMADIndex(bmadRulesDir, agents, tasks, tools, workflows, modules);
|
||||||
const taskToolResult = await taskToolGen.generateTaskToolCommands(projectDir, bmadDir, bmadCommandsDir);
|
|
||||||
|
|
||||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||||
console.log(chalk.dim(` - ${agentCount} agents installed`));
|
console.log(chalk.dim(` - ${agentCount} agents installed`));
|
||||||
if (workflowCommandCount > 0) {
|
console.log(chalk.dim(` - ${taskCount} tasks installed`));
|
||||||
console.log(chalk.dim(` - ${workflowCommandCount} workflow commands generated`));
|
console.log(chalk.dim(` - ${toolCount} tools installed`));
|
||||||
}
|
console.log(chalk.dim(` - ${workflowCount} workflows installed`));
|
||||||
if (taskToolResult.generated > 0) {
|
console.log(chalk.dim(` - Rules directory: ${path.relative(projectDir, bmadRulesDir)}`));
|
||||||
console.log(
|
|
||||||
chalk.dim(
|
|
||||||
` - ${taskToolResult.generated} task/tool commands generated (${taskToolResult.tasks} tasks, ${taskToolResult.tools} tools)`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, bmadCommandsDir)}`));
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
agents: agentCount,
|
agents: agentCount,
|
||||||
tasks: taskToolResult.tasks || 0,
|
tasks: taskCount,
|
||||||
tools: taskToolResult.tools || 0,
|
tools: toolCount,
|
||||||
workflows: workflowCommandCount,
|
workflows: workflowCount,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create BMAD index file for easy navigation
|
||||||
|
*/
|
||||||
|
async createBMADIndex(bmadRulesDir, agents, tasks, tools, workflows, modules) {
|
||||||
|
const indexPath = path.join(bmadRulesDir, 'index.mdc');
|
||||||
|
|
||||||
|
let content = `---
|
||||||
|
description: BMAD Method - Master Index
|
||||||
|
globs:
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# BMAD Method - Cursor Rules Index
|
||||||
|
|
||||||
|
This is the master index for all BMAD agents, tasks, tools, and workflows available in your project.
|
||||||
|
|
||||||
|
## Installation Complete!
|
||||||
|
|
||||||
|
BMAD rules have been installed to: \`.cursor/rules/bmad/\`
|
||||||
|
|
||||||
|
**Note:** BMAD does not modify your \`.cursorrules\` file. You manage that separately.
|
||||||
|
|
||||||
|
## How to Use
|
||||||
|
|
||||||
|
- Reference specific agents: @bmad/{module}/agents/{agent-name}
|
||||||
|
- Reference specific tasks: @bmad/{module}/tasks/{task-name}
|
||||||
|
- Reference specific tools: @bmad/{module}/tools/{tool-name}
|
||||||
|
- Reference specific workflows: @bmad/{module}/workflows/{workflow-name}
|
||||||
|
- Reference entire modules: @bmad/{module}
|
||||||
|
- Reference this index: @bmad/index
|
||||||
|
|
||||||
|
## Available Modules
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
for (const module of modules) {
|
||||||
|
content += `### ${module.toUpperCase()}\n\n`;
|
||||||
|
|
||||||
|
// List agents for this module
|
||||||
|
const moduleAgents = agents.filter((a) => a.module === module);
|
||||||
|
if (moduleAgents.length > 0) {
|
||||||
|
content += `**Agents:**\n`;
|
||||||
|
for (const agent of moduleAgents) {
|
||||||
|
content += `- @bmad/${module}/agents/${agent.name} - ${agent.name}\n`;
|
||||||
|
}
|
||||||
|
content += '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
// List tasks for this module
|
||||||
|
const moduleTasks = tasks.filter((t) => t.module === module);
|
||||||
|
if (moduleTasks.length > 0) {
|
||||||
|
content += `**Tasks:**\n`;
|
||||||
|
for (const task of moduleTasks) {
|
||||||
|
content += `- @bmad/${module}/tasks/${task.name} - ${task.name}\n`;
|
||||||
|
}
|
||||||
|
content += '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
// List tools for this module
|
||||||
|
const moduleTools = tools.filter((t) => t.module === module);
|
||||||
|
if (moduleTools.length > 0) {
|
||||||
|
content += `**Tools:**\n`;
|
||||||
|
for (const tool of moduleTools) {
|
||||||
|
content += `- @bmad/${module}/tools/${tool.name} - ${tool.name}\n`;
|
||||||
|
}
|
||||||
|
content += '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
// List workflows for this module
|
||||||
|
const moduleWorkflows = workflows.filter((w) => w.module === module);
|
||||||
|
if (moduleWorkflows.length > 0) {
|
||||||
|
content += `**Workflows:**\n`;
|
||||||
|
for (const workflow of moduleWorkflows) {
|
||||||
|
content += `- @bmad/${module}/workflows/${workflow.name} - ${workflow.name}\n`;
|
||||||
|
}
|
||||||
|
content += '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content += `
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
- All BMAD rules are Manual type - reference them explicitly when needed
|
||||||
|
- Agents provide persona-based assistance with specific expertise
|
||||||
|
- Tasks are reusable workflows for common operations
|
||||||
|
- Tools provide specialized functionality
|
||||||
|
- Workflows orchestrate multi-step processes
|
||||||
|
- Each agent includes an activation block for proper initialization
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
BMAD rules are configured as Manual rules (alwaysApply: false) to give you control
|
||||||
|
over when they're included in your context. Reference them explicitly when you need
|
||||||
|
specific agent expertise, task workflows, tools, or guided workflows.
|
||||||
|
`;
|
||||||
|
|
||||||
|
await this.writeFile(indexPath, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read and process file content
|
||||||
|
*/
|
||||||
|
async readAndProcess(filePath, metadata) {
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const content = await fs.readFile(filePath, 'utf8');
|
||||||
|
return this.processContent(content, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override processContent to add MDC metadata header for Cursor
|
||||||
|
* @param {string} content - File content
|
||||||
|
* @param {Object} metadata - File metadata
|
||||||
|
* @returns {string} Processed content with MDC header
|
||||||
|
*/
|
||||||
|
processContent(content, metadata = {}) {
|
||||||
|
// First apply base processing (includes activation injection for agents)
|
||||||
|
let processed = super.processContent(content, metadata);
|
||||||
|
|
||||||
|
// Strip any existing frontmatter from the processed content
|
||||||
|
// This prevents duplicate frontmatter blocks
|
||||||
|
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
|
||||||
|
if (frontmatterRegex.test(processed)) {
|
||||||
|
processed = processed.replace(frontmatterRegex, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the type and description based on content
|
||||||
|
const isAgent = content.includes('<agent');
|
||||||
|
const isTask = content.includes('<task');
|
||||||
|
const isTool = content.includes('<tool');
|
||||||
|
const isWorkflow = content.includes('workflow:') || content.includes('name:');
|
||||||
|
|
||||||
|
let description = '';
|
||||||
|
let globs = '';
|
||||||
|
|
||||||
|
if (isAgent) {
|
||||||
|
// Extract agent title if available
|
||||||
|
const titleMatch = content.match(/title="([^"]+)"/);
|
||||||
|
const title = titleMatch ? titleMatch[1] : metadata.name;
|
||||||
|
description = `BMAD ${metadata.module.toUpperCase()} Agent: ${title}`;
|
||||||
|
globs = '';
|
||||||
|
} else if (isTask) {
|
||||||
|
// Extract task name if available
|
||||||
|
const nameMatch = content.match(/name="([^"]+)"/);
|
||||||
|
const taskName = nameMatch ? nameMatch[1] : metadata.name;
|
||||||
|
description = `BMAD ${metadata.module.toUpperCase()} Task: ${taskName}`;
|
||||||
|
globs = '';
|
||||||
|
} else if (isTool) {
|
||||||
|
// Extract tool name if available
|
||||||
|
const nameMatch = content.match(/name="([^"]+)"/);
|
||||||
|
const toolName = nameMatch ? nameMatch[1] : metadata.name;
|
||||||
|
description = `BMAD ${metadata.module.toUpperCase()} Tool: ${toolName}`;
|
||||||
|
globs = '';
|
||||||
|
} else if (isWorkflow) {
|
||||||
|
// Workflow
|
||||||
|
description = `BMAD ${metadata.module.toUpperCase()} Workflow: ${metadata.name}`;
|
||||||
|
globs = '';
|
||||||
|
} else {
|
||||||
|
description = `BMAD ${metadata.module.toUpperCase()}: ${metadata.name}`;
|
||||||
|
globs = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create MDC metadata header
|
||||||
|
const mdcHeader = `---
|
||||||
|
description: ${description}
|
||||||
|
globs: ${globs}
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add the MDC header to the processed content
|
||||||
|
return mdcHeader + processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap launcher content with MDC metadata (without base processing)
|
||||||
|
* Launchers are already complete and should not have activation headers injected
|
||||||
|
*/
|
||||||
|
wrapLauncherWithMDC(launcherContent, metadata = {}) {
|
||||||
|
// Strip the launcher's frontmatter - we'll replace it with MDC frontmatter
|
||||||
|
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
|
||||||
|
const contentWithoutFrontmatter = launcherContent.replace(frontmatterRegex, '');
|
||||||
|
|
||||||
|
// Extract metadata from launcher frontmatter for MDC description
|
||||||
|
const nameMatch = launcherContent.match(/name:\s*"([^"]+)"/);
|
||||||
|
const name = nameMatch ? nameMatch[1] : metadata.name;
|
||||||
|
|
||||||
|
const description = `BMAD ${metadata.module.toUpperCase()} Agent: ${name}`;
|
||||||
|
|
||||||
|
// Create MDC metadata header
|
||||||
|
const mdcHeader = `---
|
||||||
|
description: ${description}
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Return MDC header + launcher content (without its original frontmatter)
|
||||||
|
return mdcHeader + contentWithoutFrontmatter;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Install a custom agent launcher for Cursor
|
* Install a custom agent launcher for Cursor
|
||||||
* @param {string} projectDir - Project directory
|
* @param {string} projectDir - Project directory
|
||||||
|
|
@ -127,7 +373,7 @@ class CursorSetup extends BaseIdeSetup {
|
||||||
* @returns {Object|null} Info about created command
|
* @returns {Object|null} Info about created command
|
||||||
*/
|
*/
|
||||||
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
||||||
const customAgentsDir = path.join(projectDir, this.configDir, this.commandsDir, 'bmad', 'custom', 'agents');
|
const customAgentsDir = path.join(projectDir, this.configDir, this.rulesDir, 'bmad', 'custom', 'agents');
|
||||||
|
|
||||||
if (!(await this.exists(path.join(projectDir, this.configDir)))) {
|
if (!(await this.exists(path.join(projectDir, this.configDir)))) {
|
||||||
return null; // IDE not configured for this project
|
return null; // IDE not configured for this project
|
||||||
|
|
@ -147,21 +393,22 @@ class CursorSetup extends BaseIdeSetup {
|
||||||
</agent-activation>
|
</agent-activation>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Cursor uses YAML frontmatter matching Claude Code format
|
// Cursor uses MDC format with metadata header
|
||||||
const commandContent = `---
|
const mdcContent = `---
|
||||||
name: '${agentName}'
|
description: "${agentName} agent"
|
||||||
description: '${agentName} agent'
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
${launcherContent}
|
${launcherContent}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const launcherPath = path.join(customAgentsDir, `${agentName}.md`);
|
const launcherPath = path.join(customAgentsDir, `${agentName}.mdc`);
|
||||||
await this.writeFile(launcherPath, commandContent);
|
await this.writeFile(launcherPath, mdcContent);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
path: launcherPath,
|
path: launcherPath,
|
||||||
command: `/bmad/custom/agents/${agentName}`,
|
command: `@${agentName}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const { BaseIdeSetup } = require('./_base-ide');
|
const { BaseIdeSetup } = require('./_base-ide');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
|
const inquirer = require('inquirer').default || require('inquirer');
|
||||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -21,7 +22,6 @@ class GitHubCopilotSetup extends BaseIdeSetup {
|
||||||
* @returns {Object} Collected configuration
|
* @returns {Object} Collected configuration
|
||||||
*/
|
*/
|
||||||
async collectConfiguration(options = {}) {
|
async collectConfiguration(options = {}) {
|
||||||
const { default: inquirer } = await import('inquirer');
|
|
||||||
const config = {};
|
const config = {};
|
||||||
|
|
||||||
console.log('\n' + chalk.blue(' 🔧 VS Code Settings Configuration'));
|
console.log('\n' + chalk.blue(' 🔧 VS Code Settings Configuration'));
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,11 @@
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
|
const inquirer = require('inquirer').default || require('inquirer');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const os = require('node:os');
|
const os = require('node:os');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const { CLIUtils } = require('./cli-utils');
|
const { CLIUtils } = require('./cli-utils');
|
||||||
const { CustomHandler } = require('../installers/lib/custom/handler');
|
const { CustomHandler } = require('../installers/lib/custom/handler');
|
||||||
|
|
||||||
// Lazy-load inquirer (ESM module) to avoid ERR_REQUIRE_ESM
|
|
||||||
let _inquirer = null;
|
|
||||||
async function getInquirer() {
|
|
||||||
if (!_inquirer) {
|
|
||||||
_inquirer = (await import('inquirer')).default;
|
|
||||||
}
|
|
||||||
return _inquirer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UI utilities for the installer
|
* UI utilities for the installer
|
||||||
*/
|
*/
|
||||||
|
|
@ -23,7 +15,6 @@ class UI {
|
||||||
* @returns {Object} Installation configuration
|
* @returns {Object} Installation configuration
|
||||||
*/
|
*/
|
||||||
async promptInstall() {
|
async promptInstall() {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
CLIUtils.displayLogo();
|
CLIUtils.displayLogo();
|
||||||
|
|
||||||
// Display version-specific start message from install-messages.yaml
|
// Display version-specific start message from install-messages.yaml
|
||||||
|
|
@ -459,7 +450,6 @@ class UI {
|
||||||
* @returns {Object} Tool configuration
|
* @returns {Object} Tool configuration
|
||||||
*/
|
*/
|
||||||
async promptToolSelection(projectDir, selectedModules) {
|
async promptToolSelection(projectDir, selectedModules) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
// Check for existing configured IDEs - use findBmadDir to detect custom folder names
|
// Check for existing configured IDEs - use findBmadDir to detect custom folder names
|
||||||
const { Detector } = require('../installers/lib/core/detector');
|
const { Detector } = require('../installers/lib/core/detector');
|
||||||
const { Installer } = require('../installers/lib/core/installer');
|
const { Installer } = require('../installers/lib/core/installer');
|
||||||
|
|
@ -592,7 +582,6 @@ class UI {
|
||||||
* @returns {Object} Update configuration
|
* @returns {Object} Update configuration
|
||||||
*/
|
*/
|
||||||
async promptUpdate() {
|
async promptUpdate() {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
const answers = await inquirer.prompt([
|
const answers = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'confirm',
|
type: 'confirm',
|
||||||
|
|
@ -617,7 +606,6 @@ class UI {
|
||||||
* @returns {Array} Selected modules
|
* @returns {Array} Selected modules
|
||||||
*/
|
*/
|
||||||
async promptModules(modules) {
|
async promptModules(modules) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
const choices = modules.map((mod) => ({
|
const choices = modules.map((mod) => ({
|
||||||
name: `${mod.name} - ${mod.description}`,
|
name: `${mod.name} - ${mod.description}`,
|
||||||
value: mod.id,
|
value: mod.id,
|
||||||
|
|
@ -649,7 +637,6 @@ class UI {
|
||||||
* @returns {boolean} User confirmation
|
* @returns {boolean} User confirmation
|
||||||
*/
|
*/
|
||||||
async confirm(message, defaultValue = false) {
|
async confirm(message, defaultValue = false) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
const { confirmed } = await inquirer.prompt([
|
const { confirmed } = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'confirm',
|
type: 'confirm',
|
||||||
|
|
@ -756,7 +743,6 @@ class UI {
|
||||||
* @returns {Array} Module choices for inquirer
|
* @returns {Array} Module choices for inquirer
|
||||||
*/
|
*/
|
||||||
async getModuleChoices(installedModuleIds, customContentConfig = null) {
|
async getModuleChoices(installedModuleIds, customContentConfig = null) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
const moduleChoices = [];
|
const moduleChoices = [];
|
||||||
const isNewInstallation = installedModuleIds.size === 0;
|
const isNewInstallation = installedModuleIds.size === 0;
|
||||||
|
|
||||||
|
|
@ -837,7 +823,6 @@ class UI {
|
||||||
* @returns {Array} Selected module IDs
|
* @returns {Array} Selected module IDs
|
||||||
*/
|
*/
|
||||||
async selectModules(moduleChoices, defaultSelections = []) {
|
async selectModules(moduleChoices, defaultSelections = []) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
const moduleAnswer = await inquirer.prompt([
|
const moduleAnswer = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
|
|
@ -858,7 +843,6 @@ class UI {
|
||||||
* @returns {Object} Directory answer from inquirer
|
* @returns {Object} Directory answer from inquirer
|
||||||
*/
|
*/
|
||||||
async promptForDirectory() {
|
async promptForDirectory() {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
return await inquirer.prompt([
|
return await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
|
|
@ -915,7 +899,6 @@ class UI {
|
||||||
* @returns {boolean} Whether user confirmed
|
* @returns {boolean} Whether user confirmed
|
||||||
*/
|
*/
|
||||||
async confirmDirectory(directory) {
|
async confirmDirectory(directory) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
const dirExists = await fs.pathExists(directory);
|
const dirExists = await fs.pathExists(directory);
|
||||||
|
|
||||||
if (dirExists) {
|
if (dirExists) {
|
||||||
|
|
@ -1102,7 +1085,6 @@ class UI {
|
||||||
* - GitHub Issue: paulpreibisch/AgentVibes#36
|
* - GitHub Issue: paulpreibisch/AgentVibes#36
|
||||||
*/
|
*/
|
||||||
async promptAgentVibes(projectDir) {
|
async promptAgentVibes(projectDir) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
CLIUtils.displaySection('🎤 Voice Features', 'Enable TTS for multi-agent conversations');
|
CLIUtils.displaySection('🎤 Voice Features', 'Enable TTS for multi-agent conversations');
|
||||||
|
|
||||||
// Check if AgentVibes is already installed
|
// Check if AgentVibes is already installed
|
||||||
|
|
@ -1253,7 +1235,6 @@ class UI {
|
||||||
* @returns {Object} Custom content configuration
|
* @returns {Object} Custom content configuration
|
||||||
*/
|
*/
|
||||||
async promptCustomContentSource() {
|
async promptCustomContentSource() {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
const customContentConfig = { hasCustomContent: true, sources: [] };
|
const customContentConfig = { hasCustomContent: true, sources: [] };
|
||||||
|
|
||||||
// Keep asking for more sources until user is done
|
// Keep asking for more sources until user is done
|
||||||
|
|
@ -1391,7 +1372,6 @@ class UI {
|
||||||
* @returns {Object} Result with selected custom modules and custom content config
|
* @returns {Object} Result with selected custom modules and custom content config
|
||||||
*/
|
*/
|
||||||
async handleCustomModulesInModifyFlow(directory, selectedModules) {
|
async handleCustomModulesInModifyFlow(directory, selectedModules) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
// Get existing installation to find custom modules
|
// Get existing installation to find custom modules
|
||||||
const { existingInstall } = await this.getExistingInstallation(directory);
|
const { existingInstall } = await this.getExistingInstallation(directory);
|
||||||
|
|
||||||
|
|
@ -1586,7 +1566,6 @@ class UI {
|
||||||
* @returns {Promise<boolean>} True if user wants to proceed, false if they cancel
|
* @returns {Promise<boolean>} True if user wants to proceed, false if they cancel
|
||||||
*/
|
*/
|
||||||
async showOldAlphaVersionWarning(installedVersion, currentVersion, bmadFolderName) {
|
async showOldAlphaVersionWarning(installedVersion, currentVersion, bmadFolderName) {
|
||||||
const inquirer = await getInquirer();
|
|
||||||
const versionInfo = this.checkAlphaVersionAge(installedVersion, currentVersion);
|
const versionInfo = this.checkAlphaVersionAge(installedVersion, currentVersion);
|
||||||
|
|
||||||
// Also warn if version is unknown or can't be parsed (legacy/unsupported)
|
// Also warn if version is unknown or can't be parsed (legacy/unsupported)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue