feat!: require positional template; drop bare stdin and --template (#942)

* 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]>
This commit is contained in:
Yang Jun
2026-07-28 00:05:03 +08:00
committed by GitHub
co-authored by Cursor
parent 964a63b362
commit 1432380329
3 changed files with 19 additions and 64 deletions
+6 -46
View File
@@ -3,19 +3,7 @@
const fs = require('fs/promises')
const Liquid = require('..').Liquid
// Preserve compatibility by falling back to legacy CLI behavior if:
// - stdin is redirected (i.e. not connected to a terminal) AND
// - there are either no arguments, or only a single argument which does not start with a dash
// TODO: Remove this fallback for 11.0
let renderPromise = null
if (!process.stdin.isTTY && (process.argv.length === 2 || (process.argv.length === 3 && !process.argv[2].startsWith('-')))) {
renderPromise = renderLegacy()
} else {
renderPromise = render()
}
renderPromise.catch(err => {
render().catch(err => {
process.stderr.write(`${err.message}\n`)
process.exitCode = 1
})
@@ -26,8 +14,8 @@ async function render () {
program
.name('liquidjs')
.description('Render a Liquid template')
.requiredOption('-t, --template <liquid | @path>', 'liquid template to render (@- to read from stdin)') // TODO: Change to argument in 11.0
.option('-c, --context <json | @path>', 'input context in JSON format (@- to read from stdin)')
.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')
@@ -57,12 +45,13 @@ async function render () {
.parse()
const options = program.opts()
const templateOption = program.args[0]
if (Object.values(options).filter((value) => value === '@-').length > 1) {
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(options.template)
const template = await resolveInputOption(templateOption)
const context = await resolveContext(options.context)
const liquid = new Liquid(options)
const output = liquid.parseAndRenderSync(template, context)
@@ -108,32 +97,3 @@ async function readStream (stream) {
}
return Buffer.concat(chunks).toString('utf8')
}
// TODO: Remove for 11.0
async function renderLegacy () {
process.stderr.write('Reading template from stdin. This mode will be removed in next major version, use --template option instead.\n')
const contextArg = process.argv.slice(2)[0]
let context = {}
if (contextArg) {
const contextJson = await resolveInputOptionLegacy(contextArg)
context = JSON.parse(contextJson)
}
const template = await readStream(process.stdin)
const liquid = new Liquid()
const output = liquid.parseAndRenderSync(template, context)
process.stdout.write(output)
}
// TODO: Remove for 11.0
async function resolveInputOptionLegacy (option) {
let content = null
if (option) {
const stat = await fs.stat(option).catch(e => null)
if (stat && stat.isFile) {
content = await fs.readFile(option, 'utf8')
} else {
content = option
}
}
return content
}