Files
webawesome/packages/webawesome/scripts/docs.js
2025-07-18 13:41:03 -04:00

269 lines
6.8 KiB
JavaScript

import Eleventy from '@11ty/eleventy';
import copy from 'recursive-copy';
import chalk from 'chalk';
import { deleteAsync } from 'del';
import { join } from 'path';
import { getCdnDir, getDocsDir, getEleventyConfigPath, getSiteDir } from './utils.js';
let eleventyBuildResolver;
let eleventyBuildPromise;
function queueBuild() {
eleventyBuildPromise = new Promise(resolve => {
eleventyBuildResolver = resolve;
});
}
// 11ty
export async function createEleventy(options = {}) {
let { isIncremental, isDeveloping, rootDir } = options;
isDeveloping ??= process.argv.includes('--develop');
isIncremental ??= isDeveloping && !process.argv.includes('--no-incremental');
const eleventy = new Eleventy(rootDir || getDocsDir(), getSiteDir(), {
quietMode: true,
configPath: getEleventyConfigPath(),
config: eleventyConfig => {
if (isDeveloping || isIncremental) {
eleventyConfig.setUseTemplateCache(false);
eleventyConfig.on('eleventy.before', function () {
queueBuild();
});
eleventyConfig.on('eleventy.beforeWatch', async function () {
queueBuild();
});
eleventyConfig.on('eleventy.after', async function () {
eleventyBuildResolver();
});
}
},
source: 'script',
runMode: isIncremental ? 'watch' : 'build',
});
eleventy.setIncrementalBuild(isIncremental);
await eleventy.init();
eleventy.logger.isChalkEnabled = false;
eleventy.logger.overrideLogger(new CustomLogger());
if (isIncremental) {
await eleventy.watch();
process.on('SIGINT', async () => {
await eleventy.stopWatch();
process.exitCode = 0;
});
}
return eleventy;
}
/**
* Generates the documentation site.
*/
export async function generateDocs(options = {}) {
let { spinner, isIncremental, isDeveloping } = options;
isDeveloping ??= process.argv.includes('--develop');
isIncremental ??= isDeveloping && !process.argv.includes('--no-incremental');
/**
* Used by the webawesome-app to skip doc generation since it will do its own.
*/
if (process.env.SKIP_ELEVENTY === 'true') {
return;
}
let eleventy = globalThis.eleventy;
spinner?.start?.('Writing the docs');
const outputs = {
warn: [],
};
function stubConsole(key) {
const originalFn = console[key];
console[key] = function (...args) {
outputs[key].push(...args);
};
return originalFn;
}
// Works around a bug in 11ty where it still prints warnings despite the logger being overriden and in quietMode.
const originalWarn = stubConsole('warn');
let output = '';
try {
if (isIncremental) {
if (!globalThis.eleventy) {
// First run
globalThis.eleventy = await createEleventy(options);
eleventy = globalThis.eleventy;
output = chalk.gray(`(${eleventy.logFinished()})`);
} else {
// eleventy incremental does its own writing, so we just kinda trust it for right now.
eleventy = globalThis.eleventy;
await eleventyBuildPromise;
let info = eleventy.logger.logger.outputs.log;
// TODO: The first write with incremental seems to be 1 behind. Not sure why. But its good enough for now.
info = info.filter(line => {
return !line.includes('Watching');
});
const lastLine = info[info.length - 1];
output = chalk.gray(`(${info.join('')})`);
eleventy.logger.logger.reset();
}
} else {
// Cleanup
await deleteAsync(getSiteDir());
globalThis.eleventy = await createEleventy(options);
eleventy = globalThis.eleventy;
// Write it
await eleventy.write();
output = chalk.gray(`(${eleventy.logFinished()})`);
}
// Copy dist (production only)
if (!isDeveloping) {
await copy(getCdnDir(), join(getSiteDir(), 'dist'));
}
if (spinner) {
spinner.succeed(`Writing the docs ${output}`);
} else {
console.log(`Writing the docs ${output}`);
}
} catch (error) {
console.warn = originalWarn;
console.error('\n\n' + chalk.red(error) + '\n');
if (spinner) {
spinner.fail(chalk.red(`Error while writing the docs.`));
} else {
console.error(chalk.red(`Error while writing the docs.`));
}
if (!isDeveloping) {
process.exit(1);
}
}
}
/**
* Much of this code is taken from 11ty's ConsoleLogger here:
* https://github.com/11ty/eleventy/blob/main/src/Util/ConsoleLogger.js
*
* Patches 11ty logger so it doesnt log everything, but we can still use its output for our own build.
* @typedef {'error'|'log'|'warn'|'info'} LogType
*/
class CustomLogger {
#outputStream;
constructor() {
this.reset();
}
flush() {
Object.keys(this.outputs).forEach(outputType => {
console[outputType](this.outputs[outputType].join(''));
});
this.reset();
}
reset() {
this.outputs = {
log: [],
info: [],
warn: [],
error: [],
};
}
/** @param {string} msg */
log(msg) {
this.message(msg);
}
/**
* @typedef LogOptions
* @property {string} message
* @property {string=} prefix
* @property {LogType=} type
* @property {string=} color
* @property {boolean=} force
* @param {LogOptions} options
*/
logWithOptions({ message, type, prefix, color, force }) {
this.message(message, type, color, force, prefix);
}
/** @param {string} msg */
forceLog(msg) {
this.message(msg, undefined, undefined, true);
}
/** @param {string} msg */
info(msg) {
this.message(msg, 'info', 'blue');
}
/** @param {string} msg */
warn(msg) {
this.message(msg, 'warn', 'yellow');
}
/** @param {string} msg */
error(msg) {
this.message(msg, 'error', 'red');
}
get outputStream() {
if (!this.#outputStream) {
this.#outputStream = new Readable({
read() {},
});
}
return this.#outputStream;
}
/** @param {string} msg */
toStream(msg) {
this.outputStream.push(msg);
}
closeStream() {
this.outputStream.push(null);
return this.outputStream;
}
/**
* Formats the message to log.
*
* @param {string} message - The raw message to log.
* @param {LogType} [type='log'] - The error level to log.
* @param {string|undefined} [chalkColor=undefined] - Color name or falsy to disable
* @param {boolean} [forceToConsole=false] - Enforce a log on console instead of specified target.
*/
message(message, type = 'log', chalkColor = undefined, _forceToConsole = false, prefix = '') {
// if (chalkColor && this.isChalkEnabled) {
// message = `${chalk.gray(prefix)} ${message.split("\n").join(`\n${chalk.gray(prefix)} `)}`;
// this.outputs[type].push(chalk[chalkColor](message));
// } else {
message = `${prefix}${message.split('\n').join(`\n${prefix}`)}`;
this.outputs[type].push(message);
// }
}
}