-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ba28204
commit 4832ba6
Showing
44 changed files
with
5,406 additions
and
6,254 deletions.
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
#!/usr/bin/env node | ||
|
||
// cli.js | ||
import fs from 'fs'; | ||
import { fileURLToPath } from 'url'; | ||
import { Command } from 'commander'; | ||
import convert from './lib/convert.js'; | ||
import path from 'path'; | ||
import nodemon from 'nodemon'; | ||
import { readFileSync, existsSync } from 'fs'; | ||
import { spawn } from 'child_process'; | ||
|
||
const __filename = fileURLToPath(import.meta.url); | ||
const __dirname = path.dirname(__filename); | ||
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8')); | ||
|
||
const manuscriptDir = path.join(process.cwd(), 'manuscript'); | ||
|
||
const program = new Command(); | ||
program.version(packageJson.version); | ||
|
||
async function buildHtml(manuscriptDir, outputDir, outputType) { | ||
await convert(manuscriptDir, outputDir, outputType); | ||
} | ||
|
||
async function buildPdf(manuscriptDir, outputDir, outputType) { | ||
await buildHtml(manuscriptDir, outputDir, outputType); | ||
|
||
// Run the prince command-line tool | ||
const prince = spawn('prince', ['build/pdf/index.html'], { stdio: 'inherit' }); | ||
|
||
prince.on('error', (error) => { | ||
console.error(`Error: ${error.message}`); | ||
}); | ||
|
||
return prince; | ||
} | ||
|
||
program | ||
.command('build') | ||
.option('-t, --type <type>', 'Specify the output type (html or pdf)', 'html') | ||
.description('Build the output from the manuscript markdown files') | ||
.action(async (options) => { | ||
console.log(`Building ${options.type.toUpperCase()}...`); | ||
|
||
if (options.type === 'html') { | ||
await buildHtml(manuscriptDir, path.join(process.cwd(), 'build', 'html'), options.type); | ||
} else if (options.type === 'pdf') { | ||
await buildPdf(manuscriptDir, path.join(process.cwd(), 'build', 'pdf'), options.type); | ||
} else { | ||
console.error('Invalid output type specified. Use either "html" or "pdf".'); | ||
} | ||
}); | ||
|
||
program | ||
.command('dev') | ||
.option('-t, --type <type>', 'Specify the output type (html or pdf)', 'html') | ||
.description('Run the development server with live-reloading') | ||
.action(async (options) => { | ||
console.log(`Running Nodemon and Webpack Server for ${options.type.toUpperCase()}...`); | ||
|
||
if (options.type === 'html') { | ||
await buildHtml(manuscriptDir, path.join(process.cwd(), 'build', 'html'), options.type); | ||
await runWebpackDevServerAsync('html'); | ||
await runNodemonAsync('html'); | ||
} else if (options.type === 'pdf') { | ||
await buildPdf(manuscriptDir, path.join(process.cwd(), 'build', 'pdf'), options.type); | ||
await runWebpackDevServerAsync('pdf'); | ||
await runNodemonAsync('pdf'); | ||
} else { | ||
console.error('Invalid output type specified. Use either "html" or "pdf".'); | ||
} | ||
}); | ||
|
||
// Setup nodemon function to return as a Promise | ||
function runNodemonAsync(outputType) { | ||
return new Promise((resolve, reject) => { | ||
runNodemon(outputType).on('quit', resolve).on('error', reject); | ||
}); | ||
} | ||
|
||
// Run the webpack server using the user's webpack.config.js | ||
function runWebpackDevServerAsync(outputType) { | ||
const server = spawn( | ||
'npx', | ||
['webpack', 'serve', '--env', `outputType=${outputType}`], | ||
{ stdio: 'inherit' } | ||
); | ||
|
||
server.on('error', (error) => { | ||
console.error(`Error: ${error.message}`); | ||
}); | ||
|
||
return server; | ||
} | ||
|
||
// Use Nodemon to watch for changes and rebuild/serve/refresh | ||
|
||
// Helper function to validate the user's nodemon.json file | ||
function validateUserNodemonConfig(config) { | ||
if (!config || !config.execMap || !config.execMap.html) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
function runNodemon(outputType) { | ||
const userNodemonConfigPath = path.join(process.cwd(), 'nodemon.json'); | ||
let nodemonConfig = {}; | ||
|
||
// Check if the user's nodemon.json file exists | ||
if (existsSync(userNodemonConfigPath)) { | ||
const userNodemonConfig = JSON.parse(readFileSync(userNodemonConfigPath, 'utf-8')); | ||
|
||
// Validate the user's nodemon.json configuration | ||
if (validateUserNodemonConfig(userNodemonConfig)) { | ||
nodemonConfig = { configFile: userNodemonConfigPath }; | ||
} | ||
} | ||
|
||
// If the user's nodemon.json file is not found or is not valid, use default settings | ||
if (!nodemonConfig.configFile) { | ||
console.log(`Using default Nodemon settings with outputType: ${outputType}.`); | ||
nodemonConfig = { | ||
script: __filename, | ||
ext: outputType === 'pdf' ? 'md,mdx,js,ejs,json,html,css,yaml' : 'md,mdx,js,ejs,json,html,css,yaml', | ||
exec: `bookshop build --type ${outputType}`, | ||
watch: 'manuscript', | ||
}; | ||
} | ||
|
||
return nodemon(nodemonConfig).on('restart', async () => { | ||
console.log(`Rebuilding ${outputType.toUpperCase()}...`); | ||
if (outputType === 'html') { | ||
await buildHtml(manuscriptDir, path.join(process.cwd(), 'build', 'html'), outputType); | ||
} else if (outputType === 'pdf') { | ||
await buildPdf(manuscriptDir, path.join(process.cwd(), 'build', 'pdf'), outputType); | ||
} | ||
}); | ||
} | ||
|
||
|
||
program.parseAsync(process.argv); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import convert from './lib/convert.js'; | ||
|
||
convert(); |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import fs from 'fs'; | ||
import ejs from 'ejs'; | ||
import fse from 'fs-extra'; | ||
import path from 'path'; | ||
import { unified } from 'unified'; | ||
import remarkGfm from 'remark-gfm'; | ||
import remarkParse from 'remark-parse'; | ||
import remarkDirective from 'remark-directive'; | ||
import remarkHtml from 'remark-html'; | ||
import yaml from 'js-yaml'; | ||
import sass from 'sass'; | ||
|
||
// Small hack to import commonJS Smartypants into our ES Module | ||
import { createRequire } from 'module'; | ||
const require = createRequire(import.meta.url); | ||
const SmartyPants = require('smartypants'); | ||
|
||
// Convert markdown and source files in /manuscript into | ||
// one .html file in build/html | ||
export default async function convert(manuscriptDir, outputDir, outputType) { | ||
try { | ||
|
||
// Define outputFile within the convert function | ||
const outputFile = path.join(outputDir, 'index.html'); | ||
|
||
// Set initial run variable to false (will be assigned true if already run) | ||
let hasRun = false; | ||
|
||
// Make sure this only gets called once per build | ||
if (hasRun) { | ||
return; | ||
} | ||
hasRun = true; | ||
// Create the output directory if it doesn't exist | ||
if (!fs.existsSync(outputDir)) { | ||
fs.mkdirSync(outputDir, { recursive: true }); | ||
} | ||
|
||
// Load and log the book.yml file | ||
console.log("Loading book.config.yml...\n"); | ||
const book = yaml.load(fs.readFileSync(path.join(manuscriptDir, '../book.config.yml'), 'utf8')); | ||
|
||
// Load the files listed | ||
const entryFile = book.settings.entryfile; | ||
|
||
// Read the contents of each file and concatenate them into one markdown document | ||
console.log(`Loading manuscript source files...\n`); | ||
const templatePath = path.join(manuscriptDir, entryFile); | ||
const templateContent = fs.readFileSync(templatePath, 'utf8'); | ||
|
||
// Render the EJS template with the book data and processed HTML content | ||
const processedEJS = ejs.render(templateContent, { | ||
meta: book.meta, | ||
outputType: outputType, | ||
manuscriptDir: manuscriptDir, | ||
path: path, | ||
}, { views: [manuscriptDir] }); | ||
|
||
// Convert the markdown document to HTML using Unified Remark | ||
console.log("Converting manuscript Markdown files into HTML...\n"); | ||
const html = unified() | ||
// convert markdown to a MD-AST syntax tree | ||
.use(remarkParse) | ||
// convert md-ast to gfm-ast (Github Flavored Markdown) and pass options | ||
.use(remarkGfm, book['gfm-options']) | ||
// convert md-ast into directive-ast | ||
// allows plugin creation for custom directives | ||
.use(remarkDirective) | ||
// convert gfm-ast to html-ast | ||
// but don't git rid of none-markdown syntax | ||
.use(remarkHtml, { sanitize: false }) | ||
.processSync(processedEJS) | ||
// convert to a string | ||
.toString(); | ||
|
||
// Use Smartypants to process symbols and quotes | ||
console.log("Converting symbols to smart symbols with \"smartypants\"...\n"); | ||
const smartHtml = SmartyPants.default(html) | ||
|
||
// Write final output to output file (build/html/index.html) | ||
fs.writeFileSync(outputFile, smartHtml); | ||
|
||
// Copy the contents of the "./manuscript/theme" directory into the "./build/html" directory | ||
const themeSrcDir = path.join(manuscriptDir, 'theme'); | ||
const themeDestDir = path.join(outputDir, 'theme'); | ||
|
||
console.log("Copying theme into build folder, but ignoring styles..css...\n"); | ||
// Copy the theme/css directory, but not its contents | ||
await fse.copy(themeSrcDir, themeDestDir, { | ||
filter: (src) => { | ||
// If there is a theme/css directory, include it | ||
if (fs.lstatSync(src).isDirectory()) { | ||
return true; | ||
} | ||
|
||
// But ignore files in the theme/css/ directory | ||
const relativePath = path.relative(themeSrcDir, src); | ||
return !relativePath.startsWith('css'); | ||
}, | ||
}); | ||
|
||
// Compile SCSS to CSS | ||
console.log(`Loading the input scss file path with ${outputType}...\n`); | ||
const scssInput = path.join(manuscriptDir, 'theme', 'css', `styles.${outputType}.scss`); | ||
|
||
console.log(`Loading the output css file path with ${outputType}...\n`); | ||
const cssOutput = path.join(outputDir, 'theme', 'css', `styles.${outputType}.css`); | ||
|
||
try { | ||
const result = sass.renderSync({ file: scssInput, outputStyle: 'compressed' }); | ||
fs.writeFileSync(cssOutput, result.css); | ||
} catch (error) { | ||
console.error('\x1b[31m%s\x1b[0m', `Error compiling SCSS: ${error.message}`); | ||
} | ||
|
||
console.log("All Finished!\n") | ||
} catch (error) { | ||
console.error('\x1b[31m%s\x1b[0m', `Error Converting Source Files: ${error.message}`); | ||
} | ||
} |
Oops, something went wrong.