diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..1df25f7
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,30 @@
+name: CI
+
+on:
+ push:
+ branches: [ main, master ]
+ pull_request:
+ branches: [ main, master ]
+
+jobs:
+ test:
+ runs-on: ${{ matrix.os }}
+
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ node-version: [16.x, 18.x, 20.x, 22.x, 24.x]
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v6
+ with:
+ node-version: ${{ matrix.node-version }}
+
+ - name: Install dependencies
+ run: npm install
+
+ - name: Run tests
+ run: npm test
diff --git a/bin/darko-serve b/bin/darko-serve
index e6d930c..0ddcc48 100755
--- a/bin/darko-serve
+++ b/bin/darko-serve
@@ -3,6 +3,7 @@
'use strict'
const program = require('commander')
+const mime = require('mime-types')
program
.option('-s --source [source]', 'Source directory (default to ./)', './')
@@ -77,12 +78,7 @@ function handle(req, res) {
function sendFile(fpath) {
debug('Sending ' + fpath)
- const mime = {
- '.css': 'text/css',
- '.html': 'text/html',
- '.js': 'application/javascript'
- }
- const contentType = mime[path.extname(fpath).toLowerCase()]
+ const contentType = mime.contentType(path.extname(fpath).toLowerCase())
if (contentType) {
res.setHeader('Content-Type', contentType)
@@ -115,6 +111,11 @@ function handle(req, res) {
return
}
+ if (!(fpath.startsWith(site.dest) || fpath.startsWith(droot))) {
+ send404()
+ return
+ }
+
const stats = fs.statSync(fpath)
if (stats.isFile()) {
diff --git a/lib/parsers/page.js b/lib/parsers/page.js
index 4fedda6..e8b66a0 100644
--- a/lib/parsers/page.js
+++ b/lib/parsers/page.js
@@ -16,7 +16,7 @@ function Page(attrs) {
this.ext = path.extname(fpath)
this.slug = path.basename(fpath, this.ext)
- this.path = path.relative(this.site.cwd, fpath)
+ this.path = path.relative(this.site.cwd, fpath).replaceAll(path.sep, '/')
this.title = util.capitalize(this.slug)
if (this.validFormat && fs.existsSync(fpath)) {
@@ -33,7 +33,7 @@ function Page(attrs) {
}
}
- this.url = path.resolve('/', this.path).replace(/\/index\.(?:md|html)$/, '')
+ this.url = `/${this.path}`.replace(/\/index\.(?:md|html)$/, '')
this.dest = path.join(this.site.dest, this.site.baseurl.slice(1),
this.path.replace(/\.\w+$/, this.ext == '.md' ? '.html' : this.ext))
diff --git a/lib/writers/templated.js b/lib/writers/templated.js
index b03d3e3..82ebc72 100644
--- a/lib/writers/templated.js
+++ b/lib/writers/templated.js
@@ -4,7 +4,6 @@ const path = require('path')
const yaml = require('yaml-js')
const mkdirp = require('mkdirp')
const debug = require('debug')('darko')
-const util = require('util')
const fs = require('fs')
const md = require('../markdown')
diff --git a/package.json b/package.json
index 4c335cd..92d0de1 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"highlight.js": "^11.11.1",
"liquid-node": "^3.0.0",
"markit": "~0.1.0",
+ "mime-types": "^3.0.1",
"mkdirp": "~0.3.5",
"rimraf": "^2.6.2",
"strftime": "~0.9.0",
diff --git a/test/fixture/assets/darko.svg b/test/fixture/assets/darko.svg
new file mode 100644
index 0000000..0f010a6
--- /dev/null
+++ b/test/fixture/assets/darko.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/test/fixture/index.md b/test/fixture/index.md
index 803965c..fe7db78 100644
--- a/test/fixture/index.md
+++ b/test/fixture/index.md
@@ -3,6 +3,8 @@ layout: post
title: Welcome!
---
+
+
Good luck, have fun!
diff --git a/test/liquid.js b/test/liquid.js
deleted file mode 100644
index a6935ae..0000000
--- a/test/liquid.js
+++ /dev/null
@@ -1,103 +0,0 @@
-'use strict'
-
-var expect = require('expect.js')
-var engine = require('../').Liquid
-
-
-function liquid(tpl, data) {
- return engine.parseAndRender(tpl, data)
-}
-
-describe('Liquid', function() {
-
- it('has filter date_to_xmlschema', function(done) {
- liquid('{{ date | date_to_xmlschema }}', {
- date: new Date(2014, 0, 12)
- }).then(function(result) {
- expect(result).to.equal('2014-01-12T00:00:00+08:00')
- done()
- })
- })
-
- it('has filter date_to_rfc822', function(done) {
- liquid('{{ date | date_to_rfc822 }}', {
- date: new Date(2014, 0, 12)
- }).then(function(result) {
- expect(result).to.equal('Sun, 12 Jan 2014 00:00:00 +0800')
- done()
- })
- })
-
- it('has filter date_to_string', function(done) {
- liquid('{{ date | date_to_string }}', {
- date: new Date(2014, 0, 12)
- }).then(function(result) {
- expect(result).to.equal('12 Jan 2014')
- done()
- })
- })
-
- it('has filter date_to_long_string', function(done) {
- liquid('{{ date | date_to_long_string }}', {
- date: new Date(2014, 0, 12)
- }).then(function(result) {
- expect(result).to.equal('12 January 2014')
- done()
- })
- })
-
- it('has filter array_to_sentence_string', function(done) {
- liquid('{{ tags | array_to_sentence_string }}', {
- tags: [ 'life', 'rails', 'conf' ]
- }).then(function(result) {
- expect(result).to.equal('life, rails, and conf')
- done()
- })
- })
-
- it('has filter markdownify', function(done) {
- liquid('{{ excerpt | markdownify }}', {
- excerpt: '## Excerpt'
- }).then(function(result) {
- expect(result).to.contain('Excerpt
')
- done()
- })
- })
-
- it('has filter jsonify', function(done) {
- liquid('{{ data | jsonify }}', {
- data: { foo: 'bar' }
- }).then(function(result) {
- expect(result).to.equal('{"foo":"bar"}')
- done()
- })
- })
-
- it('has filter xml_escape', function(done) {
- liquid('{{ data | xml_escape }}', {
- data: 'How to go home? Taxi -> Train -> Taxi'
- }).then(function(result) {
- expect(result).to.equal('How to go home? Taxi -> Train -> Taxi')
- done()
- })
- })
-
- it('has filter cgi_escape', function(done) {
- liquid('{{ data | cgi_escape }}', {
- data: 'http://google.com/foo?bar=at#anchor&title=My Blog & Your Blog'
- }).then(function(result) {
- expect(result).to.equal('http%3A%2F%2Fgoogle.com%2Ffoo%3Fbar%3Dat%23anchor%26title%3DMy+Blog+%26+Your+Blog')
- done()
- })
- })
-
- it('has filter uri_escape', function(done) {
- liquid('{{ data | uri_escape }}', {
- data: 'http://google.com/foo?bar=at#anchor&title=My Blog & Your Blog'
- }).then(function(result) {
- expect(result).to.equal('http://google.com/foo?bar=at%23anchor&title=My%20Blog%20&%20Your%20Blog')
- done()
- })
- })
-
-})
diff --git a/test/liquid.test.js b/test/liquid.test.js
new file mode 100644
index 0000000..c9097bd
--- /dev/null
+++ b/test/liquid.test.js
@@ -0,0 +1,89 @@
+'use strict'
+
+var expect = require('expect.js')
+var engine = require('..').Liquid
+
+
+function liquid(tpl, data) {
+ return engine.parseAndRender(tpl, data)
+}
+
+describe('Liquid', function() {
+ const timezoneOffset = new Date().getTimezoneOffset();
+ const timezone = [
+ timezoneOffset > 0 ? '-' : '+',
+ String(Math.abs(Math.floor(timezoneOffset / 60))).padStart(2, '0'),
+ ':',
+ String(Math.abs(timezoneOffset % 60)).padStart(2, '0')
+ ].join('');
+
+ it('has filter date_to_xmlschema', async function() {
+ const result = await liquid('{{ date | date_to_xmlschema }}', {
+ date: new Date(2014, 0, 12)
+ });
+ expect(result).to.equal(`2014-01-12T00:00:00${timezone}`);
+ });
+
+ it('has filter date_to_rfc822', async function() {
+ const result = await liquid('{{ date | date_to_rfc822 }}', {
+ date: new Date(2014, 0, 12)
+ });
+ expect(result).to.equal(`Sun, 12 Jan 2014 00:00:00 ${timezone.replace(':', '')}`);
+ });
+
+ it('has filter date_to_string', async function() {
+ const result = await liquid('{{ date | date_to_string }}', {
+ date: new Date(2014, 0, 12)
+ });
+ expect(result).to.equal('12 Jan 2014');
+ });
+
+ it('has filter date_to_long_string', async function() {
+ const result = await liquid('{{ date | date_to_long_string }}', {
+ date: new Date(2014, 0, 12)
+ });
+ expect(result).to.equal('12 January 2014')
+ })
+
+ it('has filter array_to_sentence_string', async function() {
+ const result = await liquid('{{ tags | array_to_sentence_string }}', {
+ tags: [ 'life', 'rails', 'conf' ]
+ })
+ expect(result).to.equal('life, rails, and conf');
+ })
+
+ it('has filter markdownify', async function() {
+ const result = await liquid('{{ excerpt | markdownify }}', {
+ excerpt: '## Excerpt'
+ });
+ expect(result).to.contain('Excerpt
');
+ });
+
+ it('has filter jsonify', async function() {
+ const result = await liquid('{{ data | jsonify }}', {
+ data: { foo: 'bar' }
+ });
+ expect(result).to.equal('{"foo":"bar"}');
+ });
+
+ it('has filter xml_escape', async function() {
+ const result = await liquid('{{ data | xml_escape }}', {
+ data: 'How to go home? Taxi -> Train -> Taxi'
+ });
+ expect(result).to.equal('How to go home? Taxi -> Train -> Taxi');
+ });
+
+ it('has filter cgi_escape', async function() {
+ const result = await liquid('{{ data | cgi_escape }}', {
+ data: 'http://google.com/foo?bar=at#anchor&title=My Blog & Your Blog'
+ });
+ expect(result).to.equal('http%3A%2F%2Fgoogle.com%2Ffoo%3Fbar%3Dat%23anchor%26title%3DMy+Blog+%26+Your+Blog');
+ });
+
+ it('has filter uri_escape', async function() {
+ const result = await liquid('{{ data | uri_escape }}', {
+ data: 'http://google.com/foo?bar=at#anchor&title=My Blog & Your Blog'
+ });
+ expect(result).to.equal('http://google.com/foo?bar=at%23anchor&title=My%20Blog%20&%20Your%20Blog');
+ });
+});
diff --git a/test/site.js b/test/site.test.js
similarity index 93%
rename from test/site.js
rename to test/site.test.js
index fcc1f1d..492ed1d 100644
--- a/test/site.js
+++ b/test/site.test.js
@@ -17,8 +17,8 @@ var site = new Site({
describe('basic attributes of Site', function() {
it('should set cwd and dest', function() {
- expect(site.cwd).to.contain('test/fixture')
- expect(site.dest).to.contain('test/fixture/_site')
+ expect(site.cwd.replaceAll(path.sep, '/')).to.contain('test/fixture')
+ expect(site.dest.replaceAll(path.sep, '/')).to.contain('test/fixture/_site')
})
it('should parse _config.yml', function() {