Files
BMAD-METHOD/tools/installer/message-loader.js
Brian Madison a6d075bd0b fix(installer): replace fs-extra with native node:fs to prevent file loss
fs-extra routes all operations through graceful-fs, which globally
monkey-patches node:fs with a deferred retry queue. During multi-module
installs (~500+ file ops), retried unlink operations from one module's
remove phase can fire after the next module's copy phase has written
files, silently deleting them non-deterministically.

Replace fs-extra with a thin fs-native.js wrapper over node:fs/promises
and node:fs. All 21 consumers now use native APIs with no global
monkey-patching, eliminating the retry-queue race condition entirely.

Closes #1779
2026-04-13 00:44:28 -05:00

84 lines
1.9 KiB
JavaScript

const fs = require('./fs-native');
const path = require('node:path');
const yaml = require('yaml');
const prompts = require('./prompts');
/**
* Load and display installer messages from messages.yaml
*/
class MessageLoader {
constructor() {}
/**
* Load messages from the YAML file
* @returns {Object|null} Messages object or null if not found
*/
load() {
if (this.messages) {
return this.messages;
}
const messagesPath = path.join(__dirname, 'install-messages.yaml');
try {
const content = fs.readFileSync(messagesPath, 'utf8');
this.messages = yaml.parse(content);
return this.messages;
} catch {
// File doesn't exist or is invalid - return null
return null;
}
}
/**
* Get the start message for display
* @returns {string|null} Start message or null
*/
getStartMessage() {
const messages = this.load();
return messages?.startMessage || null;
}
/**
* Get the end message for display
* @returns {string|null} End message or null
*/
getEndMessage() {
const messages = this.load();
return messages?.endMessage || null;
}
/**
* Display the start message (after logo, before prompts)
*/
async displayStartMessage() {
const message = this.getStartMessage();
if (message) {
await prompts.log.info(message);
}
}
/**
* Display the end message (after installation completes)
*/
async displayEndMessage() {
const message = this.getEndMessage();
if (message) {
await prompts.log.info(message);
}
}
/**
* Check if messages exist for the current version
* @param {string} currentVersion - Current package version
* @returns {boolean} True if messages match current version
*/
isCurrent(currentVersion) {
const messages = this.load();
return messages && messages.version === currentVersion;
}
messages = null;
}
module.exports = { MessageLoader };