builderman verion 0.1.0, initial commit/release.
This commit is contained in:
181
index.js
Normal file
181
index.js
Normal file
@@ -0,0 +1,181 @@
|
||||
//builderman
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const CleanCSS = require('clean-css');
|
||||
const minify = require('minify');
|
||||
const { XMLParser } = require('fast-xml-parser');
|
||||
const cheerio = require('cheerio');
|
||||
|
||||
const cssMinifier = new CleanCSS({
|
||||
level: 2,
|
||||
compatibility: '*'
|
||||
});
|
||||
|
||||
// Error handler function
|
||||
// ts = error message, err = error type ('nofile' or 'generic')
|
||||
// In order to implement error logging, we need to wait untill we load the config.
|
||||
// Otherwise I can guarantee that shenanigans will occur
|
||||
function fixts(ts, err) {
|
||||
if (err === 'nofile') {
|
||||
console.error(`There be nothing: ${ts}`);
|
||||
} else {
|
||||
//NOTE: Sometimes "generic" will be called. we don't actually check it. if we get an error that isnt nofile, it's generic.
|
||||
console.error(`This code sucks: ${ts}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadConfig(configPath) {
|
||||
if (!await fs.pathExists(configPath)) {
|
||||
fixts(configPath, 'nofile');
|
||||
throw new Error('Config file not found');
|
||||
}
|
||||
const configContent = await fs.readFile(configPath, 'utf8');
|
||||
const parser = new XMLParser({
|
||||
ignoreAttributes: false,
|
||||
attributeNamePrefix: '',
|
||||
allowBooleanAttributes: true,
|
||||
parseTagValue: true,
|
||||
isArray: (name, jpath, isLeafNode, isAttribute) => {
|
||||
return name === 'build' || name === 'tag';
|
||||
}
|
||||
});
|
||||
try {
|
||||
const config = parser.parse(configContent);
|
||||
return config.builds.build;
|
||||
} catch (err) {
|
||||
fixts('Failed to parse config.xml', 'generic');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function processHTML(htmlContent, buildConfig) {
|
||||
const $ = cheerio.load(htmlContent, { xmlMode: false });
|
||||
|
||||
// Handle includes - if specified, keep only those tags
|
||||
if (buildConfig.include && buildConfig.include.tag) {
|
||||
const includes = Array.isArray(buildConfig.include.tag)
|
||||
? buildConfig.include.tag
|
||||
: [buildConfig.include.tag];
|
||||
|
||||
// Remove non-included tags at root level only when includes are specified
|
||||
$('body > *').each((_, elem) => {
|
||||
if (!includes.includes(elem.tagName)) {
|
||||
$(elem).remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
// No includes specified means keep everything (except excludes)
|
||||
|
||||
// Handle excludes - always remove specified tags
|
||||
if (buildConfig.exclude && buildConfig.exclude.tag) {
|
||||
const excludes = Array.isArray(buildConfig.exclude.tag)
|
||||
? buildConfig.exclude.tag
|
||||
: [buildConfig.exclude.tag];
|
||||
|
||||
excludes.forEach(tag => {
|
||||
$(tag).remove();
|
||||
});
|
||||
}
|
||||
|
||||
return $.html();
|
||||
}
|
||||
|
||||
async function processBuild(inputDir, outputDir, buildConfig) {
|
||||
if (!await fs.pathExists(inputDir)) {
|
||||
fixts(inputDir, 'nofile');
|
||||
throw new Error('Input directory not found');
|
||||
}
|
||||
|
||||
const buildOutputDir = path.join(outputDir, buildConfig.name);
|
||||
await fs.ensureDir(buildOutputDir);
|
||||
|
||||
// Process HTML template
|
||||
const htmlFiles = await fs.readdir(inputDir);
|
||||
for (const file of htmlFiles) {
|
||||
if (file.endsWith('.html')) {
|
||||
const inputFile = path.join(inputDir, file);
|
||||
if (!await fs.pathExists(inputFile)) {
|
||||
fixts(inputFile, 'nofile');
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const htmlContent = await fs.readFile(inputFile, 'utf8');
|
||||
const processedHTML = await processHTML(htmlContent, buildConfig);
|
||||
|
||||
// Save the processed HTML to a temporary file for minification
|
||||
const tempFile = path.join(buildOutputDir, 'temp_' + file);
|
||||
await fs.writeFile(tempFile, processedHTML, 'utf8');
|
||||
|
||||
// Minify using the file path
|
||||
const minified = await minify(tempFile);
|
||||
await fs.writeFile(
|
||||
path.join(buildOutputDir, file),
|
||||
minified,
|
||||
'utf8'
|
||||
);
|
||||
|
||||
// Clean up temp file
|
||||
await fs.remove(tempFile);
|
||||
} catch (err) {
|
||||
fixts(`Error processing ${file}: ${err}`, 'generic');
|
||||
// On error, at least save the unminified version
|
||||
const debugFile = path.join(buildOutputDir, 'debug_' + file);
|
||||
await fs.writeFile(debugFile, processedHTML, 'utf8');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process associated stylesheet
|
||||
if (buildConfig.style && buildConfig.style.src) {
|
||||
const cssFile = path.join(inputDir, buildConfig.style.src);
|
||||
if (!await fs.pathExists(cssFile)) {
|
||||
fixts(cssFile, 'nofile');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const cssContent = await fs.readFile(cssFile, 'utf8');
|
||||
const minified = cssMinifier.minify(cssContent);
|
||||
await fs.writeFile(
|
||||
path.join(buildOutputDir, buildConfig.style.src),
|
||||
minified.styles,
|
||||
'utf8'
|
||||
);
|
||||
} catch (err) {
|
||||
fixts(`Error processing CSS file ${buildConfig.style.src}: ${err}`, 'generic');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function processFiles(inputDir, outputDir) {
|
||||
if (!await fs.pathExists(inputDir)) {
|
||||
fixts(inputDir, 'nofile');
|
||||
throw new Error('Input directory not found');
|
||||
}
|
||||
|
||||
// Remove old dist directory if it exists
|
||||
await fs.remove(outputDir);
|
||||
await fs.ensureDir(outputDir);
|
||||
|
||||
try {
|
||||
// Load and parse config
|
||||
const configPath = path.join(inputDir, 'config.xml');
|
||||
const builds = await loadConfig(configPath);
|
||||
|
||||
// Process each build configuration
|
||||
const buildConfigs = Array.isArray(builds) ? builds : [builds];
|
||||
for (const buildConfig of buildConfigs) {
|
||||
await processBuild(inputDir, outputDir, buildConfig);
|
||||
}
|
||||
} catch (err) {
|
||||
fixts(`Build process failed: ${err}`, 'generic');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// Run if called directly
|
||||
if (require.main === module) {
|
||||
const inputDir = process.argv[2] || 'src';
|
||||
const outputDir = process.argv[3] || 'dist';
|
||||
|
||||
processFiles(inputDir, outputDir).catch(err => fixts(err, 'generic'));
|
||||
}
|
||||
Reference in New Issue
Block a user