fix: skip root check for renderFile()

This commit is contained in:
Harttle
2021-10-06 18:38:58 +08:00
parent a3455ebd0b
commit 822ba0be0f
9 changed files with 31 additions and 24 deletions
+4 -4
View File
@@ -23,7 +23,7 @@ export class Loader {
public * lookup (file: string, type: LookupType, sync?: boolean, currentFile?: string) { public * lookup (file: string, type: LookupType, sync?: boolean, currentFile?: string) {
const { fs } = this.options const { fs } = this.options
const dirs = this.options[type] 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 if (sync ? fs.existsSync(filepath) : yield fs.exists(filepath)) return filepath
} }
throw this.lookupError(file, dirs) throw this.lookupError(file, dirs)
@@ -37,12 +37,12 @@ export class Loader {
return path.startsWith('./') || path.startsWith('../') 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 const { fs, extname } = this.options
if (this.shouldLoadRelative(file) && currentFile) { if (this.shouldLoadRelative(file) && currentFile) {
const referenced = fs.resolve(this.dirname(currentFile), file, extname) const referenced = fs.resolve(this.dirname(currentFile), file, extname)
for (const dir of dirs) { for (const dir of dirs) {
if (referenced.startsWith(dir)) { if (!enforceRoot || referenced.startsWith(dir)) {
// the relatively referenced file is within one of root dirs // the relatively referenced file is within one of root dirs
yield referenced yield referenced
return return
@@ -51,7 +51,7 @@ export class Loader {
} }
for (const dir of dirs) { for (const dir of dirs) {
const referenced = fs.resolve(dir, file, extname) const referenced = fs.resolve(dir, file, extname)
if (referenced.startsWith(dir)) { if (!enforceRoot || referenced.startsWith(dir)) {
yield referenced yield referenced
} }
} }
+7 -2
View File
@@ -5,8 +5,13 @@ import { stat, statSync, readFile as nodeReadFile, readFileSync as nodeReadFileS
const statAsync = _.promisify(stat) const statAsync = _.promisify(stat)
const readFileAsync = _.promisify<string, string, string>(nodeReadFile) const readFileAsync = _.promisify<string, string, string>(nodeReadFile)
export function exists (filepath: string) { export async function exists (filepath: string) {
return statAsync(filepath).then(() => true).catch(() => false) try {
await statAsync(filepath)
return true
} catch (err) {
return false
}
} }
export function readFile (filepath: string) { export function readFile (filepath: string) {
return readFileAsync(filepath, 'utf8') return readFileAsync(filepath, 'utf8')
+8 -7
View File
@@ -133,13 +133,13 @@ export const defaultOptions: NormalizedFullOptions = {
export function normalize (options?: LiquidOptions): NormalizedOptions { export function normalize (options?: LiquidOptions): NormalizedOptions {
options = options || {} options = options || {}
if (options.hasOwnProperty('root')) { if (options.hasOwnProperty('root')) {
options.root = normalizeStringArray(options.root) options.root = normalizeDirectoryList(options.root)
} }
if (options.hasOwnProperty('partials')) { if (options.hasOwnProperty('partials')) {
options.partials = normalizeStringArray(options.partials) options.partials = normalizeDirectoryList(options.partials)
} }
if (options.hasOwnProperty('layouts')) { if (options.hasOwnProperty('layouts')) {
options.layouts = normalizeStringArray(options.layouts) options.layouts = normalizeDirectoryList(options.layouts)
} }
if (options.hasOwnProperty('cache')) { if (options.hasOwnProperty('cache')) {
let cache: Cache<Template[]> | undefined let cache: Cache<Template[]> | undefined
@@ -165,8 +165,9 @@ export function applyDefault (options: NormalizedOptions): NormalizedFullOptions
return fullOptions return fullOptions
} }
export function normalizeStringArray (value: any): string[] { export function normalizeDirectoryList (value: any): string[] {
if (_.isArray(value)) return value as string[] let list: string[] = []
if (_.isString(value)) return [value as string] if (_.isArray(value)) list = value
return [] if (_.isString(value)) list = [value]
return list.map(str => fs.resolve(str, '.', '')).map(str => str[str.length - 1] !== '/' ? str + '/' : str)
} }
+2 -2
View File
@@ -10,7 +10,7 @@ import builtinTags from './builtin/tags'
import * as builtinFilters from './builtin/filters' import * as builtinFilters from './builtin/filters'
import { TagMap } from './template/tag/tag-map' import { TagMap } from './template/tag/tag-map'
import { FilterMap } from './template/filter/filter-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 { FilterImplOptions } from './template/filter/filter-impl-options'
import { toPromise, toValue } from './util/async' 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) { return function (this: any, filePath: string, ctx: object, callback: (err: Error | null, rendered: string) => void) {
if (firstCall) { if (firstCall) {
firstCall = false 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) self.renderFile(filePath, ctx).then(html => callback(null, html) as any, callback as any)
} }
+1 -1
View File
@@ -55,6 +55,6 @@ describe('#renderFile()', function () {
extname: '.html' extname: '.html'
}) })
return expect(engine.renderFile('/not/exist.html')).to 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\/"/)
}) })
}) })
+1 -1
View File
@@ -250,7 +250,7 @@ describe('tags/render', function () {
}) })
describe('static partial', function () { describe('static partial', function () {
it('should support filename with extention', async function () { it('should support filename with extension', async function () {
mock({ mock({
'/parent.html': 'X{% render child.html color:"red" %}Y', '/parent.html': 'X{% render child.html color:"red" %}Y',
'/child.html': 'child with {{color}}' '/child.html': 'child with {{color}}'
+5 -5
View File
@@ -75,7 +75,7 @@ describe('Liquid', function () {
extname: '.html' extname: '.html'
}) })
return expect(engine.renderFile('/not/exist.html')).to 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 () { describe('#parseFile', function () {
@@ -85,7 +85,7 @@ describe('Liquid', function () {
extname: '.html' extname: '.html'
}) })
return expect(engine.parseFile('/not/exist.html')).to 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 () { it('should fallback to require.resolve in Node.js', async function () {
const engine = new Liquid({ const engine = new Liquid({
@@ -120,7 +120,7 @@ describe('Liquid', function () {
extname: '.html' extname: '.html'
}) })
return expect(() => engine.parseFileSync('/not/exist.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 () { it('should throw with lookup list when file not exist', function () {
const engine = new Liquid({ const engine = new Liquid({
@@ -128,7 +128,7 @@ describe('Liquid', function () {
extname: '.html' extname: '.html'
}) })
return expect(() => engine.parseFileSync('/not/exist.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 () { describe('#enderToNodeStream', function () {
@@ -158,7 +158,7 @@ describe('Liquid', function () {
expect(drainStream(stream)).to.be.eventually.equal('foo') expect(drainStream(stream)).to.be.eventually.equal('foo')
}) })
it('should throw RenderError when tag throws', async () => { 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/) expect(drainStream(stream)).to.be.rejectedWith(/intended render error/)
}) })
}) })
+2 -1
View File
@@ -1,11 +1,12 @@
import { normalize } from '../../../src/liquid-options' import { normalize } from '../../../src/liquid-options'
import { expect } from 'chai' import { expect } from 'chai'
import { resolve } from 'path'
describe('LiquidOptions#root', function () { describe('LiquidOptions#root', function () {
describe('#normalize ()', function () { describe('#normalize ()', function () {
it('should normalize string typed root array', function () { it('should normalize string typed root array', function () {
const options = normalize({ root: 'foo' }) 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 () { it('should normalize null typed root as empty array', function () {
const options = normalize({ root: null } as any) const options = normalize({ root: null } as any)
+1 -1
View File
@@ -9,7 +9,7 @@ describe('fs/loader', function () {
describe('.candidates()', function () { describe('.candidates()', function () {
it('should break once found', async function () { it('should break once found', async function () {
const loader = new Loader({ relativeReference: true, fs, extname: '' } as any) 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') expect(candidates.join()).to.equal('/root/foo/bar')
}) })
}) })