@combino/plugin-rebase
Version:
Rebase plugin for Combino - handles relative path calculations for templatefiles
224 lines • 10.8 kB
JavaScript
import * as path from 'path';
/**
* Calculates the path from the final output location to a target path based on the specified path type
*/
function calculateRebasePath(targetPath, outputFilePath, options = {}) {
const { baseDir = path.dirname(outputFilePath), normalize = true, projectRoot = process.cwd(), format = 'relative', locate = false, } = options;
// Resolve the target path relative to project root
const resolvedTargetPath = path.isAbsolute(targetPath) ? targetPath : path.resolve(projectRoot, targetPath);
// Resolve the output file path
const resolvedOutputPath = path.resolve(outputFilePath);
let resultPath;
let targetPathToUse;
// If locate is enabled, find the actual output location of the referenced file
if (locate) {
// Look for the src directory or similar source directory
const currentFileDir = path.dirname(resolvedOutputPath);
let srcDir = currentFileDir;
while (srcDir !== path.dirname(srcDir)) {
if (path.basename(srcDir) === 'src' || path.basename(srcDir) === 'source') {
break;
}
srcDir = path.dirname(srcDir);
}
// If we found a src directory, resolve relative to the src directory itself
// Otherwise, resolve relative to the current file's directory
const fileBaseDir = path.basename(srcDir) === 'src' || path.basename(srcDir) === 'source' ? srcDir : currentFileDir;
// Resolve the target path relative to the base directory
targetPathToUse = path.resolve(fileBaseDir, targetPath);
}
else {
targetPathToUse = resolvedTargetPath;
}
switch (format) {
case 'cwd':
// Calculate path relative to process.cwd()
resultPath = path.relative(process.cwd(), targetPathToUse);
break;
case 'absolute':
// Return absolute path
resultPath = targetPathToUse;
break;
case 'relative':
default:
// Calculate relative path from output file location to target
resultPath = path.relative(baseDir, targetPathToUse);
break;
}
// Normalize the path if requested
if (normalize) {
resultPath = path.normalize(resultPath);
}
// Ensure the path uses forward slashes for consistency
resultPath = resultPath.replace(/\\/g, '/');
// Handle edge cases for relative paths
if (format !== 'absolute') {
if (resultPath === '') {
return '.';
}
if (resultPath === '.') {
return '.';
}
// Add dot notation for relative paths when not already present
if (format === 'relative' && !resultPath.startsWith('./') && !resultPath.startsWith('../')) {
resultPath = './' + resultPath;
}
}
return resultPath;
}
/**
* Creates a rebase function that can be used in templates
*/
function createRebaseFunction(outputFilePath, pluginOptions = {}) {
return (targetPath, options) => {
if (typeof targetPath !== 'string') {
throw new Error('rebase() expects a string argument');
}
// Handle the options
let finalOptions = { ...pluginOptions };
let finalFormat = pluginOptions.format || 'relative';
let locate = false;
// If options object is provided, use it
if (options && typeof options === 'object') {
if (options.locate) {
locate = true;
}
if (options.format && ['cwd', 'relative', 'absolute'].includes(options.format)) {
finalFormat = options.format;
}
}
finalOptions.format = finalFormat;
finalOptions.locate = locate;
return calculateRebasePath(targetPath, outputFilePath, finalOptions);
};
}
/**
* Rebase Plugin Factory Function
* Creates a plugin that provides a rebase() function for relative path calculations
*/
export default function plugin(options = {}) {
return {
// Compile hook: Add rebase function to template data and process static rebase() calls
compile: async (context) => {
// Only process files that contain rebase() calls
if (!context.content.includes('rebase(')) {
return;
}
// Create the rebase function for this specific file
const rebase = createRebaseFunction(context.id, options);
// Add rebase function to the template data so EJS can access it
// Note: This modifies the context.data object directly
context.data.rebase = rebase;
// Process static rebase() calls (those with literal strings) before EJS compilation
let processedContent = context.content;
// Replace static rebase() calls with their calculated values
// This regex matches rebase('path') or rebase("path") patterns with literal strings
processedContent = processedContent.replace(/rebase\(['"`]([^'"`]+)['"`]\)/g, (match, targetPath) => {
// Skip if the targetPath contains template variables (${...})
if (targetPath.includes('${')) {
return match; // Let EJS handle this
}
try {
const relativePath = rebase(targetPath);
return `"${relativePath}"`;
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`Warning: Failed to process rebase('${targetPath}'):`, errorMessage);
return match; // Return original if processing fails
}
});
// Handle static rebase('path', {options}) patterns
processedContent = processedContent.replace(/rebase\(['"`]([^'"`]+)['"`]\s*,\s*(\{[^}]+\})\)/g, (match, targetPath, optionsStr) => {
// Skip if the targetPath contains template variables (${...})
if (targetPath.includes('${')) {
return match; // Let EJS handle this
}
try {
// Parse the options object
const options = eval(`(${optionsStr})`);
const relativePath = rebase(targetPath, options);
return `"${relativePath}"`;
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`Warning: Failed to process rebase('${targetPath}', ${optionsStr}):`, errorMessage);
return match; // Return original if processing fails
}
});
// Also handle static rebase() calls without quotes
processedContent = processedContent.replace(/rebase\(([^)]+)\)/g, (match, targetPath) => {
// Skip if it's already been processed (has quotes)
if (match.includes('"') || match.includes("'") || match.includes('`')) {
return match;
}
// Skip if the targetPath contains template variables (${...})
if (targetPath.includes('${')) {
return match; // Let EJS handle this
}
// Check if this is a two-argument call (path, pathType)
const args = targetPath.split(',').map((arg) => arg.trim());
if (args.length === 2) {
try {
const relativePath = rebase(args[0], args[1]);
return `"${relativePath}"`;
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`Warning: Failed to process rebase(${args[0]}, ${args[1]}):`, errorMessage);
return match; // Return original if processing fails
}
}
try {
const relativePath = rebase(targetPath.trim());
return `"${relativePath}"`;
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`Warning: Failed to process rebase(${targetPath}):`, errorMessage);
return match; // Return original if processing fails
}
});
return { content: processedContent };
},
// Assemble hook: Process any remaining rebase() calls after EJS compilation
assemble: async (context) => {
// Only process files that contain rebase() calls
if (!context.content.includes('rebase(')) {
return;
}
// Create the rebase function for this specific file
const rebase = createRebaseFunction(context.id, options);
// Process any remaining rebase() calls after EJS compilation
let processedContent = context.content;
// Replace any remaining rebase() calls with their calculated values
processedContent = processedContent.replace(/rebase\(['"`]([^'"`]+)['"`]\)/g, (match, targetPath) => {
try {
const relativePath = rebase(targetPath);
return `"${relativePath}"`;
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`Warning: Failed to process rebase('${targetPath}'):`, errorMessage);
return match; // Return original if processing fails
}
});
// Handle any remaining rebase('path', {options}) patterns
processedContent = processedContent.replace(/rebase\(['"`]([^'"`]+)['"`]\s*,\s*(\{[^}]+\})\)/g, (match, targetPath, optionsStr) => {
try {
// Parse the options object
const options = eval(`(${optionsStr})`);
const relativePath = rebase(targetPath, options);
return `"${relativePath}"`;
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`Warning: Failed to process rebase('${targetPath}', ${optionsStr}):`, errorMessage);
return match; // Return original if processing fails
}
});
return { content: processedContent };
},
};
}
//# sourceMappingURL=index.js.map