UNPKG

obsidian-plugin-config

Version:

Système d'injection pour plugins Obsidian autonomes

883 lines (740 loc) β€’ 30.7 kB
#!/usr/bin/env tsx import fs from "fs"; import path from "path"; import { execSync } from "child_process"; import { askConfirmation, createReadlineInterface, isValidPath, gitExec } from "./utils.js"; const rl = createReadlineInterface(); interface InjectionPlan { targetPath: string; isObsidianPlugin: boolean; hasPackageJson: boolean; hasManifest: boolean; hasScriptsFolder: boolean; currentDependencies: string[]; } /** * Analyze the target plugin directory */ async function analyzePlugin(pluginPath: string): Promise<InjectionPlan> { const packageJsonPath = path.join(pluginPath, "package.json"); const manifestPath = path.join(pluginPath, "manifest.json"); const scriptsPath = path.join(pluginPath, "scripts"); const plan: InjectionPlan = { targetPath: pluginPath, isObsidianPlugin: false, hasPackageJson: await isValidPath(packageJsonPath), hasManifest: await isValidPath(manifestPath), hasScriptsFolder: await isValidPath(scriptsPath), currentDependencies: [] }; // Check if it's an Obsidian plugin if (plan.hasManifest) { try { const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")); plan.isObsidianPlugin = !!(manifest.id && manifest.name && manifest.version); } catch { console.warn("Warning: Could not parse manifest.json"); } } // Get current dependencies if (plan.hasPackageJson) { try { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); plan.currentDependencies = [ ...Object.keys(packageJson.dependencies || {}), ...Object.keys(packageJson.devDependencies || {}) ]; } catch { console.warn("Warning: Could not parse package.json"); } } return plan; } /** * Display injection plan and ask for confirmation */ async function showInjectionPlan(plan: InjectionPlan, autoConfirm: boolean = false, useSass: boolean = false): Promise<boolean> { console.log(`\n🎯 Injection Plan for: ${plan.targetPath}`); console.log(`πŸ“ Target: ${path.basename(plan.targetPath)}`); console.log(`πŸ“¦ Package.json: ${plan.hasPackageJson ? 'βœ…' : '❌'}`); console.log(`πŸ“‹ Manifest.json: ${plan.hasManifest ? 'βœ…' : '❌'}`); console.log(`πŸ“‚ Scripts folder: ${plan.hasScriptsFolder ? 'βœ… (will be updated)' : '❌ (will be created)'}`); console.log(`πŸ”Œ Obsidian plugin: ${plan.isObsidianPlugin ? 'βœ…' : '❌'}`); console.log(`🎨 SASS support: ${useSass ? 'βœ… (esbuild-sass-plugin will be added)' : '❌'}`); if (!plan.isObsidianPlugin) { console.log(`\n⚠️ Warning: This doesn't appear to be a valid Obsidian plugin`); console.log(` Missing manifest.json or invalid structure`); } console.log(`\nπŸ“‹ Will inject:`); console.log(` βœ… Local scripts (utils.ts, esbuild.config.ts, acp.ts, etc.)`); console.log(` βœ… Updated package.json scripts`); console.log(` βœ… Required dependencies`); console.log(` πŸ” Analyze centralized imports (manual commenting may be needed)`); if (autoConfirm) { console.log(`\nβœ… Auto-confirming injection...`); return true; } return await askConfirmation(`\nProceed with injection?`, rl); } /** * Check if plugin-config repo is clean and commit if needed */ async function ensurePluginConfigClean(): Promise<void> { const configRoot = findPluginConfigRoot(); const originalCwd = process.cwd(); try { // Change to plugin-config directory process.chdir(configRoot); // Check git status const gitStatus = execSync("git status --porcelain", { encoding: "utf8" }).trim(); if (gitStatus) { console.log(`\n⚠️ Plugin-config has uncommitted changes:`); console.log(gitStatus); console.log(`\nπŸ”§ Auto-committing changes with yarn bacp...`); // Use yarn bacp with standard message const commitMessage = "πŸ”§ Update plugin-config templates and scripts"; gitExec("git add -A"); gitExec(`git commit -m "${commitMessage}"`); // Try to push try { const currentBranch = execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf8" }).trim(); gitExec(`git push origin ${currentBranch}`); console.log(`βœ… Changes committed and pushed successfully`); } catch { // Try setting upstream if it's a new branch try { const currentBranch = execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf8" }).trim(); gitExec(`git push --set-upstream origin ${currentBranch}`); console.log(`βœ… New branch pushed with upstream set`); } catch { console.log(`⚠️ Changes committed locally but push failed. Continue with injection.`); } } } else { console.log(`βœ… Plugin-config repo is clean`); } } finally { // Always restore original directory process.chdir(originalCwd); } } /** * Find plugin-config root directory */ function findPluginConfigRoot(): string { // Option 1: auto-detect parent directory const parentPath = path.resolve(process.cwd(), "../obsidian-plugin-config"); if (fs.existsSync(parentPath)) { return parentPath; } // Option 2: Check if we're running from NPM package (global installation) // Get the directory of this script file const scriptDir = path.dirname(new URL(import.meta.url).pathname); const npmPackageRoot = path.resolve(scriptDir, ".."); // Check if we're in an NPM package structure const npmPackageJson = path.join(npmPackageRoot, "package.json"); if (fs.existsSync(npmPackageJson)) { try { const packageContent = JSON.parse(fs.readFileSync(npmPackageJson, 'utf8')); if (packageContent.name === "obsidian-plugin-config") { return npmPackageRoot; } } catch { // Ignore parsing errors } } // Fallback to current directory (original behavior) return process.cwd(); } /** * Copy file content from local plugin-config directory */ function copyFromLocal(filePath: string): string { const configRoot = findPluginConfigRoot(); const sourcePath = path.join(configRoot, filePath); try { return fs.readFileSync(sourcePath, 'utf8'); } catch (error) { throw new Error(`Failed to copy ${filePath}: ${error}`); } } /** * Clean old script files (remove existing scripts to ensure clean injection) */ async function cleanOldScripts(scriptsPath: string): Promise<void> { const scriptNames = ["utils", "esbuild.config", "acp", "update-version", "release", "help"]; const extensions = [".ts", ".mts", ".js", ".mjs"]; // Remove all existing script files with any extension for (const scriptName of scriptNames) { for (const ext of extensions) { const scriptFile = path.join(scriptsPath, `${scriptName}${ext}`); if (await isValidPath(scriptFile)) { fs.unlinkSync(scriptFile); console.log(`πŸ—‘οΈ Removed existing ${scriptName}${ext} (will be replaced)`); } } } // Remove other obsolete files const obsoleteFiles = ["start.mjs", "start.js"]; for (const fileName of obsoleteFiles) { const filePath = path.join(scriptsPath, fileName); if (await isValidPath(filePath)) { fs.unlinkSync(filePath); console.log(`πŸ—‘οΈ Removed obsolete file: ${fileName}`); } } } /** * Clean old lint files (remove old ESLint config when injecting new format) */ async function cleanOldLintFiles(targetPath: string): Promise<void> { const oldLintFiles = [".eslintrc", ".eslintrc.js", ".eslintrc.json", ".eslintignore"]; const conflictingLintFiles = ["eslint.config.ts", "eslint.config.cjs", "eslint.config.js", "eslint.config.mjs"]; // Remove old format ESLint files for (const fileName of oldLintFiles) { const filePath = path.join(targetPath, fileName); if (await isValidPath(filePath)) { fs.unlinkSync(filePath); console.log(`πŸ—‘οΈ Removed old ESLint file: ${fileName} (replaced by eslint.config.ts)`); } } // Remove conflicting new format ESLint files to avoid duplicates for (const fileName of conflictingLintFiles) { const filePath = path.join(targetPath, fileName); if (await isValidPath(filePath)) { fs.unlinkSync(filePath); console.log(`πŸ—‘οΈ Removed existing ESLint file: ${fileName} (will be replaced by injection)`); } } } /** * Inject scripts from local files */ async function injectScripts(targetPath: string, useSass: boolean = false): Promise<void> { const scriptsPath = path.join(targetPath, "scripts"); // Create scripts directory if it doesn't exist if (!await isValidPath(scriptsPath)) { fs.mkdirSync(scriptsPath, { recursive: true }); console.log(`πŸ“ Created scripts directory`); } // Clean old script files before injection await cleanOldScripts(scriptsPath); // Clean old lint files before injection await cleanOldLintFiles(targetPath); const scriptFiles = [ "templates/scripts/utils.ts", useSass ? "templates/scripts/esbuild.config-sass.ts" : "templates/scripts/esbuild.config.ts", "templates/scripts/acp.ts", "templates/scripts/update-version.ts", "templates/scripts/release.ts", "templates/scripts/help.ts" ]; const configFiles = [ "templates/tsconfig-template.json", "templates/.gitignore", "templates/eslint.config.mts", "templates/.vscode/settings.json" ]; const workflowFiles = [ "templates/.github/workflows/release.yml", "templates/.github/workflows/release-body.md" ]; console.log(`\nπŸ“₯ Copying scripts from local files...`); for (const scriptFile of scriptFiles) { try { const content = copyFromLocal(scriptFile); let fileName = path.basename(scriptFile); // Handle SASS template renaming if (fileName === 'esbuild.config-sass.ts') { fileName = 'esbuild.config.ts'; } const targetFile = path.join(scriptsPath, fileName); fs.writeFileSync(targetFile, content, 'utf8'); console.log(` βœ… ${fileName}`); } catch (error) { console.error(` ❌ Failed to inject ${scriptFile}: ${error}`); } } console.log(`\nπŸ“₯ Copying config files from local files...`); for (const configFile of configFiles) { try { const content = copyFromLocal(configFile); let fileName = path.basename(configFile); // Handle template renaming and special paths let targetFile; if (fileName === 'tsconfig-template.json') { fileName = 'tsconfig.json'; targetFile = path.join(targetPath, fileName); } else if (configFile.includes('.vscode/settings.json')) { // Special handling for .vscode/settings.json targetFile = path.join(targetPath, '.vscode', 'settings.json'); fileName = '.vscode/settings.json'; } else { targetFile = path.join(targetPath, fileName); } // Ensure directory exists for nested files const targetDir = path.dirname(targetFile); if (!await isValidPath(targetDir)) { fs.mkdirSync(targetDir, { recursive: true }); } // Handle existing config files if (await isValidPath(targetFile)) { if (fileName === 'tsconfig.json') { console.log(` πŸ”„ ${fileName} exists, updating with template`); } else if (fileName === '.gitignore') { console.log(` πŸ”„ ${fileName} exists, updating with template (keeps .injection-info.json)`); } else if (fileName === '.vscode/settings.json') { console.log(` πŸ”„ ${fileName} exists, updating with template`); } } fs.writeFileSync(targetFile, content, 'utf8'); console.log(` βœ… ${fileName}`); } catch (error) { console.error(` ❌ Failed to inject ${configFile}: ${error}`); } } console.log(`\nπŸ“₯ Copying GitHub workflows from local files...`); for (const workflowFile of workflowFiles) { try { const content = copyFromLocal(workflowFile); const relativePath = workflowFile.replace('templates/', ''); const targetFile = path.join(targetPath, relativePath); // Ensure directory exists const targetDir = path.dirname(targetFile); if (!await isValidPath(targetDir)) { fs.mkdirSync(targetDir, { recursive: true }); } fs.writeFileSync(targetFile, content, 'utf8'); console.log(` βœ… ${relativePath}`); } catch (error) { console.error(` ❌ Failed to inject ${workflowFile}: ${error}`); } } } /** * Update package.json with autonomous configuration */ async function updatePackageJson(targetPath: string, useSass: boolean = false): Promise<void> { const packageJsonPath = path.join(targetPath, "package.json"); if (!await isValidPath(packageJsonPath)) { console.log(`❌ No package.json found, skipping package.json update`); return; } try { console.log(` πŸ” Reading package.json from: ${packageJsonPath}`); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); console.log(` πŸ” Original package name: ${packageJson.name}`); // Clean up obsolete scripts first const obsoleteScripts = [ "version" // yarn version doesn't work as expected, use "v" instead ]; for (const script of obsoleteScripts) { if (packageJson.scripts && packageJson.scripts[script]) { console.log(` 🧹 Removing obsolete script: "${script}" (was: "${packageJson.scripts[script]}")`); delete packageJson.scripts[script]; } } // Update scripts packageJson.scripts = { ...packageJson.scripts, "start": "yarn install && yarn dev", "dev": "tsx scripts/esbuild.config.ts", "build": "tsc -noEmit -skipLibCheck && tsx scripts/esbuild.config.ts production", "real": "tsx scripts/esbuild.config.ts production real", "acp": "tsx scripts/acp.ts", "bacp": "tsx scripts/acp.ts -b", "update-version": "tsx scripts/update-version.ts", "v": "tsx scripts/update-version.ts", "release": "tsx scripts/release.ts", "r": "tsx scripts/release.ts", "help": "tsx scripts/help.ts", "h": "tsx scripts/help.ts", "lint": "eslint . --ext .ts", "lint:fix": "eslint . --ext .ts --fix" }; // Remove centralized dependency if (packageJson.dependencies && packageJson.dependencies["obsidian-plugin-config"]) { delete packageJson.dependencies["obsidian-plugin-config"]; console.log(` πŸ—‘οΈ Removed obsidian-plugin-config dependency`); } // Add required dependencies if (!packageJson.devDependencies) packageJson.devDependencies = {}; const requiredDeps = { "@types/eslint": "latest", "@types/node": "^22.15.26", "@types/semver": "^7.7.0", "@typescript-eslint/eslint-plugin": "latest", "@typescript-eslint/parser": "latest", "builtin-modules": "3.3.0", "dedent": "^1.6.0", "dotenv": "^16.4.5", "esbuild": "latest", "eslint": "latest", "eslint-import-resolver-typescript": "latest", "jiti": "latest", "obsidian": "*", "obsidian-typings": "^3.9.5", "semver": "^7.7.2", "tsx": "^4.19.4", "typescript": "^5.8.2", ...(useSass ? { "esbuild-sass-plugin": "^3.3.1" } : {}) }; // Force update TypeScript to compatible version if (packageJson.devDependencies.typescript && packageJson.devDependencies.typescript !== "^5.8.2") { console.log(` πŸ”„ Updating TypeScript from ${packageJson.devDependencies.typescript} to ^5.8.2`); } let addedDeps = 0; let updatedDeps = 0; for (const [dep, version] of Object.entries(requiredDeps)) { if (!packageJson.devDependencies[dep]) { packageJson.devDependencies[dep] = version; addedDeps++; } else if (packageJson.devDependencies[dep] !== version) { packageJson.devDependencies[dep] = version; updatedDeps++; } } // Ensure yarn protection if (!packageJson.engines) packageJson.engines = {}; packageJson.engines.npm = "please-use-yarn"; packageJson.engines.yarn = ">=1.22.0"; // Ensure ESM module type for modern configuration packageJson.type = "module"; // Write updated package.json fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf8'); console.log(` βœ… Updated package.json (${addedDeps} new, ${updatedDeps} updated dependencies)`); // Debug: verify package.json was written correctly console.log(` πŸ” Package name: ${packageJson.name}`); // CRITICAL DEBUG: Re-read the file to verify what was actually written const verifyContent = fs.readFileSync(packageJsonPath, 'utf8'); const verifyJson = JSON.parse(verifyContent); console.log(` πŸ” VERIFICATION - File actually contains: ${verifyJson.name}`); } catch (error) { console.error(` ❌ Failed to update package.json: ${error}`); } } /** * Analyze centralized imports in source files (without modifying) */ async function analyzeCentralizedImports(targetPath: string): Promise<void> { const srcPath = path.join(targetPath, "src"); if (!await isValidPath(srcPath)) { console.log(` ℹ️ No src directory found`); return; } console.log(`\nπŸ” Analyzing centralized imports...`); try { // Find all TypeScript files recursively const findTsFiles = (dir: string): string[] => { const files: string[] = []; const items = fs.readdirSync(dir); for (const item of items) { const fullPath = path.join(dir, item); const stat = fs.statSync(fullPath); if (stat.isDirectory()) { files.push(...findTsFiles(fullPath)); } else if (item.endsWith('.ts') || item.endsWith('.tsx')) { files.push(fullPath); } } return files; }; const tsFiles = findTsFiles(srcPath); let filesWithImports = 0; for (const filePath of tsFiles) { try { const content = fs.readFileSync(filePath, 'utf8'); // Check for imports from obsidian-plugin-config const importRegex = /import\s+.*from\s+["']obsidian-plugin-config[^"']*["']/g; if (importRegex.test(content)) { filesWithImports++; console.log(` ⚠️ ${path.relative(targetPath, filePath)} - contains centralized imports`); } } catch (error) { console.warn(` ⚠️ Could not analyze ${path.relative(targetPath, filePath)}: ${error}`); } } if (filesWithImports === 0) { console.log(` βœ… No centralized imports found`); } else { console.log(` ⚠️ Found ${filesWithImports} files with centralized imports`); console.log(` πŸ’‘ You may need to manually comment these imports for the plugin to work`); } } catch (error) { console.error(` ❌ Failed to analyze imports: ${error}`); } } /** * Create required directories */ async function createRequiredDirectories(targetPath: string): Promise<void> { const directories = [ path.join(targetPath, ".github", "workflows") ]; for (const dir of directories) { if (!await isValidPath(dir)) { fs.mkdirSync(dir, { recursive: true }); console.log(` πŸ“ Created ${path.relative(targetPath, dir)}`); } } } /** * Create injection info file */ async function createInjectionInfo(targetPath: string): Promise<void> { const configRoot = findPluginConfigRoot(); const configPackageJsonPath = path.join(configRoot, "package.json"); let injectorVersion = "unknown"; try { const configPackageJson = JSON.parse(fs.readFileSync(configPackageJsonPath, "utf8")); injectorVersion = configPackageJson.version || "unknown"; } catch { console.warn("Warning: Could not read injector version"); } const injectionInfo = { injectorVersion, injectionDate: new Date().toISOString(), injectorName: "obsidian-plugin-config" }; const infoPath = path.join(targetPath, ".injection-info.json"); fs.writeFileSync(infoPath, JSON.stringify(injectionInfo, null, 2)); console.log(` βœ… Created injection info file (.injection-info.json)`); } /** * Read injection info from target plugin */ function readInjectionInfo(targetPath: string): any | null { const infoPath = path.join(targetPath, ".injection-info.json"); if (!fs.existsSync(infoPath)) { return null; } try { return JSON.parse(fs.readFileSync(infoPath, "utf8")); } catch { console.warn("Warning: Could not parse .injection-info.json"); return null; } } /** * Clean NPM artifacts if package-lock.json is found (evidence of NPM usage) */ async function cleanNpmArtifactsIfNeeded(targetPath: string): Promise<void> { const packageLockPath = path.join(targetPath, "package-lock.json"); // Only clean if package-lock.json exists (proof of NPM installation) if (fs.existsSync(packageLockPath)) { console.log(`\n🧹 NPM installation detected, cleaning artifacts...`); try { // Remove package-lock.json fs.unlinkSync(packageLockPath); console.log(` πŸ—‘οΈ Removed package-lock.json`); // Remove node_modules if it exists const nodeModulesPath = path.join(targetPath, "node_modules"); if (fs.existsSync(nodeModulesPath)) { fs.rmSync(nodeModulesPath, { recursive: true, force: true }); console.log(` πŸ—‘οΈ Removed node_modules (will be reinstalled with Yarn)`); } console.log(` βœ… NPM artifacts cleaned to avoid Yarn conflicts`); } catch (error) { console.error(` ❌ Failed to clean NPM artifacts: ${error}`); console.log(` πŸ’‘ You may need to manually remove package-lock.json and node_modules`); } } } /** * Check if tsx is installed locally and install it if needed */ async function ensureTsxInstalled(targetPath: string): Promise<void> { console.log(`\nπŸ” Checking tsx installation...`); const packageJsonPath = path.join(targetPath, "package.json"); try { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); const devDependencies = packageJson.devDependencies || {}; const dependencies = packageJson.dependencies || {}; // Check if tsx is already installed if (devDependencies.tsx || dependencies.tsx) { console.log(` βœ… tsx is already installed`); return; } console.log(` ⚠️ tsx not found, installing as dev dependency...`); // Install tsx as dev dependency execSync('yarn add -D tsx', { cwd: targetPath, stdio: 'inherit' }); console.log(` βœ… tsx installed successfully`); } catch (error) { console.error(` ❌ Failed to install tsx: ${error}`); console.log(` πŸ’‘ You may need to install tsx manually: yarn add -D tsx`); throw new Error('tsx installation failed'); } } /** * Run yarn install in target directory */ async function runYarnInstall(targetPath: string): Promise<void> { console.log(`\nπŸ“¦ Installing dependencies...`); try { execSync('yarn install', { cwd: targetPath, stdio: 'inherit' }); console.log(` βœ… Dependencies installed successfully`); } catch (error) { console.error(` ❌ Failed to install dependencies: ${error}`); console.log(` πŸ’‘ You may need to run 'yarn install' manually in the target directory`); } } /** * Main injection function */ export async function performInjection(targetPath: string, useSass: boolean = false): Promise<void> { console.log(`\nπŸš€ Starting injection process...`); try { // Step 1: Clean NPM artifacts if needed await cleanNpmArtifactsIfNeeded(targetPath); // Step 2: Ensure tsx is installed await ensureTsxInstalled(targetPath); // Step 3: Inject scripts await injectScripts(targetPath, useSass); // Step 4: Update package.json console.log(`\nπŸ“¦ Updating package.json...`); await updatePackageJson(targetPath, useSass); // Step 5: Analyze centralized imports (without modifying) await analyzeCentralizedImports(targetPath); // Step 6: Create required directories console.log(`\nπŸ“ Creating required directories...`); await createRequiredDirectories(targetPath); // Step 7: Install dependencies await runYarnInstall(targetPath); // Step 6: Create injection info file console.log(`\nπŸ“ Creating injection info...`); await createInjectionInfo(targetPath); // FINAL DEBUG: Check package.json one last time const finalPackageJsonPath = path.join(targetPath, "package.json"); const finalContent = fs.readFileSync(finalPackageJsonPath, 'utf8'); const finalJson = JSON.parse(finalContent); console.log(`\nπŸ” FINAL CHECK - Package name at end: ${finalJson.name}`); console.log(`\nβœ… Injection completed successfully!`); console.log(`\nπŸ“‹ Next steps:`); console.log(` 1. cd ${targetPath}`); console.log(` 2. yarn build # Test the build`); console.log(` 3. yarn start # Test development mode`); console.log(` 4. yarn acp # Commit changes (or yarn bacp for build+commit)`); } catch (error) { console.error(`\n❌ Injection failed: ${error}`); throw error; } } /** * Main function */ async function main(): Promise<void> { try { console.log(`🎯 Obsidian Plugin Config - Local Injection Tool`); console.log(`πŸ“₯ Inject autonomous configuration from local files\n`); // Parse command line arguments const args = process.argv.slice(2); const autoConfirm = args.includes('--yes') || args.includes('-y'); const dryRun = args.includes('--dry-run') || args.includes('--check'); const useSass = args.includes('--sass'); const targetPath = args.find(arg => !arg.startsWith('-')); if (!targetPath) { console.error(`❌ Usage: yarn inject-path <plugin-directory> [options]`); console.error(` Example: yarn inject-path ../my-obsidian-plugin`); console.error(` Options:`); console.error(` --yes, -y Auto-confirm injection`); console.error(` --dry-run Check only (no injection)`); console.error(` --sass Use SASS templates (includes esbuild-sass-plugin)`); console.error(` Shortcuts:`); console.error(` yarn check-plugin ../plugin # Verification only`); console.error(` yarn verify-plugin ../plugin # Alias pour check-plugin`); process.exit(1); } // Resolve and validate path const resolvedPath = path.resolve(targetPath); if (!await isValidPath(resolvedPath)) { console.error(`❌ Directory not found: ${resolvedPath}`); process.exit(1); } console.log(`πŸ“ Target directory: ${resolvedPath}`); // Analyze the plugin console.log(`\nπŸ” Analyzing plugin...`); const plan = await analyzePlugin(resolvedPath); // Show plan and ask for confirmation (or just show plan in dry-run mode) if (dryRun) { console.log(`\n🎯 Injection Plan for: ${plan.targetPath}`); console.log(`πŸ“ Target: ${path.basename(plan.targetPath)}`); console.log(`πŸ“¦ Package.json: ${plan.hasPackageJson ? 'βœ…' : '❌'}`); console.log(`πŸ“‹ Manifest.json: ${plan.hasManifest ? 'βœ…' : '❌'}`); console.log(`πŸ“‚ Scripts folder: ${plan.hasScriptsFolder ? 'βœ… (will be updated)' : '❌ (will be created)'}`); console.log(`πŸ”Œ Obsidian plugin: ${plan.isObsidianPlugin ? 'βœ…' : '❌'}`); console.log(`🎨 SASS support: ${useSass ? 'βœ… (esbuild-sass-plugin will be added)' : '❌'}`); if (!plan.isObsidianPlugin) { console.log(`\n⚠️ Warning: This doesn't appear to be a valid Obsidian plugin`); console.log(` Missing manifest.json or invalid structure`); } console.log(`\nπŸ“‹ Would inject:`); console.log(` βœ… Local scripts (utils.ts, esbuild.config.ts, acp.ts, etc.)`); console.log(` βœ… Updated package.json scripts`); console.log(` βœ… Required dependencies`); console.log(` πŸ” Analyze centralized imports (manual commenting may be needed)`); // Check for existing injection using new system const injectionInfo = readInjectionInfo(resolvedPath); if (injectionInfo) { console.log(`\nβœ… Status: Plugin is already injected`); console.log(` πŸ“¦ Injector: ${injectionInfo.injectorName || 'unknown'}`); console.log(` 🏷️ Version: ${injectionInfo.injectorVersion || 'unknown'}`); console.log(` πŸ“… Date: ${injectionInfo.injectionDate ? new Date(injectionInfo.injectionDate).toLocaleDateString() : 'unknown'}`); // Check if current version is newer const configRoot = findPluginConfigRoot(); const configPackageJsonPath = path.join(configRoot, "package.json"); try { const configPackageJson = JSON.parse(fs.readFileSync(configPackageJsonPath, "utf8")); const currentVersion = configPackageJson.version; if (currentVersion && injectionInfo.injectorVersion && currentVersion !== injectionInfo.injectorVersion) { console.log(` πŸ”„ Update available: ${injectionInfo.injectorVersion} β†’ ${currentVersion}`); } } catch { // Ignore version comparison errors } } else { // Fallback: check for legacy injection (scripts/utils.ts exists) const scriptsPath = path.join(resolvedPath, "scripts"); const hasInjectedScripts = fs.existsSync(path.join(scriptsPath, "utils.ts")); if (hasInjectedScripts) { console.log(`\n⚠️ Status: Plugin appears to be injected (legacy)`); console.log(` Found: scripts/utils.ts but no .injection-info.json`); console.log(` πŸ’‘ Re-inject to add version tracking`); } else { console.log(`\n❌ Status: Plugin not yet injected`); console.log(` Missing: .injection-info.json`); } } console.log(`\nπŸ” Dry-run completed - no changes made`); console.log(` To inject: yarn inject ${targetPath} --yes`); return; } // Ensure plugin-config repo is clean before injection console.log(`\nπŸ” Checking plugin-config repository status...`); await ensurePluginConfigClean(); const confirmed = await showInjectionPlan(plan, autoConfirm, useSass); if (!confirmed) { console.log(`❌ Injection cancelled by user`); process.exit(0); } // Perform injection await performInjection(resolvedPath, useSass); } catch (error) { console.error(`πŸ’₯ Error: ${error instanceof Error ? error.message : String(error)}`); process.exit(1); } finally { rl.close(); } } // Run the script main().catch(console.error);