mirror of
https://github.com/harttle/liquidjs.git
synced 2026-10-02 16:45:14 -07:00
fix: Windows compat for contains/containsSync and toLiquidAsync arg order
Made-with: Cursor
This commit is contained in:
@@ -12,6 +12,6 @@ All core logic is written once as a `Generator` function (`function *`). Use `yi
|
|||||||
|
|
||||||
Never duplicate logic into separate async and sync methods. A single generator serves both paths.
|
Never duplicate logic into separate async and sync methods. A single generator serves both paths.
|
||||||
|
|
||||||
When wrapping an async+sync function pair (e.g. `contains`/`containsSync`, `exists`/`existsSync`, `readFile`/`readFileSync`), use `toLiquidAsync(syncFn, asyncFn?)` which returns a `LiquidAsync<F>` — one function that picks the sync or async implementation based on a leading `sync: boolean` arg. Then `yield` the result inside a generator to let the driver handle it in both modes.
|
When wrapping an async+sync function pair (e.g. `contains`/`containsSync`, `exists`/`existsSync`, `readFile`/`readFileSync`), use `toLiquidAsync(asyncFn, syncFn?)` which returns a `LiquidAsync<F>` — one function that picks the sync or async implementation based on a leading `sync: boolean` arg. Then `yield` the result inside a generator to let the driver handle it in both modes.
|
||||||
|
|
||||||
See `src/util/async.ts`.
|
See `src/util/async.ts`.
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import * as fs from './fs-impl'
|
import * as fs from './fs-impl'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import { mkdtempSync, writeFileSync, symlinkSync, rmSync } from 'fs'
|
import { mkdtempSync, writeFileSync, symlinkSync, rmSync } from 'fs'
|
||||||
const { join } = path
|
|
||||||
import { tmpdir } from 'os'
|
import { tmpdir } from 'os'
|
||||||
|
|
||||||
|
const { join } = path
|
||||||
|
|
||||||
describe('fs-impl', function () {
|
describe('fs-impl', function () {
|
||||||
describe('.resolve()', function () {
|
describe('.resolve()', function () {
|
||||||
it('should resolve based on root', async function () {
|
it('should resolve based on root', async function () {
|
||||||
@@ -54,7 +55,8 @@ describe('fs-impl', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('.contains()', () => {
|
describe('.contains()', () => {
|
||||||
it('should return false when path is a symlink to outside root', async () => {
|
const canSymlink = process.platform !== 'win32'
|
||||||
|
;(canSymlink ? it : it.skip)('should return false when path is a symlink to outside root', async () => {
|
||||||
const root = mkdtempSync(join(tmpdir(), 'liquid-contains-'))
|
const root = mkdtempSync(join(tmpdir(), 'liquid-contains-'))
|
||||||
const outside = join(tmpdir(), `secret-${Date.now()}.liquid`)
|
const outside = join(tmpdir(), `secret-${Date.now()}.liquid`)
|
||||||
writeFileSync(outside, 'x')
|
writeFileSync(outside, 'x')
|
||||||
@@ -69,7 +71,8 @@ describe('fs-impl', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('.containsSync()', () => {
|
describe('.containsSync()', () => {
|
||||||
it('should return false when path is a symlink to outside root', () => {
|
const canSymlink = process.platform !== 'win32'
|
||||||
|
;(canSymlink ? it : it.skip)('should return false when path is a symlink to outside root', () => {
|
||||||
const root = mkdtempSync(join(tmpdir(), 'liquid-contains-'))
|
const root = mkdtempSync(join(tmpdir(), 'liquid-contains-'))
|
||||||
const outside = join(tmpdir(), `secret-${Date.now()}.liquid`)
|
const outside = join(tmpdir(), `secret-${Date.now()}.liquid`)
|
||||||
writeFileSync(outside, 'x')
|
writeFileSync(outside, 'x')
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as fs from './fs-impl'
|
import * as fs from './fs-impl'
|
||||||
|
import { resolve } from 'path'
|
||||||
import { Loader, LookupType } from './loader'
|
import { Loader, LookupType } from './loader'
|
||||||
import { toValueSync } from '../util/async'
|
import { toValueSync } from '../util/async'
|
||||||
|
|
||||||
@@ -7,7 +8,7 @@ describe('fs/loader', function () {
|
|||||||
it('should resolve relatively', function () {
|
it('should resolve relatively', 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')]
|
||||||
expect(candidates).toContain('/root/foo/bar')
|
expect(candidates).toContain(resolve('/root/foo/bar'))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('.lookup()', function () {
|
describe('.lookup()', function () {
|
||||||
@@ -27,7 +28,7 @@ describe('fs/loader', function () {
|
|||||||
const mockFs = { ...fs, existsSync: () => true, exists: async () => true, contains: undefined, containsSync: undefined }
|
const mockFs = { ...fs, existsSync: () => true, exists: async () => true, contains: undefined, containsSync: undefined }
|
||||||
const loader = new Loader({ relativeReference: true, fs: mockFs, extname: '', partials: ['/root'] } as any)
|
const loader = new Loader({ relativeReference: true, fs: mockFs, extname: '', partials: ['/root'] } as any)
|
||||||
const result = toValueSync(loader.lookup('./foo/bar', LookupType.Partials, true, '/root/current'))
|
const result = toValueSync(loader.lookup('./foo/bar', LookupType.Partials, true, '/root/current'))
|
||||||
expect(result).toBe('/root/foo/bar')
|
expect(result).toBe(resolve('/root/foo/bar'))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+4
-4
@@ -32,12 +32,12 @@ export class Loader {
|
|||||||
}
|
}
|
||||||
const fs = options.fs
|
const fs = options.fs
|
||||||
this.contains = toLiquidAsync(
|
this.contains = toLiquidAsync(
|
||||||
fs.containsSync?.bind(fs) || (() => true),
|
fs.contains?.bind(fs) || (async () => true),
|
||||||
fs.contains?.bind(fs)
|
fs.containsSync?.bind(fs) || (() => true)
|
||||||
)
|
)
|
||||||
this.exists = toLiquidAsync(
|
this.exists = toLiquidAsync(
|
||||||
fs.existsSync.bind(fs),
|
fs.exists?.bind(fs) || (async () => false),
|
||||||
fs.exists.bind(fs)
|
fs.existsSync?.bind(fs)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ export class Parser {
|
|||||||
this.loader = new Loader(this.liquid.options)
|
this.loader = new Loader(this.liquid.options)
|
||||||
this.parseLimit = new Limiter('parse length', liquid.options.parseLimit)
|
this.parseLimit = new Limiter('parse length', liquid.options.parseLimit)
|
||||||
this.readFile = toLiquidAsync(
|
this.readFile = toLiquidAsync(
|
||||||
this.fs.readFileSync.bind(this.fs),
|
this.fs.readFile?.bind(this.fs) || (async () => { throw new Error('readFile not implemented') }),
|
||||||
this.fs.readFile.bind(this.fs)
|
this.fs.readFileSync?.bind(this.fs)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
public parse (html: string, filepath?: string): Template[] {
|
public parse (html: string, filepath?: string): Template[] {
|
||||||
|
|||||||
+4
-4
@@ -4,12 +4,12 @@ export type LiquidAsync<F extends (...args: any[]) => any> =
|
|||||||
(sync: boolean, ...args: Parameters<F>) => ReturnType<F> | Promise<ReturnType<F>>
|
(sync: boolean, ...args: Parameters<F>) => ReturnType<F> | Promise<ReturnType<F>>
|
||||||
|
|
||||||
export function toLiquidAsync<F extends (...args: any[]) => any> (
|
export function toLiquidAsync<F extends (...args: any[]) => any> (
|
||||||
syncFn: F,
|
asyncFn: (...args: Parameters<F>) => Promise<ReturnType<F>>,
|
||||||
asyncFn?: (...args: Parameters<F>) => Promise<ReturnType<F>>
|
syncFn?: F
|
||||||
): LiquidAsync<F> {
|
): LiquidAsync<F> {
|
||||||
const asyncImpl = asyncFn || syncFn as any
|
const syncImpl = syncFn || asyncFn as any
|
||||||
return (sync: boolean, ...args: any[]) => {
|
return (sync: boolean, ...args: any[]) => {
|
||||||
return sync ? syncFn(...args as Parameters<F>) : asyncImpl(...args as Parameters<F>)
|
return sync ? syncImpl(...args as Parameters<F>) : asyncFn(...args as Parameters<F>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ describe('.parseAndRender()', function () {
|
|||||||
const html = await engine.parseAndRender(src)
|
const html = await engine.parseAndRender(src)
|
||||||
expect(html).toBe('true')
|
expect(html).toBe('true')
|
||||||
})
|
})
|
||||||
describe('symlink outside root', function () {
|
const canSymlink = process.platform !== 'win32'
|
||||||
|
;(canSymlink ? describe : describe.skip)('symlink outside root', function () {
|
||||||
let root: string, secret: string
|
let root: string, secret: string
|
||||||
beforeAll(function () {
|
beforeAll(function () {
|
||||||
root = mkdtempSync(join(tmpdir(), 'liquid-e2e-root-'))
|
root = mkdtempSync(join(tmpdir(), 'liquid-e2e-root-'))
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
import { isString, forOwn } from '../../src/util/underscore'
|
import { isString, forOwn } from '../../src/util/underscore'
|
||||||
import * as fs from '../../src/fs/fs-impl'
|
import * as fs from '../../src/fs/fs-impl'
|
||||||
import { resolve } from 'path'
|
import { resolve, sep } from 'path'
|
||||||
|
|
||||||
interface FileDescriptor {
|
interface FileDescriptor {
|
||||||
mode: string;
|
mode: string;
|
||||||
@@ -33,12 +33,12 @@ export function mock (options: { [path: string]: (string | FileDescriptor) }) {
|
|||||||
};
|
};
|
||||||
(fs as any).contains = async (root: string, file: string) => {
|
(fs as any).contains = async (root: string, file: string) => {
|
||||||
root = resolve(root)
|
root = resolve(root)
|
||||||
if (!root.endsWith('/')) root += '/'
|
if (!root.endsWith(sep)) root += sep
|
||||||
return file.startsWith(root)
|
return file.startsWith(root)
|
||||||
};
|
};
|
||||||
(fs as any).containsSync = (root: string, file: string) => {
|
(fs as any).containsSync = (root: string, file: string) => {
|
||||||
root = resolve(root)
|
root = resolve(root)
|
||||||
if (!root.endsWith('/')) root += '/'
|
if (!root.endsWith(sep)) root += sep
|
||||||
return file.startsWith(root)
|
return file.startsWith(root)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user