chore: alphabetize npm scripts and fix test file formatting

This commit is contained in:
Magal 2026-05-21 11:36:19 -03:00
parent ea813716e2
commit 58c2e460c8
3 changed files with 105 additions and 75 deletions

View File

@ -36,9 +36,9 @@
"format:fix:staged": "prettier --write", "format:fix:staged": "prettier --write",
"install:bmad": "node tools/installer/bmad-cli.js install", "install:bmad": "node tools/installer/bmad-cli.js install",
"lint": "eslint . --ext .js,.cjs,.mjs,.yaml --max-warnings=0", "lint": "eslint . --ext .js,.cjs,.mjs,.yaml --max-warnings=0",
"memtrace:restart": "node _bmad/scripts/memtrace/memtrace-restart.mjs",
"lint:fix": "eslint . --ext .js,.cjs,.mjs,.yaml --fix", "lint:fix": "eslint . --ext .js,.cjs,.mjs,.yaml --fix",
"lint:md": "markdownlint-cli2 \"**/*.md\"", "lint:md": "markdownlint-cli2 \"**/*.md\"",
"memtrace:restart": "node _bmad/scripts/memtrace/memtrace-restart.mjs",
"prepare": "command -v husky >/dev/null 2>&1 && husky || exit 0", "prepare": "command -v husky >/dev/null 2>&1 && husky || exit 0",
"quality": "npm run format:check && npm run lint && npm run lint:md && npm run docs:build && npm run test:install && npm run test:urls && npm run validate:refs && npm run validate:skills", "quality": "npm run format:check && npm run lint && npm run lint:md && npm run docs:build && npm run test:install && npm run test:urls && npm run validate:refs && npm run validate:skills",
"rebundle": "node tools/installer/bundlers/bundle-web.js rebundle", "rebundle": "node tools/installer/bundlers/bundle-web.js rebundle",

View File

@ -43,15 +43,19 @@ function runInjector(mode, envOverrides = {}) {
const scriptPath = path.resolve(__dirname, '../_bmad/scripts/memtrace/inject-mcp-config.mjs'); const scriptPath = path.resolve(__dirname, '../_bmad/scripts/memtrace/inject-mcp-config.mjs');
const command = `node "${scriptPath}" --mode ${mode}`; const command = `node "${scriptPath}" --mode ${mode}`;
exec(command, { exec(
env: { ...process.env, ...envOverrides } command,
}, (error, stdout, stderr) => { {
if (error) { env: { ...process.env, ...envOverrides },
reject({ error, stdout, stderr }); },
} else { (error, stdout, stderr) => {
resolve({ stdout, stderr }); if (error) {
} reject({ error, stdout, stderr });
}); } else {
resolve({ stdout, stderr });
}
},
);
}); });
} }
@ -77,14 +81,14 @@ async function runTests() {
assert( assert(
config.mcpServers !== undefined && config.mcpServers.memtrace !== undefined, config.mcpServers !== undefined && config.mcpServers.memtrace !== undefined,
'Test 1.1: Creates new Claude config file and injects memtrace server skeleton' 'Test 1.1: Creates new Claude config file and injects memtrace server skeleton',
); );
assert( assert(
config.mcpServers.memtrace.command === 'memtrace' && config.mcpServers.memtrace.args[0] === 'mcp', config.mcpServers.memtrace.command === 'memtrace' && config.mcpServers.memtrace.args[0] === 'mcp',
'Test 1.1: Injected server details match expected schema format' 'Test 1.1: Injected server details match expected schema format',
); );
} catch (err) { } catch (error) {
assert(false, 'Test 1.1 Failed with error', err.message || JSON.stringify(err)); assert(false, 'Test 1.1 Failed with error', error.message || JSON.stringify(error));
} }
// Test 1.2: File exists with other servers (preserves other servers) // Test 1.2: File exists with other servers (preserves other servers)
@ -93,9 +97,9 @@ async function runTests() {
mcpServers: { mcpServers: {
otherServer: { otherServer: {
command: 'node', command: 'node',
args: ['other-path/server.js'] args: ['other-path/server.js'],
} },
} },
}; };
await fs.writeFile(claudeTestFile, JSON.stringify(preExistingConfig, null, 2), 'utf8'); await fs.writeFile(claudeTestFile, JSON.stringify(preExistingConfig, null, 2), 'utf8');
@ -105,14 +109,14 @@ async function runTests() {
assert( assert(
config.mcpServers.otherServer !== undefined && config.mcpServers.otherServer.command === 'node', config.mcpServers.otherServer !== undefined && config.mcpServers.otherServer.command === 'node',
'Test 1.2: Preserves pre-existing mcpServers in Claude config' 'Test 1.2: Preserves pre-existing mcpServers in Claude config',
); );
assert( assert(
config.mcpServers.memtrace !== undefined && config.mcpServers.memtrace.command === 'memtrace', config.mcpServers.memtrace !== undefined && config.mcpServers.memtrace.command === 'memtrace',
'Test 1.2: Correctly appends memtrace server configuration alongside existing ones' 'Test 1.2: Correctly appends memtrace server configuration alongside existing ones',
); );
} catch (err) { } catch (error) {
assert(false, 'Test 1.2 Failed with error', err.message || JSON.stringify(err)); assert(false, 'Test 1.2 Failed with error', error.message || JSON.stringify(error));
} }
// Test 1.3: File exists and memtrace key already exists (overwrites memtrace key only) // Test 1.3: File exists and memtrace key already exists (overwrites memtrace key only)
@ -121,13 +125,13 @@ async function runTests() {
mcpServers: { mcpServers: {
otherServer: { otherServer: {
command: 'node', command: 'node',
args: ['other-path/server.js'] args: ['other-path/server.js'],
}, },
memtrace: { memtrace: {
command: 'old-command', command: 'old-command',
args: ['old-arg'] args: ['old-arg'],
} },
} },
}; };
await fs.writeFile(claudeTestFile, JSON.stringify(preExistingConfig, null, 2), 'utf8'); await fs.writeFile(claudeTestFile, JSON.stringify(preExistingConfig, null, 2), 'utf8');
@ -137,14 +141,14 @@ async function runTests() {
assert( assert(
config.mcpServers.otherServer !== undefined && config.mcpServers.otherServer.command === 'node', config.mcpServers.otherServer !== undefined && config.mcpServers.otherServer.command === 'node',
'Test 1.3: Overwriting preserves other servers' 'Test 1.3: Overwriting preserves other servers',
); );
assert( assert(
config.mcpServers.memtrace.command === 'memtrace' && config.mcpServers.memtrace.args[0] === 'mcp', config.mcpServers.memtrace.command === 'memtrace' && config.mcpServers.memtrace.args[0] === 'mcp',
'Test 1.3: Correctly overwrites only the memtrace key' 'Test 1.3: Correctly overwrites only the memtrace key',
); );
} catch (err) { } catch (error) {
assert(false, 'Test 1.3 Failed with error', err.message || JSON.stringify(err)); assert(false, 'Test 1.3 Failed with error', error.message || JSON.stringify(error));
} }
console.log(''); console.log('');
@ -164,14 +168,14 @@ async function runTests() {
assert( assert(
config.mcp !== undefined && config.mcp.memtrace !== undefined, config.mcp !== undefined && config.mcp.memtrace !== undefined,
'Test 2.1: Creates new OpenCode config file and injects memtrace server skeleton' 'Test 2.1: Creates new OpenCode config file and injects memtrace server skeleton',
); );
assert( assert(
config.mcp.memtrace.type === 'local' && config.mcp.memtrace.command[0] === 'memtrace' && config.mcp.memtrace.command[1] === 'mcp', config.mcp.memtrace.type === 'local' && config.mcp.memtrace.command[0] === 'memtrace' && config.mcp.memtrace.command[1] === 'mcp',
'Test 2.1: Injected server details match expected OpenCode schema format' 'Test 2.1: Injected server details match expected OpenCode schema format',
); );
} catch (err) { } catch (error) {
assert(false, 'Test 2.1 Failed with error', err.message || JSON.stringify(err)); assert(false, 'Test 2.1 Failed with error', error.message || JSON.stringify(error));
} }
// Test 2.2: File exists with other keys (preserves other keys) // Test 2.2: File exists with other keys (preserves other keys)
@ -180,9 +184,9 @@ async function runTests() {
mcp: { mcp: {
otherServer: { otherServer: {
type: 'local', type: 'local',
command: ['other-server'] command: ['other-server'],
} },
} },
}; };
await fs.writeFile(opencodeTestFile, JSON.stringify(preExistingConfig, null, 2), 'utf8'); await fs.writeFile(opencodeTestFile, JSON.stringify(preExistingConfig, null, 2), 'utf8');
@ -192,14 +196,14 @@ async function runTests() {
assert( assert(
config.mcp.otherServer !== undefined && config.mcp.otherServer.type === 'local', config.mcp.otherServer !== undefined && config.mcp.otherServer.type === 'local',
'Test 2.2: Preserves pre-existing mcp in OpenCode config' 'Test 2.2: Preserves pre-existing mcp in OpenCode config',
); );
assert( assert(
config.mcp.memtrace !== undefined && config.mcp.memtrace.type === 'local', config.mcp.memtrace !== undefined && config.mcp.memtrace.type === 'local',
'Test 2.2: Correctly appends memtrace server configuration alongside existing ones' 'Test 2.2: Correctly appends memtrace server configuration alongside existing ones',
); );
} catch (err) { } catch (error) {
assert(false, 'Test 2.2 Failed with error', err.message || JSON.stringify(err)); assert(false, 'Test 2.2 Failed with error', error.message || JSON.stringify(error));
} }
// Test 2.3: File exists and memtrace key already exists (overwrites memtrace key only) // Test 2.3: File exists and memtrace key already exists (overwrites memtrace key only)
@ -208,13 +212,13 @@ async function runTests() {
mcp: { mcp: {
otherServer: { otherServer: {
type: 'local', type: 'local',
command: ['other-server'] command: ['other-server'],
}, },
memtrace: { memtrace: {
type: 'remote', type: 'remote',
command: ['old-memtrace'] command: ['old-memtrace'],
} },
} },
}; };
await fs.writeFile(opencodeTestFile, JSON.stringify(preExistingConfig, null, 2), 'utf8'); await fs.writeFile(opencodeTestFile, JSON.stringify(preExistingConfig, null, 2), 'utf8');
@ -224,20 +228,20 @@ async function runTests() {
assert( assert(
config.mcp.otherServer !== undefined && config.mcp.otherServer.type === 'local', config.mcp.otherServer !== undefined && config.mcp.otherServer.type === 'local',
'Test 2.3: Overwriting preserves other OpenCode servers' 'Test 2.3: Overwriting preserves other OpenCode servers',
); );
assert( assert(
config.mcp.memtrace.type === 'local' && config.mcp.memtrace.command[0] === 'memtrace', config.mcp.memtrace.type === 'local' && config.mcp.memtrace.command[0] === 'memtrace',
'Test 2.3: Correctly overwrites only the memtrace key in OpenCode config' 'Test 2.3: Correctly overwrites only the memtrace key in OpenCode config',
); );
} catch (err) { } catch (error) {
assert(false, 'Test 2.3 Failed with error', err.message || JSON.stringify(err)); assert(false, 'Test 2.3 Failed with error', error.message || JSON.stringify(error));
} }
// Clean up // Clean up
try { try {
await fs.rm(tempDir, { recursive: true, force: true }); await fs.rm(tempDir, { recursive: true, force: true });
} catch (err) { } catch {
// Ignore cleanup errors // Ignore cleanup errors
} }
@ -252,7 +256,7 @@ async function runTests() {
} }
} }
runTests().catch(err => { runTests().catch((error) => {
console.error('Fatal test error:', err); console.error('Fatal test error:', error);
process.exit(1); process.exit(1);
}); });

View File

@ -58,7 +58,7 @@ async function ensureDir(filePath) {
const dir = path.dirname(filePath); const dir = path.dirname(filePath);
try { try {
await fs.mkdir(dir, { recursive: true }); await fs.mkdir(dir, { recursive: true });
} catch (err) {} } catch { /* directory may already exist */ }
} }
async function runVerification() { async function runVerification() {
@ -116,20 +116,32 @@ async function runVerification() {
const possibleBash1 = path.join(gitParent, 'bin', 'bash.exe'); const possibleBash1 = path.join(gitParent, 'bin', 'bash.exe');
const possibleBash2 = path.join(gitParent, 'bin', 'sh.exe'); const possibleBash2 = path.join(gitParent, 'bin', 'sh.exe');
if (await fs.access(possibleBash1).then(() => true).catch(() => false)) { if (
await fs
.access(possibleBash1)
.then(() => true)
.catch(() => false)
) {
command = `"${possibleBash1}" install-bmad-memtrace.sh`; command = `"${possibleBash1}" install-bmad-memtrace.sh`;
} else if (await fs.access(possibleBash2).then(() => true).catch(() => false)) { } else if (
await fs
.access(possibleBash2)
.then(() => true)
.catch(() => false)
) {
command = `"${possibleBash2}" install-bmad-memtrace.sh`; command = `"${possibleBash2}" install-bmad-memtrace.sh`;
} }
} }
} catch (e) { } catch {
// Fallback // Fallback
const paths = [ const paths = [String.raw`C:\Program Files\Git\bin\bash.exe`, String.raw`C:\Program Files (x86)\Git\bin\bash.exe`];
'C:\\Program Files\\Git\\bin\\bash.exe',
'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
];
for (const p of paths) { for (const p of paths) {
if (await fs.access(p).then(() => true).catch(() => false)) { if (
await fs
.access(p)
.then(() => true)
.catch(() => false)
) {
command = `"${p}" install-bmad-memtrace.sh`; command = `"${p}" install-bmad-memtrace.sh`;
break; break;
} }
@ -150,23 +162,38 @@ async function runVerification() {
// 5. Assertions // 5. Assertions
// AC 1: .memtrace-workspace exists // AC 1: .memtrace-workspace exists
const anchorExists = await fs.access(path.join(tempDir, '.memtrace-workspace')).then(() => true).catch(() => false); const anchorExists = await fs
.access(path.join(tempDir, '.memtrace-workspace'))
.then(() => true)
.catch(() => false);
assert(anchorExists, 'AC 1: .memtrace-workspace anchor file successfully created in project root'); assert(anchorExists, 'AC 1: .memtrace-workspace anchor file successfully created in project root');
// Legacy cleanup: README.md is deleted // Legacy cleanup: README.md is deleted
const readmeExists = await fs.access(dummyClonedFile).then(() => true).catch(() => false); const readmeExists = await fs
.access(dummyClonedFile)
.then(() => true)
.catch(() => false);
assert(!readmeExists, 'Legacy cleanup: Tracked clone files (README.md) successfully deleted'); assert(!readmeExists, 'Legacy cleanup: Tracked clone files (README.md) successfully deleted');
// Git removal: .git is deleted // Git removal: .git is deleted
const gitExists = await fs.access(path.join(tempDir, '.git')).then(() => true).catch(() => false); const gitExists = await fs
.access(path.join(tempDir, '.git'))
.then(() => true)
.catch(() => false);
assert(!gitExists, 'Security/Standalone: .git directory completely removed'); assert(!gitExists, 'Security/Standalone: .git directory completely removed');
// Staging cleanup: bmad-install is deleted // Staging cleanup: bmad-install is deleted
const stagingExists = await fs.access(path.join(tempDir, 'bmad-install')).then(() => true).catch(() => false); const stagingExists = await fs
.access(path.join(tempDir, 'bmad-install'))
.then(() => true)
.catch(() => false);
assert(!stagingExists, 'Runtime cleanup: bmad-install staging directory successfully removed'); assert(!stagingExists, 'Runtime cleanup: bmad-install staging directory successfully removed');
// BMad preservation: _bmad directory remains // BMad preservation: _bmad directory remains
const bmadExists = await fs.access(path.join(tempDir, '_bmad')).then(() => true).catch(() => false); const bmadExists = await fs
.access(path.join(tempDir, '_bmad'))
.then(() => true)
.catch(() => false);
assert(bmadExists, 'Core preservation: _bmad directory preserved post-cleanup'); assert(bmadExists, 'Core preservation: _bmad directory preserved post-cleanup');
// AC 2: Claude Desktop configuration successfully injected // AC 2: Claude Desktop configuration successfully injected
@ -174,7 +201,7 @@ async function runVerification() {
const claudeConfig = JSON.parse(claudeContent); const claudeConfig = JSON.parse(claudeContent);
assert( assert(
claudeConfig.mcpServers && claudeConfig.mcpServers.memtrace && claudeConfig.mcpServers.memtrace.command === 'memtrace', claudeConfig.mcpServers && claudeConfig.mcpServers.memtrace && claudeConfig.mcpServers.memtrace.command === 'memtrace',
'AC 2: Claude Desktop config correctly created and populated with memtrace MCP server' 'AC 2: Claude Desktop config correctly created and populated with memtrace MCP server',
); );
// AC 3: OpenCode configuration successfully injected // AC 3: OpenCode configuration successfully injected
@ -182,20 +209,19 @@ async function runVerification() {
const opencodeConfig = JSON.parse(opencodeContent); const opencodeConfig = JSON.parse(opencodeContent);
assert( assert(
opencodeConfig.mcp && opencodeConfig.mcp.memtrace && opencodeConfig.mcp.memtrace.type === 'local', opencodeConfig.mcp && opencodeConfig.mcp.memtrace && opencodeConfig.mcp.memtrace.type === 'local',
'AC 3: OpenCode config correctly created and populated with memtrace local definition' 'AC 3: OpenCode config correctly created and populated with memtrace local definition',
); );
} catch (error) {
} catch (err) { console.error('Verification failed with error:', error);
console.error('Verification failed with error:', err); if (error.stdout) console.error('stdout:', error.stdout);
if (err.stdout) console.error('stdout:', err.stdout); if (error.stderr) console.error('stderr:', error.stderr);
if (err.stderr) console.error('stderr:', err.stderr);
failed++; failed++;
} }
// Clean up // Clean up
try { try {
await fs.rm(tempDir, { recursive: true, force: true }); await fs.rm(tempDir, { recursive: true, force: true });
} catch (err) {} } catch { /* cleanup errors are non-fatal */ }
console.log(`\n${colors.cyan}========================================`); console.log(`\n${colors.cyan}========================================`);
console.log(`Verification Summary: Passed: ${passed}, Failed: ${failed}`); console.log(`Verification Summary: Passed: ${passed}, Failed: ${failed}`);
@ -208,7 +234,7 @@ async function runVerification() {
} }
} }
runVerification().catch(err => { runVerification().catch((error) => {
console.error('Fatal verification error:', err); console.error('Fatal verification error:', error);
process.exit(1); process.exit(1);
}); });