snippet-manager
Version:
Snippet Manager is a simple **command-line tool** for managing code snippets, similar to a **mini Git** for your snippets. You can easily add, list, update, delete, and inject snippets into files. This tool helps you keep your code snippets organized and
295 lines (256 loc) • 9.27 kB
JavaScript
import fs from 'fs';
import path from 'path';
import os from 'os';
import { Low, JSONFile } from 'lowdb';
import inquirer from 'inquirer';
import chalk from 'chalk';
import readline from 'readline';
import figlet from 'figlet';
import FileTreeSelectionPrompt from 'inquirer-file-tree-selection-prompt';
// Register the file-tree-selection prompt
inquirer.registerPrompt('file-tree-selection', FileTreeSelectionPrompt);
// Define database paths
const dbDir = path.join(os.homedir(), '.snippet-manager');
const dbFilePath = path.join(dbDir, 'db.json');
// Ensure the database directory exists
if (!fs.existsSync(dbDir)) {
fs.mkdirSync(dbDir, { recursive: true });
}
const adapter = new JSONFile(dbFilePath);
const db = new Low(adapter);
// Initialize the database
async function initDB() {
await db.read();
db.data ||= { snippets: [] };
await db.write();
}
// Display app title using figlet
function displayTitle() {
figlet.text('Snippet Manager', { font: 'Slant' }, (err, data) => {
if (err) {
console.log(chalk.red('Something went wrong...'));
return;
}
console.log(chalk.blue(data));
});
}
// Add a new snippet
async function addSnippet(title, code) {
await db.read();
db.data.snippets.push({ title, code, version: 1, createdAt: new Date() });
await db.write();
console.log(chalk.green('Snippet added successfully!'));
}
// List all snippets
async function listSnippets() {
await db.read();
if (db.data.snippets.length === 0) {
console.log(chalk.yellow('No snippets found.'));
return;
}
db.data.snippets.forEach((snippet, index) => {
console.log(chalk.blue(`${index + 1}. ${snippet.title} (Version: ${snippet.version})`));
});
}
// View a specific snippet
async function viewSnippet() {
await listSnippets();
const { index } = await inquirer.prompt([
{ type: 'number', name: 'index', message: 'Enter the snippet index to view:' },
]);
const snippetIndex = index - 1;
await db.read();
if (snippetIndex < 0 || snippetIndex >= db.data.snippets.length) {
console.log(chalk.red('Invalid snippet index.'));
return;
}
const snippet = db.data.snippets[snippetIndex];
console.log(
chalk.green(`\nTitle: ${snippet.title}\n`) +
chalk.cyan(`Version: ${snippet.version}\nCreated At: ${snippet.createdAt}\n`) +
chalk.white('Code:\n') +
chalk.gray(`${snippet.code}\n`)
);
}
// Update an existing snippet
async function updateSnippet(index, newCode) {
await db.read();
if (index < 0 || index >= db.data.snippets.length) {
console.log(chalk.red('Invalid snippet index.'));
return;
}
db.data.snippets[index].code = newCode;
db.data.snippets[index].version += 1;
await db.write();
console.log(chalk.green('Snippet updated successfully!'));
}
// Delete a snippet
async function deleteSnippet() {
await listSnippets();
const { index } = await inquirer.prompt([
{ name: 'index', message: 'Enter snippet index to delete:', type: 'number' },
]);
const snippetIndex = index - 1;
if (snippetIndex < 0 || snippetIndex >= db.data.snippets.length) {
console.log(chalk.red('Invalid snippet index.'));
return;
}
const deletedSnippet = db.data.snippets.splice(snippetIndex, 1);
await db.write();
console.log(chalk.green(`Snippet "${deletedSnippet[0].title}" deleted successfully!`));
}
// Append a snippet to a file
async function appendSnippetToFile() {
await db.read();
const { snippetIndex } = await inquirer.prompt([
{
type: 'list',
name: 'snippetIndex',
message: 'Select a snippet to append to a file:',
choices: db.data.snippets.map((snippet, index) => ({
name: `${snippet.title} (Version: ${snippet.version})`,
value: index,
})),
},
]);
const snippetContent = `// Snippet: ${db.data.snippets[snippetIndex].title}\n${db.data.snippets[snippetIndex].code}\n\n`;
const { method } = await inquirer.prompt([
{
type: 'list',
name: 'method',
message: 'How would you like to select the file?',
choices: ['Manual Path Entry', 'File Tree Selection'],
},
]);
let filePath;
if (method === 'Manual Path Entry') {
const { inputFilePath } = await inquirer.prompt([
{
type: 'input',
name: 'inputFilePath',
message: 'Enter the file path (Example: ./controllers/user.js):',
validate: (input) => (input ? true : 'File path cannot be empty.'),
},
]);
filePath = path.resolve(inputFilePath);
} else {
const { selectedFilePath } = await inquirer.prompt([
{
type: 'file-tree-selection',
name: 'selectedFilePath',
message: 'Select the file from the tree:',
},
]);
filePath = path.resolve(selectedFilePath);
}
try {
fs.appendFileSync(filePath, snippetContent);
console.log(chalk.green(`Snippet appended to ${filePath} successfully!`));
} catch (error) {
console.error(chalk.red(`Failed to append snippet: ${error.message}`));
}
}
// Add a multi-line snippet
async function addMultiLineSnippet() {
console.log(chalk.yellow('Enter your multi-line code snippet. Type "END" on a new line when you are done:\n'));
const lines = [];
const rl = readline.createInterface({ input: process.stdin });
for await (const line of rl) {
if (line.trim() === 'END') break;
lines.push(line);
}
rl.close();
const { title } = await inquirer.prompt([
{ type: 'input', name: 'title', message: 'Snippet Title:' },
]);
await addSnippet(title, lines.join('\n'));
}
// Update a multi-line snippet
async function updateMultiLineSnippet() {
await listSnippets();
const { index } = await inquirer.prompt([
{ name: 'index', message: 'Enter snippet index to update:', type: 'number' },
]);
const snippetIndex = index - 1;
if (snippetIndex < 0 || snippetIndex >= db.data.snippets.length) {
console.log(chalk.red('Invalid snippet index.'));
return;
}
console.log(chalk.yellow('Enter the new multi-line code snippet. Type "END" on a new line when you are done:\n'));
const lines = [];
const rl = readline.createInterface({ input: process.stdin });
for await (const line of rl) {
if (line.trim() === 'END') break;
lines.push(line);
}
rl.close();
await updateSnippet(snippetIndex, lines.join('\n'));
}
// Get user analytics
async function sendAnalytics(action) {
try {
await fetch("https://snippet-manager-analytics-production.up.railway.app/log", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action, timestamp: new Date().toISOString() }),
});
// console.log(`Analytics sent: ${action}`); // Debugging
} catch (error) {
// console.error("Failed to send analytics:", error.message);
// Fail silently to avoid affecting user experience
}
}
// Main prompt for user actions
async function promptUser() {
while (true) {
const { action } = await inquirer.prompt([
{
type: 'list',
name: 'action',
message: 'What would you like to do?',
choices: [
'Add Snippet',
'List Snippets',
'View Snippet',
'Update Snippet',
'Delete Snippet',
'Inject Snippet in File',
'Exit',
],
},
]);
sendAnalytics(action);
switch (action) {
case 'Add Snippet':
await addMultiLineSnippet();
break;
case 'List Snippets':
await listSnippets();
break;
case 'View Snippet':
await viewSnippet();
break;
case 'Update Snippet':
await updateMultiLineSnippet();
break;
case 'Delete Snippet':
await deleteSnippet();
break;
case 'Inject Snippet in File':
await appendSnippetToFile();
break;
case 'Exit':
console.log(chalk.yellow('Exiting...'));
return;
}
}
}
// Main function to start the app
async function main() {
displayTitle();
await initDB();
await promptUser();
}
// Run the app
main().catch((err) => console.error(chalk.red(`Error: ${err.message}`)));