diff --git a/src/fs/loader.ts b/src/fs/loader.ts index 81bcc589a..70eb45f4c 100644 --- a/src/fs/loader.ts +++ b/src/fs/loader.ts @@ -23,7 +23,7 @@ export class Loader { public * lookup (file: string, type: LookupType, sync?: boolean, currentFile?: string) { const { fs } = this.options const dirs = this.options[type] - for (const filepath of this.candidates(file, dirs, currentFile)) { + for (const filepath of this.candidates(file, dirs, currentFile, type !== LookupType.Root)) { if (sync ? fs.existsSync(filepath) : yield fs.exists(filepath)) return filepath } throw this.lookupError(file, dirs) @@ -37,12 +37,12 @@ export class Loader { return path.startsWith('./') || path.startsWith('../') } - public * candidates (file: string, dirs: string[], currentFile?: string) { + public * candidates (file: string, dirs: string[], currentFile?: string, enforceRoot?: boolean) { const { fs, extname } = this.options if (this.shouldLoadRelative(file) && currentFile) { const referenced = fs.resolve(this.dirname(currentFile), file, extname) for (const dir of dirs) { - if (referenced.startsWith(dir)) { + if (!enforceRoot || referenced.startsWith(dir)) { // the relatively referenced file is within one of root dirs yield referenced return @@ -51,7 +51,7 @@ export class Loader { } for (const dir of dirs) { const referenced = fs.resolve(dir, file, extname) - if (referenced.startsWith(dir)) { + if (!enforceRoot || referenced.startsWith(dir)) { yield referenced } } diff --git a/src/fs/node.ts b/src/fs/node.ts index 03712eb52..8ef7bdade 100644 --- a/src/fs/node.ts +++ b/src/fs/node.ts @@ -5,8 +5,13 @@ import { stat, statSync, readFile as nodeReadFile, readFileSync as nodeReadFileS const statAsync = _.promisify(stat) const readFileAsync = _.promisify(nodeReadFile) -export function exists (filepath: string) { - return statAsync(filepath).then(() => true).catch(() => false) +export async function exists (filepath: string) { + try { + await statAsync(filepath) + return true + } catch (err) { + return false + } } export function readFile (filepath: string) { return readFileAsync(filepath, 'utf8') diff --git a/src/liquid-options.ts b/src/liquid-options.ts index ef92fda89..2803ec4e2 100644 --- a/src/liquid-options.ts +++ b/src/liquid-options.ts @@ -133,13 +133,13 @@ export const defaultOptions: NormalizedFullOptions = { export function normalize (options?: LiquidOptions): NormalizedOptions { options = options || {} if (options.hasOwnProperty('root')) { - options.root = normalizeStringArray(options.root) + options.root = normalizeDirectoryList(options.root) } if (options.hasOwnProperty('partials')) { - options.partials = normalizeStringArray(options.partials) + options.partials = normalizeDirectoryList(options.partials) } if (options.hasOwnProperty('layouts')) { - options.layouts = normalizeStringArray(options.layouts) + options.layouts = normalizeDirectoryList(options.layouts) } if (options.hasOwnProperty('cache')) { let cache: Cache | undefined @@ -165,8 +165,9 @@ export function applyDefault (options: NormalizedOptions): NormalizedFullOptions return fullOptions } -export function normalizeStringArray (value: any): string[] { - if (_.isArray(value)) return value as string[] - if (_.isString(value)) return [value as string] - return [] +export function normalizeDirectoryList (value: any): string[] { + let list: string[] = [] + if (_.isArray(value)) list = value + if (_.isString(value)) list = [value] + return list.map(str => fs.resolve(str, '.', '')).map(str => str[str.length - 1] !== '/' ? str + '/' : str) } diff --git a/src/liquid.ts b/src/liquid.ts index 286f9649f..46b463b0c 100644 --- a/src/liquid.ts +++ b/src/liquid.ts @@ -10,7 +10,7 @@ import builtinTags from './builtin/tags' import * as builtinFilters from './builtin/filters' import { TagMap } from './template/tag/tag-map' import { FilterMap } from './template/filter/filter-map' -import { LiquidOptions, normalizeStringArray, NormalizedFullOptions, applyDefault, normalize } from './liquid-options' +import { LiquidOptions, normalizeDirectoryList, NormalizedFullOptions, applyDefault, normalize } from './liquid-options' import { FilterImplOptions } from './template/filter/filter-impl-options' import { toPromise, toValue } from './util/async' @@ -116,7 +116,7 @@ export class Liquid { return function (this: any, filePath: string, ctx: object, callback: (err: Error | null, rendered: string) => void) { if (firstCall) { firstCall = false - self.options.root.unshift(...normalizeStringArray(this.root)) + self.options.root.unshift(...normalizeDirectoryList(this.root)) } self.renderFile(filePath, ctx).then(html => callback(null, html) as any, callback as any) } diff --git a/test/e2e/render-file.ts b/test/e2e/render-file.ts index edacaba8f..4caa04da1 100644 --- a/test/e2e/render-file.ts +++ b/test/e2e/render-file.ts @@ -55,6 +55,6 @@ describe('#renderFile()', function () { extname: '.html' }) return expect(engine.renderFile('/not/exist.html')).to - .be.rejectedWith(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/) + .be.rejectedWith(/Failed to lookup "\/not\/exist.html" in "\/boo\/,\/root\/"/) }) }) diff --git a/test/integration/builtin/tags/render.ts b/test/integration/builtin/tags/render.ts index e91d08b9f..203118509 100644 --- a/test/integration/builtin/tags/render.ts +++ b/test/integration/builtin/tags/render.ts @@ -250,7 +250,7 @@ describe('tags/render', function () { }) describe('static partial', function () { - it('should support filename with extention', async function () { + it('should support filename with extension', async function () { mock({ '/parent.html': 'X{% render child.html color:"red" %}Y', '/child.html': 'child with {{color}}' diff --git a/test/integration/liquid/liquid.ts b/test/integration/liquid/liquid.ts index e01a2aac8..69b20bff7 100644 --- a/test/integration/liquid/liquid.ts +++ b/test/integration/liquid/liquid.ts @@ -75,7 +75,7 @@ describe('Liquid', function () { extname: '.html' }) return expect(engine.renderFile('/not/exist.html')).to - .be.rejectedWith(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/) + .be.rejectedWith(/Failed to lookup "\/not\/exist.html" in "\/boo\/,\/root\/"/) }) }) describe('#parseFile', function () { @@ -85,7 +85,7 @@ describe('Liquid', function () { extname: '.html' }) return expect(engine.parseFile('/not/exist.html')).to - .be.rejectedWith(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/) + .be.rejectedWith(/Failed to lookup "\/not\/exist.html" in "\/boo\/,\/root\/"/) }) it('should fallback to require.resolve in Node.js', async function () { const engine = new Liquid({ @@ -120,7 +120,7 @@ describe('Liquid', function () { extname: '.html' }) return expect(() => engine.parseFileSync('/not/exist.html')) - .to.throw(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/) + .to.throw(/Failed to lookup "\/not\/exist.html" in "\/boo\/,\/root\/"/) }) it('should throw with lookup list when file not exist', function () { const engine = new Liquid({ @@ -128,7 +128,7 @@ describe('Liquid', function () { extname: '.html' }) return expect(() => engine.parseFileSync('/not/exist.html')) - .to.throw(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/) + .to.throw(/Failed to lookup "\/not\/exist.html" in "\/boo\/,\/root\/"/) }) }) describe('#enderToNodeStream', function () { @@ -158,7 +158,7 @@ describe('Liquid', function () { expect(drainStream(stream)).to.be.eventually.equal('foo') }) it('should throw RenderError when tag throws', async () => { - const stream = engine.renderFileToNodeStream('error.html') + const stream = await engine.renderFileToNodeStream('error.html') expect(drainStream(stream)).to.be.rejectedWith(/intended render error/) }) }) diff --git a/test/integration/liquid/root.ts b/test/integration/liquid/root.ts index d6caf3428..cc9aba147 100644 --- a/test/integration/liquid/root.ts +++ b/test/integration/liquid/root.ts @@ -1,11 +1,12 @@ import { normalize } from '../../../src/liquid-options' import { expect } from 'chai' +import { resolve } from 'path' describe('LiquidOptions#root', function () { describe('#normalize ()', function () { it('should normalize string typed root array', function () { const options = normalize({ root: 'foo' }) - expect(options.root).to.eql(['foo']) + expect(options.root).to.eql([resolve('foo') + '/']) }) it('should normalize null typed root as empty array', function () { const options = normalize({ root: null } as any) diff --git a/test/unit/fs/loader.ts b/test/unit/fs/loader.ts index cfc26afa8..54da91365 100644 --- a/test/unit/fs/loader.ts +++ b/test/unit/fs/loader.ts @@ -9,7 +9,7 @@ describe('fs/loader', function () { describe('.candidates()', function () { it('should break once found', async function () { const loader = new Loader({ relativeReference: true, fs, extname: '' } as any) - const candidates = [...loader.candidates('./foo/bar', ['/root', '/root/foo'], '/root/current')] + const candidates = [...loader.candidates('./foo/bar', ['/root', '/root/foo'], '/root/current', true)] expect(candidates.join()).to.equal('/root/foo/bar') }) })