Comparing version 0.3.1 to 1.0.0
25
cli.js
@@ -7,2 +7,8 @@ #!/usr/bin/env node | ||
.demand(1, 1, 'Error: You must specify an template engine') | ||
.alias({ | ||
s: 'src', | ||
d: 'dist', | ||
l: 'layouts', | ||
}) | ||
.string(['s', 'd', 'l']) | ||
.default({ | ||
@@ -13,8 +19,2 @@ s: 'src/', | ||
}) | ||
.string(['s', 'd', 'l']) | ||
.alias({ | ||
s: 'src', | ||
d: 'dist', | ||
l: 'layouts', | ||
}) | ||
.describe({ | ||
@@ -32,5 +32,12 @@ s: 'Set the src directory', | ||
var onessg = require('./index.js'); | ||
onessg(argv._[0], argv.s, argv.d, argv.l, function (err) { | ||
console.error(err); | ||
process.exit(1); | ||
var dirs={ | ||
src: argv.s, | ||
dist: argv.d, | ||
layouts: argv.l, | ||
}; | ||
onessg(argv._[0], dirs, function (err) { | ||
if (err) { | ||
console.error(err); | ||
process.exit(1); | ||
} | ||
}); |
203
index.js
var fs = require('fs-extra'); | ||
var path = require('path'); | ||
var replaceExt=require('replace-ext'); | ||
var fm = require('front-matter'); | ||
var path = require('path-extra'); | ||
var glob = require('glob'); | ||
var matter = require('gray-matter'); | ||
var cons = require('consolidate'); | ||
var glob = require('glob'); | ||
var yaml = require('js-yaml'); | ||
var marked = require('marked'); | ||
var async = require('async'); | ||
var _ = require('lodash'); | ||
module.exports = function (engine, src, dist, layouts, cb) { | ||
// SANITY CHECKS | ||
// Check that src exists: | ||
fs.access(src, function (err) { | ||
if (err) return cb(err); | ||
// Directory vars: | ||
var src; | ||
var layouts; | ||
var dist; | ||
module.exports = function (engine, dirs, cb) { | ||
// Make dirs available globally | ||
setDirs(dirs, function (err) { | ||
if (err) cb(err); | ||
}); | ||
@@ -20,82 +23,74 @@ // Check that engine is a string: | ||
if (typeof cons[engine] !== 'function') return cb(new Error(engine+' is not a valid consolidate.js template engine')); | ||
// MAIN CODE | ||
// For each html file in src: | ||
forGlob(path.join(src, '**/*.html'), function (filePath) { | ||
// For each file in src: | ||
forGlob('**/*.@(html|md|markdown)', function (filePath, cb) { | ||
// Load and parse FM: | ||
loadFile(filePath, function (err, data) { | ||
loadFile(filePath, function (err, file) { | ||
if (err) return cb(err); | ||
// Render it: | ||
render(data, filePath, function (err, html) { | ||
// Run through middleware: | ||
middleware(file.content, path.extname(filePath), function (err, body) { | ||
if (err) return cb(err); | ||
// Get path to write to: | ||
var writePath=path.join(dist, filePath.replace(src, '')); | ||
// Output using fs-extra: | ||
fs.outputFile(writePath, html, function (err) { | ||
if (err) return cb(err); | ||
}); | ||
}); | ||
}); | ||
}, function () { | ||
// Empty for now, since forGlob() is sync | ||
}); | ||
// For each markdown file in src: | ||
forGlob(path.join(src, '**/*.@(md|markdown)'), function (filePath) { | ||
// Load and parse FM: | ||
loadFile(filePath, function (err, data) { | ||
if (err) return cb(err); | ||
marked(data.body, function (err, body) { | ||
if (err) return cb(err); | ||
// Overwrite markdown with html: | ||
data.body=body; | ||
// Overwrite body: | ||
file.content=body; | ||
// Render it: | ||
render(data, filePath, function (err, html) { | ||
render(file, engine, filePath, function (err, html) { | ||
if (err) return cb(err); | ||
// Get path to write to: | ||
var writePath=replaceExt(path.join(dist, filePath.replace(src, '')), '.html'); | ||
// Output using fs-extra: | ||
fs.outputFile(writePath, html, function (err) { | ||
if (err) return cb(err); | ||
}); | ||
// Write to file and pass cb | ||
writeFile(html, filePath, cb); | ||
}); | ||
}); | ||
}); | ||
}, function () { | ||
// Empty for now, since forGlob() is sync | ||
}, function (err) { | ||
cb(err); | ||
}); | ||
// IN-SCOPE HELPER FUNCTIONS | ||
// Declare render() inside the main function for access to var engine | ||
function render(data, filePath, cb) { | ||
// Get defaults: | ||
getDefaults(filePath, function (err, defaults) { | ||
if (err) return cb(err); | ||
// Set Defaults: | ||
_.defaultsDeep(data.attributes, defaults); | ||
// If layout, render: | ||
if (data.attributes._layout) { | ||
// Get layouts/layoutName.* : | ||
var layout=glob.sync(path.join(layouts, data.attributes._layout)+'.*')[0]; | ||
// Glob doesn't throw an error if the layout path doesn't exist, so we do: | ||
if (!layout) cb(new Error('The file: '+path.join(layouts, data.attributes._layout)+'.'+engine+' does not exist')); | ||
var locals=data.attributes; | ||
locals._body=data.body; | ||
// Render with consolidate.js: | ||
cons[engine](layout, locals, cb); | ||
} else cb(null, data.body); // Else, return body | ||
}); | ||
}; | ||
// HELPER FUNCTIONS | ||
function render(file, engine, filePath, cb) { | ||
// Get defaults: | ||
getDefaults(path.join(src, filePath), function (err, defaults) { | ||
if (err) return cb(err); | ||
// Set Defaults: | ||
_.defaultsDeep(file.data, defaults); | ||
// If layout, render: | ||
if (file.data._layout) { | ||
// Get layouts/layoutName.* : | ||
var layout=glob.sync(path.join(layouts, file.data._layout)+'.*')[0]; | ||
// Glob doesn't throw an error if the layout path doesn't exist, so we do: | ||
if (!layout) cb(new Error('The layout: '+file.data._layout+' cannot be found in '+layouts)); | ||
var locals=file.data; | ||
locals._body=file.content; | ||
// Render with consolidate.js: | ||
cons[engine](layout, locals, cb); | ||
} else cb(null, file.content); // Else, return body | ||
}); | ||
} | ||
function getDefaults(filePath, cb) { | ||
var dirPath=path.dirname(filePath); | ||
var dirArr=[dirPath]; | ||
var defaultArr=[]; | ||
recurse(dirPath); | ||
async.eachOf(dirArr, load, function (err) { | ||
if (err) cb(err); | ||
cb(null, _.spread(_.defaultsDeep)(defaultArr)); | ||
}); | ||
function recurse(dirPath) { | ||
if (path.normalizeTrim(dirPath) === path.normalizeTrim(src)) return; | ||
else { | ||
var newPath=path.dirname(dirPath); | ||
dirArr.push(newPath); | ||
return recurse(newPath); | ||
} | ||
} | ||
// Declare getDefaults inside the main function for access to var src | ||
function getDefaults(filePath, cb, defaults) { | ||
glob(path.join(path.dirname(filePath), '_defaults.*'), function (err, res) { | ||
function load(dirPath, i, cb) { | ||
glob(path.join(dirPath, '_defaults.*'), function (err, res) { | ||
if (err) return cb(err); | ||
if (!defaults) defaults={}; | ||
if (!res[0]) return recurse(); | ||
var ext=path.extname(res[0]); | ||
var defaults={}; | ||
if (!res[0]) return finish(); | ||
try { | ||
switch (ext) { | ||
switch (path.extname(res[0])) { | ||
case '.yaml': | ||
case '.yml': | ||
_.defaultsDeep(defaults, yaml.safeLoad(fs.readFileSync(res[0], 'utf8'))); | ||
defaults=yaml.safeLoad(fs.readFileSync(res[0], 'utf8')); | ||
break; | ||
case '.json': | ||
_.defaultsDeep(defaults, fs.readJsonSync(res[0])); | ||
defaults=fs.readJsonSync(res[0]); | ||
} | ||
@@ -105,17 +100,34 @@ } catch (e) { | ||
} | ||
recurse(); | ||
return finish(); | ||
function finish() { | ||
defaultArr[i]=defaults; | ||
cb(null); | ||
return; | ||
} | ||
}); | ||
function recurse() { | ||
if (path.dirname(filePath)+path.sep === path.normalize(src+path.sep)) return cb(null, defaults); | ||
else { | ||
var newPath=path.dirname(filePath); | ||
return getDefaults(newPath, cb, defaults); | ||
} | ||
} | ||
} | ||
}; | ||
// HELPER FUNCTIONS | ||
} | ||
function middleware(text, ext, cb) { | ||
// Check path's ext: | ||
switch (ext) { | ||
case '.html': | ||
// noop: | ||
return cb(null, text); | ||
case '.md': | ||
case '.markdown': | ||
// Render markdown: | ||
return marked(text, cb); | ||
} | ||
} | ||
function writeFile(html, filePath, cb) { | ||
// Get path to write to using path-extra: | ||
var writePath=path.replaceExt(path.join(dist, filePath), '.html'); | ||
// Output using fs-extra: | ||
fs.outputFile(writePath, html, function (err) { | ||
cb(err); | ||
}); | ||
} | ||
// loadFile() calls (err, front-matter object) | ||
function loadFile(name, cb) { | ||
fs.readFile(name, 'utf8', function (err, res) { | ||
fs.readFile(path.join(src, name), 'utf8', function (err, res) { | ||
if (err) return cb(err); | ||
@@ -125,3 +137,3 @@ var json; | ||
try { | ||
json=fm(res); | ||
json=matter(res); | ||
} catch (e) { | ||
@@ -135,7 +147,22 @@ return cb(e); | ||
function forGlob(pattern, iter, cb) { | ||
glob(pattern, {nodir: true}, function (err, res) { | ||
glob(pattern, {nodir: true, cwd: src}, function (err, res) { | ||
if (err) return cb(err); | ||
res.forEach(iter); | ||
cb(null); | ||
async.each(res, iter, cb); | ||
}); | ||
return; | ||
} | ||
function setDirs(dirs, cb) { | ||
// Check that src exists: | ||
fs.access(dirs.src, function (err) { | ||
if (err) return cb(err); | ||
}); | ||
// Check that layouts exists: | ||
fs.access(dirs.layouts, function (err) { | ||
if (err) return cb(err); | ||
}); | ||
src=dirs.src; | ||
dist=dirs.dist; | ||
layouts=dirs.layouts; | ||
cb(); | ||
} |
{ | ||
"name": "onessg", | ||
"version": "0.3.1", | ||
"version": "1.0.0", | ||
"description": "The Static Site Generator that does only one thing: compile your html and markdown.", | ||
@@ -13,2 +13,3 @@ "main": "index.js", | ||
"benchmark": "./cli.js ejs -s test/src -d test/dist -l test/layouts", | ||
"toc": "doctoc README.md", | ||
"mocha": "nyc mocha --ui tdd", | ||
@@ -31,18 +32,22 @@ "lint": "eslint --ignore-path .gitignore '**/*.js'" | ||
"dependencies": { | ||
"async": "^2.0.1", | ||
"consolidate": "^0.14.1", | ||
"front-matter": "^2.1.0", | ||
"fs-extra": "^0.30.0", | ||
"glob": "^7.0.5", | ||
"gray-matter": "^2.0.2", | ||
"js-yaml": "^3.6.1", | ||
"lodash": "^4.14.0", | ||
"marked": "^0.3.6", | ||
"replace-ext": "^1.0.0", | ||
"path-extra": "^4.0.0", | ||
"yargs": "^5.0.0" | ||
}, | ||
"devDependencies": { | ||
"autoresolve": "0.0.3", | ||
"doctoc": "^1.2.0", | ||
"ejs": "^2.5.1", | ||
"eslint": "~3.4.0", | ||
"eslint": "~3.5.0", | ||
"mocha": "^3.0.2", | ||
"nyc": "^7.1.0" | ||
"nyc": "^8.1.0", | ||
"suppose": "^0.6.1" | ||
} | ||
} |
@@ -17,2 +17,15 @@ # onessg | ||
## Contents | ||
<!-- START doctoc generated TOC please keep comment here to allow auto update --> | ||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> | ||
- [Installation](#installation) | ||
- [Tutorial](#tutorial) | ||
- [CLI Usage & Options](#cli-usage-&-options) | ||
- [Contributing](#contributing) | ||
- [License](#license) | ||
<!-- END doctoc generated TOC please keep comment here to allow auto update --> | ||
## Installation | ||
@@ -26,4 +39,6 @@ | ||
## Example | ||
**Note:** We recommend installing onessg as a devDependency (with the `-D` flag) and running it via an npm script. If you choose to install onessg globally, you will also need to install your template engine globally as well. | ||
## Tutorial | ||
Examples will use [ejs](https://github.com/mde/ejs/) as the template engine, you can use any template engine supported by [consolidate.js](https://github.com/tj/consolidate.js/). | ||
@@ -44,4 +59,6 @@ | ||
All files can include front-matter (yaml or json). | ||
--- | ||
All files in `src/` can include front-matter. YAML is parsed by default, see the [gray-matter docs](https://github.com/jonschlinkert/gray-matter#optionslang) for a list of supported languages. | ||
**src/page-one.md**: | ||
@@ -55,2 +72,3 @@ ```html | ||
``` | ||
Notice the underscore before `layout`. _Anything prefixed with an underscore is reserved word for onessg._ All keys in the front-matter will be passed as a local to your templates. | ||
@@ -67,2 +85,4 @@ | ||
Layouts are written in the templating language of your choice. We are using EJS here, but you can use any template engine on [this list](https://github.com/tj/consolidate.js/#supported-template-engines). | ||
**layouts/page.ejs** looks like this: | ||
@@ -82,11 +102,15 @@ ```html | ||
``` | ||
Notice the local `_body`. This is the contents of the file. For **page-one.html**, it is `<!-- Your HTML -->`. | ||
Notice the local `_body`. This is the local for outputing the contents of each file. For **page-one.md**, it is `Hello World!`. | ||
**Run:** | ||
```bash | ||
onessg ejs | ||
``` | ||
(Substitute ejs with the name of your template engine) | ||
onessg will compile all html files in `src/` (and subdirectories), and output them to `dist/` (retaining the directory structure): | ||
(Substitute `ejs` with the name of your template engine) | ||
onessg will compile all the html and markdown files in `src/` (and subdirectories), and output them to `dist/` (retaining the directory structure): | ||
``` | ||
@@ -103,3 +127,5 @@ . | ||
``` | ||
**dist/page-one.html** looks like this (leading whitespace is removed by ejs due to the `rmWhitespace` option that we set in `_defaults.yaml`): | ||
**dist/page-one.html** looks like this: | ||
```html | ||
@@ -118,5 +144,11 @@ <!DOCTYPE html> | ||
``` | ||
- The title (`My first Page`) comes from the front-matter. | ||
- The author's name (`John Smith`) comes from the `_defaults.yaml` file. | ||
- Leading whitespace is removed by ejs due to the `rmWhitespace` option that we set in `_defaults.yaml`. | ||
**Success!!!** :tada: | ||
Now we are going to add a subdirectory to `src/`. Inside the subdirectory, we will add a `_defaults.yaml` and an html page. Now our tree looks like this: | ||
Now we are going to add a subdirectory to `src/`, named `subdirectory` (of course! :wink:). Inside the subdirectory, we will add a `_defaults.yaml` and an html page, named `subpage.html`. Now our directory tree looks like this: | ||
``` | ||
@@ -148,3 +180,3 @@ . | ||
- `_layout: page` Here we are setting a default layout. This means we will not have to set `_layout` in each pages' front-matter. | ||
- `author: Jane Smith` Here we are overriding a default set in `src/_defaults.yaml`. | ||
- `author: Jane Smith` Here we are overriding the default author set in `src/_defaults.yaml`. | ||
@@ -155,2 +187,3 @@ **src/subdirectory/subpage.html**: | ||
``` | ||
Note that we have omitted the front-matter. The defaults from the `_defaults` file in this directory and parent directories (up to `src/`) will apply. | ||
@@ -181,3 +214,3 @@ | ||
**dist/subdirectory/subpage.html**: | ||
**dist/subdirectory/subpage.html** looks like this: | ||
```html | ||
@@ -196,4 +229,9 @@ <!DOCTYPE html> | ||
``` | ||
Note that the default title from **src/_defaults.yaml** has been applied. The `rmWhitespace` option also is in effect. | ||
A few things to note: | ||
- The default title from `src/_defaults.yaml` has been applied. | ||
- The default author from `src/_defaults.yaml` has been overridden by the one in `src/subdirectory/_defaults.yaml`. | ||
- The `rmWhitespace` option from `src/_defaults.yaml` is also in effect. | ||
**Hooray!** You are now a certified onessg user! :mortar_board: | ||
@@ -222,6 +260,8 @@ | ||
## Development | ||
## Contributing | ||
Contributions welcome; please discuss before making significant changes. All new features should be tested. Run `npm test` to run the tests. You can generate a code coverage report by running `npm run coverage`. The report will be found at `coverage/lcov-report/index.html`. | ||
If you make changes to the headings in `README.md`, please run `npm run toc` to update the table of contents. | ||
For bugs :beetle:, feature requests :bulb:, and questions :speech_balloon:, please file an issue! | ||
@@ -228,0 +268,0 @@ |
@@ -1,10 +0,10 @@ | ||
var execSync=require('child_process').execSync; | ||
var exec=require('child_process').exec; | ||
var fs=require('fs-extra'); | ||
var path=require('path'); | ||
var path=require('path-extra'); | ||
var assert=require('assert'); | ||
var replaceExt=require('replace-ext'); | ||
var onessg=require('../index.js'); | ||
var suppose=require('suppose'); | ||
var resolve=require('autoresolve'); | ||
var onessg=require(resolve('index.js')); | ||
assert.file=function (fileName) { | ||
fileName=replaceExt(fileName, '.html'); | ||
// path-extra: | ||
fileName=path.replaceExt(fileName, '.html'); | ||
var expected=fs.readFileSync(path.join('test/expected', fileName), 'utf8'); | ||
@@ -17,4 +17,31 @@ var actual=fs.readFileSync(path.join('test/dist', fileName), 'utf8'); | ||
fs.removeSync('test/dist/'); | ||
// Build with cli: | ||
execSync('./../cli.js ejs', {cwd: 'test'}); | ||
suite('cli', function () { | ||
this.timeout(5000); | ||
this.slow(3000); | ||
test('works', function (done) { | ||
// NOTE: This also builds the files for the unit tests below! | ||
suppose(resolve('cli.js'), ['ejs', '-s', 'test/src', '-d', 'test/dist', '-l', 'test/layouts']) | ||
.on('error', function (err) { | ||
done(err); | ||
}) | ||
.end(function (code) { | ||
assert.equal(code, 0, 'CLI exited with non-zero exit code'); | ||
done(); | ||
}); | ||
}); | ||
test('returns errors', function (done) { | ||
var error=''; | ||
// Run cli.js ejs -s noop: | ||
suppose(resolve('cli.js'), ['ejs', '-s', 'noop']) | ||
.on('error', function (err) { | ||
error=err; | ||
}) | ||
.end(function (code) { | ||
assert.notEqual(code, 0, 'expected CLI to return non-zero exit code on error'); | ||
// Errors: | ||
assert(error, 'expected CLI to print error message'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
// Tests: | ||
@@ -28,5 +55,2 @@ suite('plain html', function () { | ||
}); | ||
test('empty front-matter', function () { | ||
assert.file('empty-fm.html'); | ||
}); | ||
}); | ||
@@ -82,2 +106,5 @@ suite('markdown', function () { | ||
}); | ||
test('works in nested subfolders', function () { | ||
assert.file('nested/folder/no-author-no-editor.html'); | ||
}); | ||
test('_defaults.json works', function () { | ||
@@ -87,10 +114,23 @@ assert.file('json/no-author.html'); | ||
}); | ||
suite('errors', function () { // NOTE: This suite should be run last! | ||
test('invalid src', function (done) { | ||
onessg('ejs', 'noop', 'test/dist', 'test/layouts', function (e) { | ||
suite('errors', function () { | ||
var dirs={}; | ||
setup(function () { | ||
dirs.src='test/src'; | ||
dirs.dist='test/dist'; | ||
dirs.layouts='test/layouts'; | ||
}); | ||
test('invalid src/', function (done) { | ||
dirs.src='noop'; | ||
onessg('ejs', dirs, function (e) { | ||
done(assert(e)); | ||
}); | ||
}); | ||
test('invalid layouts/', function (done) { | ||
dirs.layouts='noop'; | ||
onessg('ejs', dirs, function (e) { | ||
done(assert(e)); | ||
}); | ||
}); | ||
test('invalid type for engine', function (done) { | ||
onessg(0, 'test/src', 'test/dist', 'test/layouts', function (e) { | ||
onessg(0, dirs, function (e) { | ||
done(assert(e)); | ||
@@ -100,13 +140,6 @@ }); | ||
test('unsupported engine', function (done) { | ||
onessg('noop', 'test/src', 'test/dist', 'test/layouts', function (e) { | ||
onessg('noop', dirs, function (e) { | ||
done(assert(e)); | ||
}); | ||
}); | ||
test('cli returns errors', function (done) { | ||
this.timeout(5000); | ||
this.retries(4); | ||
exec('./../cli.js ejs -s noop', {cwd: 'test'}, function (e) { | ||
return done(assert(e)); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
42044
57
376
0
261
4
0
10
7
+ Addedasync@^2.0.1
+ Addedgray-matter@^2.0.2
+ Addedpath-extra@^4.0.0
+ Addedansi-red@0.1.1(transitive)
+ Addedansi-wrap@0.1.0(transitive)
+ Addedasync@2.6.4(transitive)
+ Addedcoffee-script@1.12.7(transitive)
+ Addedescape-string-regexp@1.0.5(transitive)
+ Addedextend-shallow@2.0.1(transitive)
+ Addedgray-matter@2.1.1(transitive)
+ Addedis-extendable@0.1.1(transitive)
+ Addedpath-extra@4.3.0(transitive)
+ Addedtoml@2.3.6(transitive)
- Removedfront-matter@^2.1.0
- Removedreplace-ext@^1.0.0
- Removedfront-matter@2.3.0(transitive)