ultimate-jekyll-manager
Version:
Ultimate Jekyll dependency manager
373 lines (310 loc) • 10.2 kB
JavaScript
// Libraries
const Manager = new (require('../../build.js'));
const logger = Manager.logger('jekyll');
const argv = Manager.getArguments();
const { series, watch } = require('gulp');
const glob = require('glob').globSync;
const path = require('path');
const { execute } = require('node-powertools');
const jetpack = require('fs-jetpack');
// Load package
const package = Manager.getPackage('main');
const project = Manager.getPackage('project');
const config = Manager.getConfig('project');
const rootPathPackage = Manager.getRootPath('main');
const rootPathProject = Manager.getRootPath('project');
// Set index
let index = -1;
// Flags
let browserSyncLaunched = false;
// Glob
const input = [
// Files to include
'dist/**/*',
// Include plugin so we can live-reload it
`${rootPathPackage}/../jekyll-uj-powertools/**/*`,
// Files to exclude
'!dist/.jekyll-cache/**',
'!dist/.jekyll-metadata',
];
const output = '';
const delay = 1500; // Increased from 500ms to allow distribute task to complete
// const delay = 500; // Increased from 500ms to allow distribute task to complete
// Task
async function jekyll(complete) {
// Wait for any active tasks (distribute, webpack, etc.) to complete
// await new Promise(resolve => {
// Manager.waitForTasks(() => {
// logger.log('All prerequisite tasks complete, proceeding with Jekyll build');
// resolve();
// });
// });
try {
// Log
logger.log('Starting...');
// Increment index
index++;
// Notify
if (global.browserSync) {
global.browserSync.notify('Rebuilding...');
}
// Run buildpre hook
await hook('build:pre', index);
// Build Jekyll
const command = [
// Jekyll command
'bundle exec jekyll build',
'--source dist',
'--config ' + [
// This is the base config file with all the user-editable settings
`./node_modules/${package.name}/dist/defaults/src/_config.yml`,
// This is the default config with NON user-editable settings
`./node_modules/${package.name}/dist/config/_config_default.yml`,
// This is the user's project config file
'dist/_config.yml',
// Add browsersync config IF BUILD_MODE is not true
Manager.isBuildMode() ? '' : '.temp/_config_browsersync.yml',
// Add development config IF BUILD_MODE is not true
Manager.isBuildMode() ? '' : `./node_modules/${package.name}/dist/config/_config_development.yml`,
].join(','),
'--incremental',
// '--disable-disk-cache',
]
// Log command
logger.log(`Running command: ${logger.format.gray(command.join(' '))}`);
// Build Jekyll
await execute(command.join(' '), {log: true});
// Log working URL
logger.log(`Your site is being served at: ${logger.format.cyan(Manager.getWorkingUrl())}`);
// Reposition "/blog/index.html" to "/blog.html" for compatibility
// This is needed because Jekyll creates a folder for the blog index page
// but we want it to be at the root level for compatibility with various hosting providers
await fixBlogIndex();
// Run buildpost hook
await hook('build:post', index);
// Create build JSON with runtime config
await createBuildJSON();
// Log
logger.log('Finished!');
// Launch browser sync
if (!browserSyncLaunched) {
launchBrowserSync();
}
// Reload browser
if (global.browserSync) {
global.browserSync.reload();
}
// Complete
return complete();
} catch (error) {
// Handle any errors that occur during Jekyll build
return Manager.reportBuildError(Object.assign(error, { plugin: 'Jekyll' }), complete);
}
}
// Watch for changes
function jekyllWatcher(complete) {
// Quit if in build mode
if (Manager.isBuildMode()) {
logger.log('[watcher] Skipping watcher in build mode');
return complete();
}
// Log
logger.log('[watcher] Watching for changes...');
// Watch for changes
watch(input, { delay: delay, dot: true }, jekyll)
.on('change', (path) => {
logger.log(`[watcher] File changed (${path})`);
// Check if changed file is a .rb file
if (path.endsWith('.rb')) {
logger.log(`[watcher] Detected .rb file, removing Jekyll cache...`);
jetpack.remove('dist/.jekyll-cache');
jetpack.remove('dist/.jekyll-metadata');
}
});
// Complete
return complete();
}
// Default Task
module.exports = series(jekyll, jekyllWatcher);
// Run hooks
async function hook(file, index) {
// Full path
const fullPath = path.join(process.cwd(), 'hooks', `${file}.js`);
// Log
// logger.log(`Loading hook: ${fullPath}`);
// Check if it exists
if (!jetpack.exists(fullPath)) {
return console.warn(`Hook not found: ${fullPath}`);
}
// Log
logger.log(`Running hook: ${fullPath}`);
// Load hook
let hook;
try {
// Load the hook
hook = require(fullPath);
} catch (e) {
throw new Error(`Error loading hook: ${fullPath} ${e.stack}`);
}
// Execute hook
try {
return await hook(index);
} catch (e) {
throw new Error(`Error running hook: ${fullPath} ${e.stack}`);
}
}
// Launch browser sync
async function launchBrowserSync() {
// Quit if in build mode
if (Manager.isBuildMode()) {
// Log
return logger.log('Skipping browser sync since in build mode');
}
// Quit if browser argument is false
if (argv.browser === false) {
// Log
return logger.log('Skipping browser sync since browser argument is false');
}
// Get external URL
const externalUrl = Manager.getWorkingUrl();
// Check if the tab is already open
const isOpen = await isBrowserTabOpen(externalUrl);
// Set flag
browserSyncLaunched = true
// Log
if (isOpen) {
logger.log('Switched to existing browser tab');
return;
}
// Log
logger.log(`Opening browser to: ${externalUrl}`);
// Open the browser with the external URL
return execute(`open ${externalUrl}`);
}
// Check if browser tab is open
async function isBrowserTabOpen(url) {
try {
const script = `
set logOutput to ""
set foundMatch to false
tell application "Google Chrome"
set windowList to every window
repeat with w in windowList
set tabList to every tab of w
set tabIndex to 1
repeat with t in tabList
set tabURL to URL of t
set logOutput to logOutput & "Checking: " & tabURL & "\\n"
if tabURL contains "${url}" then
tell w to set active tab index to tabIndex
tell application "System Events" to tell process "Google Chrome" to perform action "AXRaise" of window 1
activate
set foundMatch to true
tell t to reload
end if
set tabIndex to tabIndex + 1
end repeat
end repeat
end tell
return foundMatch
`;
// Execute
const result = await execute(`osascript -e '${script}'`);
// Convert to boolean
return result.trim() === 'true'; // Convert to boolean
} catch (error) {
return false;
}
}
// Get git info
async function getGitInfo() {
return await execute('git remote -v')
.then((r) => {
// Split on whitespace
const split = r.split(/\s+/);
const url = split[1];
// Get user and repo
const user = url.split('/')[3];
const name = url.split('/')[4].replace('.git', '');
// Return
return {user, name};
})
}
// Get runtime Jekyll config
async function getRuntimeConfig() {
try {
// Check if Jekyll generated the site config file
const siteConfigPath = '_site/config.json';
if (jetpack.exists(siteConfigPath)) {
const runtimeConfig = jetpack.read(siteConfigPath, 'json');
logger.log('Using runtime Jekyll config from config.json');
return runtimeConfig;
}
// Fallback to static config if runtime config not found
logger.log('No runtime config found at _site/config.json, using static config');
return config;
} catch (e) {
logger.error('Error reading runtime config:', e);
return config;
}
}
// Fix blog index positioning
async function fixBlogIndex() {
try {
const blogIndexPath = '_site/blog/index.html';
const blogTargetPath = '_site/blog.html';
// Check if blog/index.html exists
if (jetpack.exists(blogIndexPath)) {
// Move blog/index.html to blog.html
jetpack.move(blogIndexPath, blogTargetPath, { overwrite: true });
// Remove empty blog directory if it exists and is empty
const blogDir = '_site/blog';
if (jetpack.exists(blogDir) && jetpack.list(blogDir).length === 0) {
jetpack.remove(blogDir);
}
logger.log('Moved blog/index.html to blog.html');
}
} catch (e) {
logger.error('Error fixing blog index:', e);
}
}
// Create build.json
async function createBuildJSON() {
// Create build log JSON
try {
// Get info first
const git = await getGitInfo();
// Get runtime config
const runtimeConfig = await getRuntimeConfig();
// Create JSON
const json = {
timestamp: new Date().toISOString(),
repo: {
user: git.user,
name: git.name,
},
environment: Manager.getEnvironment(),
packages: {
'web-manager': require('web-manager/package.json').version,
[package.name]: package.version,
},
config: runtimeConfig,
}
// Add legacy properties
// TODO: REMOVE WHEN THE SITE HAS BEEN ACTIVE FOR SOME TIME SO USERS ARE FORCED TO NEW BUILD.JSON
{
json['npm-build'] = new Date().toISOString();
// json.brand = config.brand;
// json['admin-dashboard'] = JSON.parse(config['admin-dashboard']);
}
// Write to file
jetpack.write('_site/build.json', JSON.stringify(json, null, 2));
// Write to legacy file /@output/build/build.json
// TODO: REMOVE WHEN THE SITE HAS BEEN ACTIVE FOR SOME TIME SO USERS ARE FORCED TO NEW BUILD.JSON
jetpack.write('_site/@output/build/build.json', JSON.stringify(json, null, 2));
// Log
logger.log('Created build.json with runtime config');
} catch (e) {
console.error('Error updating build.json', e);
}
}