tops-bmad
Version:
CLI tool to install BMAD workflow files into any project with integrated Shai-Hulud 2.0 security scanning
99 lines (88 loc) • 3.37 kB
JavaScript
import AdmZip from "adm-zip";
import fs from "fs-extra";
import path from "path";
import { fileURLToPath } from "url";
import { decryptFile } from "./encrypt.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const TEMP_DIR = path.join(process.cwd(), "bmad-temp");
const LOCAL_ZIP = path.join(__dirname, "..", "bmad-package.zip");
const DECRYPTED_ZIP = path.join(process.cwd(), "bmad-temp-decrypted.zip");
/**
* Validates the secret key by attempting to decrypt the package
* @param {string} secretKey - The secret key to validate
* @returns {Promise<void>}
*/
export async function validateSecretKey(secretKey) {
try {
// Check if local zip file exists
if (!await fs.pathExists(LOCAL_ZIP)) {
throw new Error(`BMAD package not found at: ${LOCAL_ZIP}`);
}
// Attempt to decrypt to a temporary location
await fs.ensureDir(TEMP_DIR);
const TEST_DECRYPTED_ZIP = path.join(process.cwd(), "bmad-temp-test-decrypt.zip");
try {
await decryptFile(LOCAL_ZIP, TEST_DECRYPTED_ZIP, secretKey);
// Verify the decrypted file is a valid zip
try {
const zip = new AdmZip(TEST_DECRYPTED_ZIP);
zip.getEntries(); // This will throw if not a valid zip
} catch (error) {
throw new Error("Invalid secret key. The decrypted file is not a valid zip.");
}
} finally {
// Clean up test decrypted file
if (await fs.pathExists(TEST_DECRYPTED_ZIP)) {
await fs.remove(TEST_DECRYPTED_ZIP).catch(() => {});
}
}
} catch (error) {
// Provide user-friendly error messages
if (error.message.includes("Invalid secret key") ||
error.message.includes("bad decrypt") ||
error.message.includes("wrong final block length")) {
throw new Error("Invalid secret key. Please check your password and try again.");
}
throw error;
}
}
/**
* Extracts the local BMAD package
* @param {string} secretKey - The secret key to decrypt the zip file
* @returns {Promise<void>}
*/
export async function downloadBMAD(secretKey) {
try {
console.log("📦 Locating BMAD package...");
// Check if local zip file exists
if (!await fs.pathExists(LOCAL_ZIP)) {
throw new Error(`BMAD package not found at: ${LOCAL_ZIP}`);
}
console.log("🔓 Decrypting BMAD package...");
// Decrypt the zip file to a temporary location
await fs.ensureDir(TEMP_DIR);
await decryptFile(LOCAL_ZIP, DECRYPTED_ZIP, secretKey);
console.log("📦 Extracting BMAD package...");
// Verify the decrypted file is a valid zip before extracting
let zip;
try {
zip = new AdmZip(DECRYPTED_ZIP);
zip.getEntries(); // This will throw if not a valid zip
} catch (error) {
throw new Error("Decrypted file is not a valid zip. The encryption key may be incorrect or the file may be corrupted.");
}
zip.extractAllTo(TEMP_DIR, true);
// Clean up decrypted zip file for security
if (await fs.pathExists(DECRYPTED_ZIP)) {
await fs.remove(DECRYPTED_ZIP);
}
} catch (error) {
// Clean up decrypted zip file on error
if (await fs.pathExists(DECRYPTED_ZIP)) {
await fs.remove(DECRYPTED_ZIP).catch(() => {});
}
console.error("❌ Error downloading BMAD package:", error.message);
throw error;
}
}