mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-15 04:10:40 -07:00
* feat!: remove CLI support for template via STDIN Fixes #940 Co-authored-by: Cursor <[email protected]> * feat!: take CLI template as positional argument Make template a required positional arg (drop --template) for v11 per #586; stdin template remains unsupported without a special error. Co-authored-by: Cursor <[email protected]> * fix: restore --template CLI option Revert the positional-only template change. Keep --template as the primary API; stdin template remains unsupported without a special error. Co-authored-by: Cursor <[email protected]> * fix: restore explicit @- stdin for template and context Keep @- for --template and --context; only the legacy bare-stdin-as-template fallback stays removed. Co-authored-by: Cursor <[email protected]> * feat: accept CLI template as positional or --template Support positional <template> alongside --template/-t for compatibility; error if both are set and differ. Bare stdin template remains removed; @- still works. Co-authored-by: Cursor <[email protected]> * feat!: remove --template CLI option Template is positional-only; bare stdin template and --template/-t are both removed. Keep @- for template and --context. Co-authored-by: Cursor <[email protected]> --------- Co-authored-by: Cursor <[email protected]>
100 lines
4.4 KiB
JavaScript
Executable File
100 lines
4.4 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
const fs = require('fs/promises')
|
|
const Liquid = require('..').Liquid
|
|
|
|
render().catch(err => {
|
|
process.stderr.write(`${err.message}\n`)
|
|
process.exitCode = 1
|
|
})
|
|
|
|
async function render () {
|
|
const { program } = require('commander')
|
|
|
|
program
|
|
.name('liquidjs')
|
|
.description('Render a Liquid template')
|
|
.argument('<template>', 'liquid template to render (inline, @path, or @- for stdin)')
|
|
.option('-c, --context <json | @path>', 'input context in JSON format (inline, @path, or @- for stdin)')
|
|
.option('-o, --output <path>', 'write rendered output to file (omit to write to stdout)')
|
|
.option('--cache [size]', 'cache previously parsed template structures (default cache size: 1024)')
|
|
.option('--extname <string>', 'use a default filename extension when resolving partials and layouts')
|
|
.option('--jekyll-include', 'use jekyll-style include (pass parameters to include variable of current scope)')
|
|
.option('--js-truthy', 'use JavaScript-style truthiness')
|
|
.option('--layouts <path...>', 'directories from where to resolve layouts (defaults to --root)')
|
|
.option('--lenient-if', 'do not throw on undefined variables in conditional expressions (when using --strict-variables)')
|
|
.option('--no-dynamic-partials', 'always treat file paths for partials and layouts as a literal value')
|
|
.option('--no-greedy', 'disable greedy matching for --trim* options')
|
|
.option('--no-relative-reference', 'require absolute file paths for partials and layouts')
|
|
.option('--ordered-filter-parameters', 'respect parameter order when using filters')
|
|
.option('--output-delimiter-left <string>', 'left delimiter to use for liquid outputs')
|
|
.option('--output-delimiter-right <string>', 'right delimiter to use for liquid outputs')
|
|
.option('--partials <path...>', 'directories from where to resolve partials (defaults to --root)')
|
|
.option('--preserve-timezones', 'preserve input timezone in date filter')
|
|
.option('--root <path...>', 'directories from where to resolve partials and layouts (defaults to ".")')
|
|
.option('--strict-filters', 'throw on undefined filters instead of skipping them')
|
|
.option('--strict-variables', 'throw on undefined variables instead of rendering them as empty string')
|
|
.option('--tag-delimiter-left', 'left delimiter to use for liquid tags')
|
|
.option('--tag-delimiter-right', 'right delimiter to use for liquid tags')
|
|
.option('--timezone-offset <value>', 'JavaScript timezone name or timezoneOffset value to use in date filter (defaults to local timezone)')
|
|
.option('--trim-output-left', 'trim whitespace from left of liquid outputs')
|
|
.option('--trim-output-right', 'trim whitespace from right of liquid outputs')
|
|
.option('--trim-tag-left', 'trim whitespace from left of liquid tags')
|
|
.option('--trim-tag-right', 'trim whitespace from right of liquid tags')
|
|
.showHelpAfterError('Use -h or --help for additional information.')
|
|
.parse()
|
|
|
|
const options = program.opts()
|
|
const templateOption = program.args[0]
|
|
|
|
if (Object.values({ template: templateOption, context: options.context }).filter((value) => value === '@-').length > 1) {
|
|
throw new Error(`The stdin input specifier '@-' must only be used once.`)
|
|
}
|
|
|
|
const template = await resolveInputOption(templateOption)
|
|
const context = await resolveContext(options.context)
|
|
const liquid = new Liquid(options)
|
|
const output = liquid.parseAndRenderSync(template, context)
|
|
if (options.output) {
|
|
await fs.writeFile(options.output, output)
|
|
} else {
|
|
process.stdout.write(output)
|
|
}
|
|
}
|
|
|
|
async function resolveContext (contextOption) {
|
|
let contextJson = '{}'
|
|
if (contextOption) {
|
|
contextJson = await resolveInputOption(contextOption)
|
|
}
|
|
const context = JSON.parse(contextJson)
|
|
return context
|
|
}
|
|
|
|
async function resolveInputOption (option) {
|
|
let content = null
|
|
if (option) {
|
|
if (option === '@-') {
|
|
content = await readStream(process.stdin)
|
|
} else if (option.startsWith('@')) {
|
|
const filePath = option.slice(1)
|
|
const stat = await fs.stat(filePath, { throwIfNoEntry: false })
|
|
if (!stat || !stat.isFile) {
|
|
throw new Error(`'${filePath}' does not exist or is not a file`)
|
|
}
|
|
content = await fs.readFile(filePath, 'utf8')
|
|
} else {
|
|
content = option
|
|
}
|
|
}
|
|
return content
|
|
}
|
|
|
|
async function readStream (stream) {
|
|
const chunks = []
|
|
for await (const chunk of stream) {
|
|
chunks.push(chunk)
|
|
}
|
|
return Buffer.concat(chunks).toString('utf8')
|
|
}
|