Initial commit.

This commit is contained in:
David Soulayrol 2021-10-27 22:44:50 +02:00
commit 01d5e2b210
11 changed files with 7248 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
dist
node_modules

1
README.md Symbolic link
View file

@ -0,0 +1 @@
content/index.md

9
content/index.md Normal file
View file

@ -0,0 +1,9 @@
# How To Use
1. Clone the project into a new one.
2. Update dependencies (`npm outdated`, `npm update` and `ncu` which
can be deployed with `npm install -g npm-check-updates`).
3. Fill in `package.json`.
4. Install the dependencies (`npm install`).
4. Configure `src/config.js`.
5. Develop content, layouts and add-ons...

32
layouts/default.njk Normal file
View file

@ -0,0 +1,32 @@
<!DOCTYPE HTML>
<html>
<head>
<title>{{ site.title }} | {{ page.title }}</title>
<meta charset="utf-8">
<meta name="author" content="David Soulayrol">
<meta name="generator" content="Metalsmith" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="/css/mini.css" />
{% block head %}{% endblock %}
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm-12">
{% block content %}
{{ contents | safe }}
{% endblock %}
</div>
</div>
<div class="row">
<div class="col-sm-12">
<footer id="page_footer">
{% block footer %}{% endblock %}
</footer>
</div>
</div>
</div>
</body>
</html>

6815
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

62
package.json Normal file
View file

@ -0,0 +1,62 @@
{
"name": "metalsmith-starter-kit",
"version": "2021.08.24",
"private": true,
"description": "A kit to deploy new projects using Metalsmith and some common dependencies",
"main": "",
"scripts": {
"build": "DEBUG=metalsmith* npm run clean && npm run build:metalsmith",
"build:prod": "NODE_ENV=production npm run build",
"build:metalsmith": "nodejs scripts/run.js build",
"clean": "rimraf dist",
"dev": "npm run build && DEBUG=metalsmith* nodemon scripts/run.js serve",
"server": "npm run build && http-server dist",
"deploy": "npm run build:prod && cd dist && rsync -v -rlptz --relative * $SERVER_PATH",
"lint": "npm run lint:js && npm run lint:css",
"lint:js": "eslint src test",
"lint:css": "stylelint src/assets/css/**/*.css",
"test": "npm run lint && npm run build"
},
"author": "David Soulayrol <david@soulayrol.name>",
"dependencies": {
"mini.css": "^3.0.1"
},
"devDependencies": {
"browser-sync": "^2.27.5",
"bs-fullscreen-message": "^1.1.0",
"clean-css": "^5.1.5",
"cli-table2": "^0.2.0",
"debug": "^4.3.2",
"eslint": "^7.32.0",
"eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-standard": "4.1.0",
"filesize": "^8.0.0",
"http-server": "^13.0.1",
"jstransformer-nunjucks": "^1.0.0",
"metalsmith": "^2.3.0",
"metalsmith-assets": "^0.1.0",
"metalsmith-clean-css": "^6.1.3",
"metalsmith-layouts": "2.3.1",
"metalsmith-markdownit": "^0.5.0",
"metalsmith-rename": "^1.0.0",
"metalsmith-sitemap": "^1.2.2",
"nodemon": "^2.0.12",
"rimraf": "^3.0.2",
"slug": "^5.1.0",
"stylelint": "^13.13.1",
"stylelint-config-standard": "^22.0.0"
},
"nodemonConfig": {
"delay": 2500,
"ignore": [
"test/*",
"docs/*"
],
"watch": [
"scripts"
]
}
}

20
scripts/config.js Normal file
View file

@ -0,0 +1,20 @@
const { resolve, join } = require('path')
const hostname = 'http://example.com'
const projectRoot = resolve(__dirname, '..')
const distribution = join(projectRoot, 'dist')
module.exports = {
hostname,
paths: {
projectRoot,
/* Nodes */
nodeModules: join(projectRoot, 'node_modules'),
/* Metalsmith */
metalsmithSource: 'content',
metalsmithDestination: distribution,
/* Server */
serverRoot: distribution
}
}

View file

@ -0,0 +1,108 @@
const path = require('path')
const Table = require('cli-table2')
const filesize = require('filesize')
function generateFileMap (files) {
return Object.keys(files).reduce((map, filename) => {
const file = files[filename]
const parsedFilename = path.parse(filename)
const ext = parsedFilename.ext.substr(1)
const extFiles = map[ext] || []
return {
...map,
[ext]: [
...extFiles,
{
file,
filename
}
]
}
}, {})
}
export function StatisticsPlugin (options) {
return (files, metalsmith, done) => {
const fileMap = generateFileMap(files)
const fileTypes = Object.keys(fileMap)
// File overview table
fileTypes.forEach((filetype) => {
const fileTypeFiles = fileMap[filetype]
const count = fileTypeFiles.length
const size = fileTypeFiles.reduce((totalsize, entry) => {
// Some plugins (eg. metalsmith-data-markdown) replace the Buffer by a string
if (typeof entry.file.contents === 'string') {
return totalsize + entry.file.contents.length
} else {
return totalsize + entry.file.contents.byteLength
}
}, 0)
const filenamesTable = new Table({
head: [`${count} ${filetype}-${count > 1 ? 'files' : 'file'} with total ${filesize(size)}`, 'File size'],
wordWrap: true,
colWidths: [process.stdout.columns - 16, 12]
})
fileTypeFiles.forEach((entry) => {
let size = 0
// Some plugins (eg. metalsmith-data-markdown) replace the Buffer by a string
if (typeof entry.file.contents === 'string') {
size = entry.file.contents.length
} else {
size = entry.file.contents.byteLength
}
filenamesTable.push([entry.filename, size])
})
console.log(filenamesTable.toString())
})
done()
}
}
export function DebugPlugin (options) {
function sanitizeTableContent (content) {
const length = content.length
content = content.replace(/\s+/g, ' ').slice(0, config.maxContentLength)
if (length > config.maxContentLength) {
content = content.trim() + '...'
}
return content
}
const defaultOptions = {
maxContentLength: 1000
}
const config = {
...defaultOptions,
...options
}
return (files, metalsmith, done) => {
const fileMap = generateFileMap(files)
const fileTypes = Object.keys(fileMap)
fileTypes.forEach((filetype) => {
const fileTypeFiles = fileMap[filetype]
fileTypeFiles.forEach((entry) => {
const content = sanitizeTableContent(entry.file.contents.toString())
const size = filesize(entry.file.contents.byteLength)
const metadata = {
...entry.file
}
delete metadata.contents
const fileTable = new Table({
head: [`${entry.filename} @ ${size}`],
wordWrap: true,
colWidths: [process.stdout.columns - 2]
})
fileTable.push([JSON.stringify(metadata, null, 2)])
fileTable.push([content])
console.log(fileTable.toString())
})
})
done()
}
}

View file

@ -0,0 +1,63 @@
const path = require('path')
const Table = require('cli-table2')
const filesize = require('filesize')
module.exports = plugin
function generateFileMap (files) {
return Object.keys(files).reduce((map, filename) => {
const file = files[filename]
const parsedFilename = path.parse(filename)
const ext = parsedFilename.ext.substr(1)
const extFiles = map[ext] || []
return {
...map,
[ext]: [
...extFiles,
{
file,
filename
}
]
}
}, {})
}
function plugin () {
return (files, metalsmith, done) => {
const fileMap = generateFileMap(files)
const fileTypes = Object.keys(fileMap)
// File overview table
fileTypes.forEach((filetype) => {
const fileTypeFiles = fileMap[filetype]
const count = fileTypeFiles.length
const size = fileTypeFiles.reduce((totalsize, entry) => {
// Some plugins (eg. metalsmith-data-markdown) replace the Buffer by a string
if (typeof entry.file.contents === 'string') {
return totalsize + entry.file.contents.length
} else {
return totalsize + entry.file.contents.byteLength
}
}, 0)
const filenamesTable = new Table({
head: [`${count} ${filetype}-${count > 1 ? 'files' : 'file'} with total ${filesize(size)}`, 'File size'],
wordWrap: true,
colWidths: [process.stdout.columns - 16, 12]
})
fileTypeFiles.forEach((entry) => {
var size = 0
// Some plugins (eg. metalsmith-data-markdown) replace the Buffer by a string
if (typeof entry.file.contents === 'string') {
size = entry.file.contents.length
} else {
size = entry.file.contents.byteLength
}
filenamesTable.push([entry.filename, size])
})
console.log(filenamesTable.toString())
})
done()
}
}

65
scripts/metalsmith.js Normal file
View file

@ -0,0 +1,65 @@
/* This is the actual metalsmith configuration script. */
const assets = require('metalsmith-assets')
const cleanCSS = require('metalsmith-clean-css')
const config = require('./config.js')
const layouts = require('metalsmith-layouts')
const Metalsmith = require('metalsmith')
const markdown = require('metalsmith-markdownit')
const rename = require('metalsmith-rename')
const path = require('path')
const sitemap = require('metalsmith-sitemap')
const slug = require('slug')
const statistics = require('./metalsmith-statistics-plugin')
const __PROD__ = process.env.NODE_ENV === 'production'
module.exports = new Metalsmith(config.paths.projectRoot)
.clean(__PROD__)
.metadata({
remove_diatrics: function (e) { return slug(e, {mode: 'rfc3986' }) }
})
.source(config.paths.metalsmithSource)
.destination(config.paths.metalsmithDestination)
.use(cleanCSS({
files: 'content/css/*.css',
cleanCSS: {
rebase: true
}
}))
.use(assets({
source: './assets/' + (process.env.NODE_ENV || 'dev'),
destination: './'
}))
.use(markdown({
html: true,
typographer: true,
quotes: ['«\xA0', '\xA0»', '\xA0', '\xA0'],
plugin: {
pattern: '**/*.md',
// fields: ['contents', 'excerpt']
extension: 'njk'
}
}))
.use(layouts({
default: 'default.njk',
pattern: '**/*.njk',
engineOptions: {
filters: {
setAttribute: (dictionary, key, value) => {
dictionary[key] = value
return dictionary
}
},
globals: {
production: __PROD__
}
}
}))
.use(rename([
[/\.njk$/, '.html']
]))
.use(sitemap({
changefreq: 'yearly',
hostname: config.hostname
}))
.use(statistics())

71
scripts/run.js Normal file
View file

@ -0,0 +1,71 @@
const bs = require('browser-sync').create('Metalsmith')
const config = require('./config.js')
const debug = require('debug')('Run')
const metalsmith = require('./metalsmith')
const path = require('path')
const strip = require('strip-ansi')
function build (sync) {
debug('Building Metalsmith')
metalsmith.build((err) => {
if (err) {
debug('Metalsmith build error:')
debug(err)
if (sync) {
return bs.sockets.emit('fullscreen:message', {
title: 'Metalsmith Error:',
body: strip(`${err.message}\n\n${err.stack}`),
timeout: 100000
})
} else {
throw err
}
}
debug('Metalsmith build finished!')
if (sync) {
bs.reload()
}
})
}
function serve () {
bs.init({
server: config.paths.serverRoot,
port: 8080,
ui: {
port: 9000
},
open: false,
logLevel: 'debug',
logPrefix: 'BrowserSync',
logConnections: true,
logFileChanges: true,
notify: true,
files: [{
match: [
path.resolve(config.paths.projectRoot, 'content', '**', '*'),
path.resolve(config.paths.projectRoot, 'layouts', '**', '*.njk')
],
fn: function (event, file) {
build(true)
},
options: {
ignored: ['**/.#*', '**/*~', '**/#*#']
// /\.#|node_modules|~$/
}
}]
})
}
const args = process.argv.slice(2)
switch (args[0]) {
case 'build':
build()
break
case 'serve':
serve()
break
default:
console.log('Unknown arguments "' + args[0] + '"')
}