@julianoczkowski/my-rules
Version:
Personal development rules, guidelines, and documentation for coding projects
266 lines (223 loc) • 8.96 kB
JavaScript
import {
writeFileSync,
mkdirSync,
readdirSync,
readFileSync,
statSync,
} from "fs";
import { join } from "path";
import { fileURLToPath } from "url";
import { dirname } from "path";
import prompts from "prompts";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Get the docs directory path
const DOCS_DIR = join(__dirname, "docs");
function getFolderDescription(folderName) {
const descriptions = {
"Cursor Rules":
"IDE-specific configuration and development standards for Cursor",
"VS Code Rules":
"IDE-specific configuration and development standards for VS Code",
Templates: "Project templates and boilerplate code",
Scripts: "Utility scripts and automation tools",
Docs: "General documentation and guides",
};
return descriptions[folderName] || "Documentation and configuration files";
}
async function discoverAvailableFolders() {
try {
const folders = [];
const items = readdirSync(DOCS_DIR);
for (const item of items) {
const itemPath = join(DOCS_DIR, item);
const stat = statSync(itemPath);
if (stat.isDirectory()) {
folders.push({
name: item,
path: item,
description: getFolderDescription(item),
});
}
}
return folders;
} catch (error) {
console.log(`⚠️ Error reading docs directory: ${error.message}`);
return [];
}
}
async function discoverFiles(selectedFolders) {
const filesToDownload = [];
for (const folderName of selectedFolders) {
const folderPath = join(DOCS_DIR, folderName);
try {
const items = readdirSync(folderPath);
for (const item of items) {
const itemPath = join(folderPath, item);
const stat = statSync(itemPath);
if (stat.isFile()) {
filesToDownload.push({
name: item,
path: join(folderName, item),
content: readFileSync(itemPath, "utf8"),
});
}
}
} catch (error) {
console.log(`⚠️ Error reading folder ${folderName}: ${error.message}`);
}
}
return filesToDownload;
}
async function showFolderPicker(availableFolders) {
try {
console.log("\n📁 Select Documentation Folders to Download:");
console.log("=".repeat(50));
// Create choices for the checkbox prompt
const choices = availableFolders.map((folder) => ({
title: folder.name,
description: folder.description,
value: folder.name,
selected: false, // Start with none selected
}));
// Add special options
choices.unshift(
{
title: "📥 Select All",
description: "Download all available documentation folders",
value: "select_all",
selected: false,
},
{
title: "❌ Select None",
description: "Skip download",
value: "select_none",
selected: false,
}
);
const response = await prompts({
type: "multiselect",
name: "folders",
message: "👉 Choose which folders to download:",
choices: choices,
instructions: "Use ↑↓ to navigate, space to select, enter to confirm",
hint: "- Space to select. Return to submit",
});
// Handle special selections
if (response.folders && response.folders.includes("select_all")) {
return availableFolders.map((f) => f.name);
}
if (response.folders && response.folders.includes("select_none")) {
return [];
}
// Return selected folders (excluding special options)
return response.folders
? response.folders.filter(
(folder) => folder !== "select_all" && folder !== "select_none"
)
: [];
} catch (error) {
console.log(`⚠️ Error with interactive picker: ${error.message}`);
console.log("📋 Falling back to downloading all folders...");
return availableFolders.map((f) => f.name);
}
}
async function downloadDocs(interactive = true, forceInteractive = false) {
try {
// Welcome screen with ASCII logo
console.log(`
╔══════════════════════════════════════════════════════════════╗
║ ║
║ ███╗ ███╗██╗ ██╗ ██████╗ ██╗ ██╗██╗ ███████╗ ║
║ ████╗ ████║╚██╗ ██╔╝ ██╔══██╗██║ ██║██║ ██╔════╝ ║
║ ██╔████╔██║ ╚████╔╝ ██████╔╝██║ ██║██║ ███████╗ ║
║ ██║╚██╔╝██║ ╚██╔╝ ██╔══██╗██║ ██║██║ ╚════██║ ║
║ ██║ ╚═╝ ██║ ██║ ██║ ██║╚██████╔╝███████╗███████║ ║
║ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝ ║
║ ║
║ 📚 Personal Development Rules & Guidelines ║
║ ║
╚══════════════════════════════════════════════════════════════╝
`);
// Determine target directory
let targetDir = process.cwd();
if (process.env.INIT_CWD) {
targetDir = process.env.INIT_CWD;
}
// If we're inside node_modules, go up to the project root
if (targetDir.includes("node_modules")) {
targetDir = targetDir.split("node_modules")[0];
}
const availableFolders = await discoverAvailableFolders();
if (availableFolders.length === 0) {
console.log("⚠️ No documentation folders found");
return;
}
let selectedFolders = [];
// Check if we're in a real interactive terminal or forced interactive mode
const isInteractiveTerminal =
(interactive && process.stdin.isTTY && process.stdout.isTTY) ||
forceInteractive;
if (isInteractiveTerminal) {
// Interactive mode - show picker
selectedFolders = await showFolderPicker(availableFolders);
if (selectedFolders.length === 0) {
console.log("👋 No folders selected. Download cancelled.");
return;
}
} else {
// Non-interactive mode (like postinstall) - download all
selectedFolders = availableFolders.map((f) => f.name);
}
const filesToDownload = await discoverFiles(selectedFolders);
if (filesToDownload.length === 0) {
console.log("⚠️ No files found to download in selected folders");
return;
}
console.log(`📋 Found ${filesToDownload.length} files to download`);
let downloadedCount = 0;
for (const file of filesToDownload) {
const targetPath = join(targetDir, file.path);
const targetDirPath = dirname(targetPath);
try {
// Create directory if it doesn't exist
mkdirSync(targetDirPath, { recursive: true });
// Write file
writeFileSync(targetPath, file.content, "utf8");
console.log(`📥 Downloading ${file.path}...`);
console.log(`✓ Downloaded ${file.path}`);
downloadedCount++;
} catch (error) {
console.log(`⚠️ Failed to download ${file.path}: ${error.message}`);
}
}
console.log(
`\n🎉 Download complete! (${downloadedCount}/${filesToDownload.length} files)`
);
console.log("📚 Your rules and documentation are now available locally");
console.log(`📁 Files downloaded to: ${targetDir}`);
if (downloadedCount > 0) {
console.log("\n📖 Downloaded content:");
const folderSummary = {};
for (const file of filesToDownload) {
const folderName = file.path.split("/")[0];
folderSummary[folderName] = (folderSummary[folderName] || 0) + 1;
}
for (const [folder, count] of Object.entries(folderSummary)) {
console.log(` • ${folder}/ - ${count} files`);
}
}
console.log(
"\n💡 Visit https://github.com/julianoczkowski/my-rules for more info."
);
} catch (error) {
console.log(`❌ Error: ${error.message}`);
// Don't exit with error code to avoid breaking npm install
}
}
// Export the functions for CLI usage
export { downloadDocs, discoverAvailableFolders, showFolderPicker };
// Run automatically if called directly (for postinstall)
if (import.meta.url === `file://${process.argv[1]}`) {
downloadDocs(false).catch(console.error); // Non-interactive for postinstall
}