UNPKG

obsidian-plugin-config

Version:

Global CLI injection tool for Obsidian plugins

174 lines (154 loc) 5.16 kB
import { access, mkdir, copyFile, rm } from 'fs/promises'; import path from 'path'; import * as readline from 'readline'; import { execSync } from 'child_process'; export function createReadlineInterface(): readline.Interface { return readline.createInterface({ input: process.stdin as NodeJS.ReadableStream, output: process.stdout as NodeJS.WritableStream }); } export const askQuestion = async ( question: string, rl: readline.Interface ): Promise<string> => { try { return await new Promise((resolve) => rl.question(question, (input) => resolve(input.trim())) ); } catch (error) { console.error('Error asking question:', error); throw error; } }; /** * Ask a yes/no confirmation question with standardized logic * Accepts: y, yes, Y, YES, or empty (default to yes) * Rejects: n, no, N, NO * Invalid input defaults to no for safety */ export const askConfirmation = async ( question: string, rl: readline.Interface ): Promise<boolean> => { const answer = await askQuestion(`${question} [Y/n]: `, rl); const response = answer.toLowerCase(); // Accept: y, yes, Y, YES, or empty (default to yes) // Reject: n, no, N, NO const isYes = response === '' || response === 'y' || response === 'yes'; const isNo = response === 'n' || response === 'no'; if (isNo) { return false; } else if (isYes) { return true; } else { console.log('Please answer Y (yes) or n (no). Defaulting to no for safety.'); return false; } }; export const cleanInput = (inputStr: string): string => { if (!inputStr) return ''; return inputStr.trim().replace(/["`]/g, "'").replace(/\r\n/g, '\n'); }; export const isValidPath = async (pathToCheck: string): Promise<boolean> => { if (!pathToCheck) return false; try { // Using async fs.access is preferred over synchronous existsSync // as it doesn't block the main thread/event loop await access(pathToCheck.trim()); return true; } catch { return false; } }; export async function copyFilesToTargetDir(buildPath: string): Promise<void> { const pluginDir = process.cwd(); const manifestSrc = path.join(pluginDir, 'manifest.json'); const manifestDest = path.join(buildPath, 'manifest.json'); const cssDest = path.join(buildPath, 'styles.css'); const folderToRemove = path.join(buildPath, '_.._'); try { await mkdir(buildPath, { recursive: true }); } catch (error: unknown) { if ((error as NodeJS.ErrnoException).code !== 'EEXIST') { console.error(`Error creating directory: ${(error as Error).message}`); } } // Copy manifest try { await copyFile(manifestSrc, manifestDest); } catch (error: unknown) { console.error(`Error copying manifest: ${(error as Error).message}`); } // Copy CSS try { const srcStylesPath = path.join(pluginDir, 'src/styles.css'); const rootStylesPath = path.join(pluginDir, 'styles.css'); // First check if CSS exists in src/styles.css if (await isValidPath(srcStylesPath)) { await copyFile(srcStylesPath, cssDest); } // Otherwise, check if it exists in the root else if (await isValidPath(rootStylesPath)) { await copyFile(rootStylesPath, cssDest); if (await isValidPath(folderToRemove)) { await rm(folderToRemove, { recursive: true }); } } else { return; } } catch (error: unknown) { console.error(`Error copying CSS: ${(error as Error).message}`); } } /** * Execute a shell command. * Uses shell spawning for cross-platform compatibility (Windows + Linux). * @param pipe - set to true to suppress stdout/stderr output */ export function gitExec(command: string, cwd?: string, pipe = false): void { try { execSync(command, { stdio: pipe ? 'pipe' : 'inherit', shell: process.platform === 'win32' ? 'cmd.exe' : '/bin/sh', cwd }); } catch (error: unknown) { console.error(`Error executing '${command}':`, (error as Error).message); throw error; } } /** * Execute a shell command and return its stdout as a trimmed string. * Uses shell spawning for cross-platform compatibility (Windows + Linux). */ export function gitOutput(command: string, cwd?: string): string { const result = execSync(command, { encoding: 'utf8', shell: process.platform === 'win32' ? 'cmd.exe' : '/bin/sh', cwd }); return result.trim(); } /** * Ensure Git repository is synchronized with remote before pushing */ export async function ensureGitSync(): Promise<void> { try { console.log('🔄 Checking Git synchronization...'); // Fetch latest changes from remote gitExec('git fetch origin'); // Check if branch is behind remote const status = gitOutput('git status --porcelain -b'); if (status.includes('behind')) { console.log('📥 Branch behind remote. Pulling changes...'); gitExec('git pull'); console.log('✅ Successfully pulled remote changes'); } else { console.log('✅ Repository is synchronized with remote'); } } catch (error: unknown) { console.error(`❌ Git sync failed: ${(error as Error).message}`); throw error; } }