obsidian-plugin-config
Version:
Système d'injection pour plugins Obsidian autonomes
883 lines (740 loc) β’ 30.7 kB
text/typescript
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);