Compare commits
3 Commits
f25fcc686c
...
966ca5db0b
| Author | SHA1 | Date |
|---|---|---|
|
|
966ca5db0b | |
|
|
e0318d9da8 | |
|
|
4a983d64a7 |
|
|
@ -61,6 +61,7 @@ _bmad-output
|
||||||
.claude
|
.claude
|
||||||
.codex
|
.codex
|
||||||
.github/chatmodes
|
.github/chatmodes
|
||||||
|
.github/agents
|
||||||
.agent
|
.agent
|
||||||
.agentvibes/
|
.agentvibes/
|
||||||
.kiro/
|
.kiro/
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# BMad Method
|

|
||||||
|
|
||||||
[](https://www.npmjs.com/package/bmad-method)
|
[](https://www.npmjs.com/package/bmad-method)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 366 KiB |
|
|
@ -13,17 +13,15 @@ By the end of this 30-minute tutorial, you'll have:
|
||||||
- Passing tests for an existing demo app feature
|
- Passing tests for an existing demo app feature
|
||||||
|
|
||||||
:::note[Prerequisites]
|
:::note[Prerequisites]
|
||||||
- Node.js installed (v18 or later)
|
- Node.js installed (v20 or later)
|
||||||
- 30 minutes of focused time
|
- 30 minutes of focused time
|
||||||
|
- We'll use TodoMVC (<https://todomvc.com/examples/react/>) as our demo app
|
||||||
- We'll use TodoMVC (<https://todomvc.com/examples/react/dist/>) as our demo app
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
:::tip[Quick Path]
|
:::tip[Quick Path]
|
||||||
Load TEA (`*tea`) → scaffold framework (`*framework`) → create test plan (`*test-design`) → generate tests (`*automate`) → run with `npx playwright test`.
|
Load TEA (`*tea`) → scaffold framework (`*framework`) → create test plan (`*test-design`) → generate tests (`*automate`) → run with `npx playwright test`.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
|
||||||
## TEA Approaches Explained
|
## TEA Approaches Explained
|
||||||
|
|
||||||
Before we start, understand the three ways to use TEA:
|
Before we start, understand the three ways to use TEA:
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ 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 ora = require('ora');
|
||||||
const { XmlHandler } = require('../../../lib/xml-handler');
|
const { XmlHandler } = require('../../../lib/xml-handler');
|
||||||
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
|
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
|
||||||
const { filterCustomizationData } = require('../../../lib/agent/compiler');
|
const { filterCustomizationData } = require('../../../lib/agent/compiler');
|
||||||
|
|
@ -414,27 +415,48 @@ class ModuleManager {
|
||||||
// Create cache directory if it doesn't exist
|
// Create cache directory if it doesn't exist
|
||||||
await fs.ensureDir(cacheDir);
|
await fs.ensureDir(cacheDir);
|
||||||
|
|
||||||
|
// Track if we need to install dependencies
|
||||||
|
let needsDependencyInstall = false;
|
||||||
|
let wasNewClone = false;
|
||||||
|
|
||||||
// Check if already cloned
|
// Check if already cloned
|
||||||
if (await fs.pathExists(moduleCacheDir)) {
|
if (await fs.pathExists(moduleCacheDir)) {
|
||||||
// Try to update if it's a git repo
|
// Try to update if it's a git repo
|
||||||
|
const updateSpinner = ora(`Updating ${moduleInfo.name} from remote repository...`).start();
|
||||||
try {
|
try {
|
||||||
|
const currentRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
||||||
execSync('git fetch --depth 1', { cwd: moduleCacheDir, stdio: 'pipe' });
|
execSync('git fetch --depth 1', { cwd: moduleCacheDir, stdio: 'pipe' });
|
||||||
execSync('git checkout -f', { cwd: moduleCacheDir, stdio: 'pipe' });
|
execSync('git checkout -f', { cwd: moduleCacheDir, stdio: 'pipe' });
|
||||||
execSync('git pull --ff-only', { cwd: moduleCacheDir, stdio: 'pipe' });
|
execSync('git pull --ff-only', { cwd: moduleCacheDir, stdio: 'pipe' });
|
||||||
|
const newRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
||||||
|
|
||||||
|
if (currentRef === newRef) {
|
||||||
|
updateSpinner.succeed(`${moduleInfo.name} is already up to date`);
|
||||||
|
} else {
|
||||||
|
updateSpinner.succeed(`Updated ${moduleInfo.name} to latest version`);
|
||||||
|
// Force dependency install since we got new code
|
||||||
|
needsDependencyInstall = true;
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
updateSpinner.warn(`Update failed, re-downloading ${moduleInfo.name}`);
|
||||||
// If update fails, remove and re-clone
|
// If update fails, remove and re-clone
|
||||||
await fs.remove(moduleCacheDir);
|
await fs.remove(moduleCacheDir);
|
||||||
|
wasNewClone = true;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
wasNewClone = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone if not exists or was removed
|
// Clone if not exists or was removed
|
||||||
if (!(await fs.pathExists(moduleCacheDir))) {
|
if (wasNewClone) {
|
||||||
console.log(chalk.dim(` Cloning external module: ${moduleInfo.name}`));
|
const cloneSpinner = ora(`Downloading ${moduleInfo.name} from remote repository...`).start();
|
||||||
try {
|
try {
|
||||||
execSync(`git clone --depth 1 "${moduleInfo.url}" "${moduleCacheDir}"`, {
|
execSync(`git clone --depth 1 "${moduleInfo.url}" "${moduleCacheDir}"`, {
|
||||||
stdio: 'pipe',
|
stdio: 'pipe',
|
||||||
});
|
});
|
||||||
|
cloneSpinner.succeed(`Downloaded ${moduleInfo.name}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
cloneSpinner.fail(`Failed to download ${moduleInfo.name}`);
|
||||||
throw new Error(`Failed to clone external module '${moduleCode}': ${error.message}`);
|
throw new Error(`Failed to clone external module '${moduleCode}': ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -444,11 +466,25 @@ class ModuleManager {
|
||||||
const nodeModulesPath = path.join(moduleCacheDir, 'node_modules');
|
const nodeModulesPath = path.join(moduleCacheDir, 'node_modules');
|
||||||
if (await fs.pathExists(packageJsonPath)) {
|
if (await fs.pathExists(packageJsonPath)) {
|
||||||
// Install if node_modules doesn't exist, or if package.json is newer (dependencies changed)
|
// Install if node_modules doesn't exist, or if package.json is newer (dependencies changed)
|
||||||
const needsInstall = !(await fs.pathExists(nodeModulesPath));
|
const nodeModulesMissing = !(await fs.pathExists(nodeModulesPath));
|
||||||
let packageJsonNewer = false;
|
|
||||||
|
|
||||||
if (!needsInstall) {
|
// Force install if we updated or cloned new
|
||||||
|
if (needsDependencyInstall || wasNewClone || nodeModulesMissing) {
|
||||||
|
const installSpinner = ora(`Installing dependencies for ${moduleInfo.name}...`).start();
|
||||||
|
try {
|
||||||
|
execSync('npm install --production --no-audit --no-fund --prefer-offline --no-progress', {
|
||||||
|
cwd: moduleCacheDir,
|
||||||
|
stdio: 'pipe',
|
||||||
|
timeout: 120_000, // 2 minute timeout
|
||||||
|
});
|
||||||
|
installSpinner.succeed(`Installed dependencies for ${moduleInfo.name}`);
|
||||||
|
} catch (error) {
|
||||||
|
installSpinner.warn(`Failed to install dependencies for ${moduleInfo.name}`);
|
||||||
|
console.warn(chalk.yellow(` Warning: ${error.message}`));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
// Check if package.json is newer than node_modules
|
// Check if package.json is newer than node_modules
|
||||||
|
let packageJsonNewer = false;
|
||||||
try {
|
try {
|
||||||
const packageStats = await fs.stat(packageJsonPath);
|
const packageStats = await fs.stat(packageJsonPath);
|
||||||
const nodeModulesStats = await fs.stat(nodeModulesPath);
|
const nodeModulesStats = await fs.stat(nodeModulesPath);
|
||||||
|
|
@ -457,18 +493,20 @@ class ModuleManager {
|
||||||
// If stat fails, assume we need to install
|
// If stat fails, assume we need to install
|
||||||
packageJsonNewer = true;
|
packageJsonNewer = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (needsInstall || packageJsonNewer) {
|
if (packageJsonNewer) {
|
||||||
console.log(chalk.dim(` Installing dependencies for ${moduleInfo.name}...`));
|
const installSpinner = ora(`Installing dependencies for ${moduleInfo.name}...`).start();
|
||||||
try {
|
try {
|
||||||
execSync('npm install --production --no-audit --no-fund --prefer-offline --no-progress', {
|
execSync('npm install --production --no-audit --no-fund --prefer-offline --no-progress', {
|
||||||
cwd: moduleCacheDir,
|
cwd: moduleCacheDir,
|
||||||
stdio: 'inherit',
|
stdio: 'pipe',
|
||||||
timeout: 120_000, // 2 minute timeout
|
timeout: 120_000, // 2 minute timeout
|
||||||
});
|
});
|
||||||
|
installSpinner.succeed(`Installed dependencies for ${moduleInfo.name}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(chalk.yellow(` Warning: Failed to install dependencies for ${moduleInfo.name}: ${error.message}`));
|
installSpinner.warn(`Failed to install dependencies for ${moduleInfo.name}`);
|
||||||
|
console.warn(chalk.yellow(` Warning: ${error.message}`));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,10 @@ export default defineConfig({
|
||||||
tagline: 'AI-driven agile development with specialized agents and workflows that scale from bug fixes to enterprise platforms.',
|
tagline: 'AI-driven agile development with specialized agents and workflows that scale from bug fixes to enterprise platforms.',
|
||||||
|
|
||||||
logo: {
|
logo: {
|
||||||
src: './public/img/logo.svg',
|
light: './public/img/bmad-light.png',
|
||||||
alt: 'BMAD Logo',
|
dark: './public/img/bmad-dark.png',
|
||||||
|
alt: 'BMAD Method',
|
||||||
|
replacesTitle: true,
|
||||||
},
|
},
|
||||||
favicon: '/favicon.ico',
|
favicon: '/favicon.ico',
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
|
|
@ -222,6 +222,8 @@ header.header .header.sl-flex {
|
||||||
|
|
||||||
.site-title {
|
.site-title {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Logo sizing - constrain to reasonable size */
|
/* Logo sizing - constrain to reasonable size */
|
||||||
|
|
@ -470,14 +472,14 @@ footer {
|
||||||
/* Responsive padding on navbar row only - banner stays full-width */
|
/* Responsive padding on navbar row only - banner stays full-width */
|
||||||
@media (min-width: 50rem) {
|
@media (min-width: 50rem) {
|
||||||
header.header .header.sl-flex {
|
header.header .header.sl-flex {
|
||||||
padding-left: 2.5rem;
|
padding-left: 1rem;
|
||||||
padding-right: 2.5rem;
|
padding-right: 2.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 72rem) {
|
@media (min-width: 72rem) {
|
||||||
header.header .header.sl-flex {
|
header.header .header.sl-flex {
|
||||||
padding-left: 3rem;
|
padding-left: 1rem;
|
||||||
padding-right: 3rem;
|
padding-right: 3rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue