From 1c4f9fc19bba4411afe73663481c985b303d8652 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Fri, 11 Sep 2026 17:18:18 +0200 Subject: [PATCH 1/5] fix: always return a Version object from quarto.version/quarto.config.version() init.lua's version() silently returned the raw version string whenever pandoc.types.Version() failed to parse it, making quarto.version and quarto.config.version() polymorphic: a Version object on success, a plain string otherwise. pandoc.types.Version's dotted-integer parser rejects any string with a "+" (semver build metadata) or without a leading digit, which consumers like the `{{< version >}}` shortcode (version.lua) don't expect -- it unconditionally calls table.concat(quarto.version, '.'), which crashes with "bad argument #1 to 'concat' (table expected, got string)" whenever the string fallback is hit. This is reachable outside of CI too: QUARTO_FORCE_VERSION is a user-settable env override, and quarto-bld prepare-dist --set-version accepts an arbitrary string, so a distro packager appending a revision suffix could hit the same crash on a real build. Extract the leading dot-separated numeric component of the version string before constructing the Version object, so parsing always succeeds and the string-fallback branch becomes unreachable. Both quarto.version and quarto.config.version() go through the same local version() function, so one fix covers both call sites. --- src/resources/pandoc/datadir/init.lua | 14 ++- .../lua-unit/quarto-version-repr.test.ts | 85 +++++++++++++++++++ tests/unit-lua/quarto-version-repr.test.lua | 41 +++++++++ 3 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 tests/smoke/lua-unit/quarto-version-repr.test.ts create mode 100644 tests/unit-lua/quarto-version-repr.test.lua diff --git a/src/resources/pandoc/datadir/init.lua b/src/resources/pandoc/datadir/init.lua index bd19df840ff..6b4fb4527b3 100644 --- a/src/resources/pandoc/datadir/init.lua +++ b/src/resources/pandoc/datadir/init.lua @@ -677,11 +677,21 @@ end local function version() local versionString = param('quarto-version', 'unknown') - local success, versionObject = pcall(pandoc.types.Version, versionString) + -- pandoc.types.Version's dotted-integer parser rejects semver build + -- metadata (e.g. "1.9.13+test.20260910") and any string without a + -- leading digit. Extract the leading dot-separated numeric component so + -- that construction always succeeds and this always returns a Version + -- object -- callers (e.g. table.concat(quarto.version, '.')) must not + -- have to handle a plain string fallback. + local numericVersion = versionString:match('^(%d+%.%d+%.%d+)') + or versionString:match('^(%d+%.%d+)') + or versionString:match('^(%d+)') + or '0' + local success, versionObject = pcall(pandoc.types.Version, numericVersion) if success then return versionObject else - return versionString + return pandoc.types.Version('0') end end diff --git a/tests/smoke/lua-unit/quarto-version-repr.test.ts b/tests/smoke/lua-unit/quarto-version-repr.test.ts new file mode 100644 index 00000000000..5c6332186aa --- /dev/null +++ b/tests/smoke/lua-unit/quarto-version-repr.test.ts @@ -0,0 +1,85 @@ +/* + * quarto-version-repr.test.ts + * + * Regression test for quarto.version / quarto.config.version() always being + * a pandoc Version object (table), even when the `quarto-version` filter + * param carries semver build metadata or a prerelease suffix that pandoc's + * dotted-integer Version parser rejects (e.g. "1.9.13+test.20260910" or + * "1.9.13-1"). Such strings reach quarto-version via QUARTO_FORCE_VERSION, + * a packager-appended revision suffix, or CI build-metadata stamping + * (test-smokes-built.yml). + * + * Unlike tests/smoke/lua-unit/lua-unit.test.ts, this does not go through + * `quarto run` -- that invokes pandoc without --data-dir, so init.lua from + * src/resources/pandoc/datadir/ (which defines quarto.version / + * quarto.config.version()) never loads. Instead this invokes pandoc + * directly with --data-dir pointing at that directory, forcing the + * quarto-version filter param via QUARTO_FILTER_PARAMS -- the same + * mechanism a real quarto render uses (src/command/render/filters.ts, + * src/command/render/pandoc.ts). + */ + +import { encodeBase64 } from "encoding/base64"; +import { fromFileUrl, join } from "../../../src/deno_ral/path.ts"; +import { assert } from "testing/asserts"; +import { execProcess } from "../../../src/core/process.ts"; +import { pandocBinaryPath, resourcePath } from "../../../src/core/resources.ts"; +import { unitTest } from "../../test.ts"; + +const testsDir = fromFileUrl(new URL("../../", import.meta.url)); +const unitLuaDir = join(testsDir, "unit-lua"); +const luaScript = join(unitLuaDir, "quarto-version-repr.test.lua"); + +// The `;;` at the end preserves the default search path. +const LUA_PATH = [join(unitLuaDir, "?.lua"), ""].join(";") + ";"; + +// A plain dotted-integer version parses fine under pandoc.types.Version -- +// this case must keep working (regression guard for the normal case). The +// other two are strings pandoc.types.Version's parser rejects: a +// build-metadata suffix (as CI's test-smokes-built.yml stamps via +// QUARTO_FORCE_VERSION, and as a distro packager might append) and a +// version with no leading digit at all. +const VERSION_STRINGS: Record = { + "plain numeric version": "1.9.13", + "build-metadata suffixed version": "1.9.13+test.20260910", + "non-numeric version": "unknown", +}; + +for (const [label, versionString] of Object.entries(VERSION_STRINGS)) { + unitTest(`quarto-version-repr > ${label}`, async () => { + const filterParams = encodeBase64( + JSON.stringify({ "quarto-version": versionString }), + ); + const result = await execProcess( + { + cmd: pandocBinaryPath(), + args: [ + "--data-dir", + resourcePath("pandoc/datadir"), + "--from", + "markdown", + "--to", + "plain", + "--lua-filter", + luaScript, + ], + env: { + LUA_PATH, + QUARTO_FILTER_PARAMS: filterParams, + QUARTO_SHARE_PATH: resourcePath(), + }, + }, + "test\n", + undefined, + undefined, + true, // capture stdout/stderr + ); + assert( + result.success, + `quarto.version / quarto.config.version() were not a Version object ` + + `for quarto-version="${versionString}" (exit ${result.code}):\n` + + `--- stdout ---\n${result.stdout ?? ""}\n` + + `--- stderr ---\n${result.stderr ?? ""}`, + ); + }); +} diff --git a/tests/unit-lua/quarto-version-repr.test.lua b/tests/unit-lua/quarto-version-repr.test.lua new file mode 100644 index 00000000000..4c4f786a9d0 --- /dev/null +++ b/tests/unit-lua/quarto-version-repr.test.lua @@ -0,0 +1,41 @@ +-- Regression test for quarto.version / quarto.config.version() always +-- supporting table.concat(), even when the `quarto-version` filter param is +-- a string pandoc.types.Version's dotted-integer parser rejects (e.g. a +-- semver build-metadata suffix like "1.9.13+test.20260910", or an entirely +-- non-numeric string). Note: pandoc.types.Version is actually lenient about +-- a trailing "-suffix" (e.g. "1.9.13-1" parses fine, silently dropping the +-- suffix) -- the "+" character is what breaks its parser, along with any +-- string that doesn't start with a digit. +-- +-- On successful parsing, pandoc.types.Version returns a userdata object +-- (not a plain Lua table) that nonetheless supports table.concat() via its +-- metatable -- so this test checks table.concat() succeeds, not literal +-- Lua `type()`. +-- +-- Unlike the other tests in this directory, this one is not run through +-- `quarto run` (which invokes pandoc without --data-dir, so init.lua from +-- src/resources/pandoc/datadir/ never loads). It is invoked directly against +-- pandoc with --data-dir pointing at that directory, so the real init.lua +-- runs and populates the real `quarto` global -- see +-- tests/smoke/lua-unit/quarto-version-repr.test.ts, which sets +-- QUARTO_FILTER_PARAMS to force a specific `quarto-version` filter param. +-- +-- Reproduces the exact call made by the `{{< version >}}` shortcode +-- (src/resources/extensions/quarto/version/version.lua). +local lu = require('luaunit') + +TestQuartoVersionRepr = {} + +function TestQuartoVersionRepr:testVersionConcatWorks() + local ok, err = pcall(table.concat, quarto.version, '.') + lu.assertTrue(ok, 'table.concat(quarto.version, ".") failed: ' .. tostring(err)) +end + +function TestQuartoVersionRepr:testConfigVersionConcatWorks() + local ok, err = pcall(table.concat, quarto.config.version(), '.') + lu.assertTrue(ok, 'table.concat(quarto.config.version(), ".") failed: ' .. tostring(err)) +end + +function Pandoc(doc) + os.exit(lu.LuaUnit.run()) +end From 5693fd71911822468bf3a1739efeecb42da3dc49 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Fri, 11 Sep 2026 18:02:12 +0200 Subject: [PATCH 2/5] fix: handle version strings with more than 3 dotted components The previous fix extracted the leading numeric prefix from a version string with a fixed-arity pattern chain (^%d+%.%d+%.%d+, then ^%d+%.%d+, then ^%d+), which silently truncates any version with more than three dot-separated numeric components -- e.g. "1.2.3.4" became "1.2.3". QUARTO_FORCE_VERSION and quarto-bld prepare-dist --set-version both accept arbitrary strings, so a 4+ component version is reachable, not just hypothetical. Replace the pattern chain with leadingDottedNumber(), which walks the string collecting all leading dot-separated digit runs instead of matching a hard-coded number of them. Strengthen the existing regression tests to assert the exact concatenated value (not just that table.concat didn't crash), and add a four-component case. --- src/resources/pandoc/datadir/init.lua | 31 ++++++++++++++--- .../lua-unit/quarto-version-repr.test.ts | 33 +++++++++++++------ tests/unit-lua/quarto-version-repr.test.lua | 20 ++++++++--- 3 files changed, 66 insertions(+), 18 deletions(-) diff --git a/src/resources/pandoc/datadir/init.lua b/src/resources/pandoc/datadir/init.lua index 6b4fb4527b3..dfb4a58a1c1 100644 --- a/src/resources/pandoc/datadir/init.lua +++ b/src/resources/pandoc/datadir/init.lua @@ -675,6 +675,32 @@ local function outputFile() end end +-- Walks `s` from the start, collecting dot-separated runs of digits (e.g. +-- "1.9.13" out of "1.9.13+test.20260910", or "1.2.3.4" unchanged). Lua +-- patterns can't express "one or more repeated groups", so this is done by +-- hand rather than a fixed-arity chain of %d+%.%d+... alternatives, which +-- would silently truncate any version with more components than the chain +-- hard-codes. +local function leadingDottedNumber(s) + local parts = {} + local i = 1 + while true do + local numStart, numEnd = s:find('^%d+', i) + if not numStart then break end + table.insert(parts, s:sub(numStart, numEnd)) + i = numEnd + 1 + if s:sub(i, i) == '.' then + i = i + 1 + else + break + end + end + if #parts == 0 then + return nil + end + return table.concat(parts, '.') +end + local function version() local versionString = param('quarto-version', 'unknown') -- pandoc.types.Version's dotted-integer parser rejects semver build @@ -683,10 +709,7 @@ local function version() -- that construction always succeeds and this always returns a Version -- object -- callers (e.g. table.concat(quarto.version, '.')) must not -- have to handle a plain string fallback. - local numericVersion = versionString:match('^(%d+%.%d+%.%d+)') - or versionString:match('^(%d+%.%d+)') - or versionString:match('^(%d+)') - or '0' + local numericVersion = leadingDottedNumber(versionString) or '0' local success, versionObject = pcall(pandoc.types.Version, numericVersion) if success then return versionObject diff --git a/tests/smoke/lua-unit/quarto-version-repr.test.ts b/tests/smoke/lua-unit/quarto-version-repr.test.ts index 5c6332186aa..12695829589 100644 --- a/tests/smoke/lua-unit/quarto-version-repr.test.ts +++ b/tests/smoke/lua-unit/quarto-version-repr.test.ts @@ -35,20 +35,33 @@ const LUA_PATH = [join(unitLuaDir, "?.lua"), ""].join(";") + ";"; // A plain dotted-integer version parses fine under pandoc.types.Version -- // this case must keep working (regression guard for the normal case). The -// other two are strings pandoc.types.Version's parser rejects: a -// build-metadata suffix (as CI's test-smokes-built.yml stamps via -// QUARTO_FORCE_VERSION, and as a distro packager might append) and a -// version with no leading digit at all. -const VERSION_STRINGS: Record = { - "plain numeric version": "1.9.13", - "build-metadata suffixed version": "1.9.13+test.20260910", - "non-numeric version": "unknown", +// four-component case guards against a fixed-arity extraction silently +// truncating a version with more components than it hard-codes. The other +// two are strings pandoc.types.Version's parser rejects: a build-metadata +// suffix (as CI's test-smokes-built.yml stamps via QUARTO_FORCE_VERSION, and +// as a distro packager might append) and a version with no leading digit at +// all. +const VERSION_CASES: Record = { + "plain numeric version": { input: "1.9.13", expected: "1.9.13" }, + "four-component version": { input: "1.2.3.4", expected: "1.2.3.4" }, + "build-metadata suffixed version": { + input: "1.9.13+test.20260910", + expected: "1.9.13", + }, + "non-numeric version": { input: "unknown", expected: "0" }, }; -for (const [label, versionString] of Object.entries(VERSION_STRINGS)) { +for ( + const [label, { input: versionString, expected }] of Object.entries( + VERSION_CASES, + ) +) { unitTest(`quarto-version-repr > ${label}`, async () => { const filterParams = encodeBase64( - JSON.stringify({ "quarto-version": versionString }), + JSON.stringify({ + "quarto-version": versionString, + "expected-version": expected, + }), ); const result = await execProcess( { diff --git a/tests/unit-lua/quarto-version-repr.test.lua b/tests/unit-lua/quarto-version-repr.test.lua index 4c4f786a9d0..7a0d25c86df 100644 --- a/tests/unit-lua/quarto-version-repr.test.lua +++ b/tests/unit-lua/quarto-version-repr.test.lua @@ -26,14 +26,26 @@ local lu = require('luaunit') TestQuartoVersionRepr = {} +-- Optional: when the `expected-version` filter param is set, assert the +-- concatenated value exactly, not just that concat() didn't crash. Catches +-- silent truncation (e.g. a fixed-arity extraction pattern dropping a 4th +-- dotted component) that a crash-only check would miss. +local expectedVersion = param('expected-version', nil) + function TestQuartoVersionRepr:testVersionConcatWorks() - local ok, err = pcall(table.concat, quarto.version, '.') - lu.assertTrue(ok, 'table.concat(quarto.version, ".") failed: ' .. tostring(err)) + local ok, result = pcall(table.concat, quarto.version, '.') + lu.assertTrue(ok, 'table.concat(quarto.version, ".") failed: ' .. tostring(result)) + if expectedVersion then + lu.assertEquals(result, expectedVersion) + end end function TestQuartoVersionRepr:testConfigVersionConcatWorks() - local ok, err = pcall(table.concat, quarto.config.version(), '.') - lu.assertTrue(ok, 'table.concat(quarto.config.version(), ".") failed: ' .. tostring(err)) + local ok, result = pcall(table.concat, quarto.config.version(), '.') + lu.assertTrue(ok, 'table.concat(quarto.config.version(), ".") failed: ' .. tostring(result)) + if expectedVersion then + lu.assertEquals(result, expectedVersion) + end end function Pandoc(doc) From 560d9b5f3f991d4a0445eb7f8b39e98198115aed Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Wed, 16 Sep 2026 12:15:51 +0200 Subject: [PATCH 3/5] docs: clarify version parsing comments --- src/resources/pandoc/datadir/init.lua | 8 ++------ tests/smoke/lua-unit/quarto-version-repr.test.ts | 14 +++++++------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/resources/pandoc/datadir/init.lua b/src/resources/pandoc/datadir/init.lua index dfb4a58a1c1..d0f937d4882 100644 --- a/src/resources/pandoc/datadir/init.lua +++ b/src/resources/pandoc/datadir/init.lua @@ -675,12 +675,8 @@ local function outputFile() end end --- Walks `s` from the start, collecting dot-separated runs of digits (e.g. --- "1.9.13" out of "1.9.13+test.20260910", or "1.2.3.4" unchanged). Lua --- patterns can't express "one or more repeated groups", so this is done by --- hand rather than a fixed-arity chain of %d+%.%d+... alternatives, which --- would silently truncate any version with more components than the chain --- hard-codes. +-- Returns the leading sequence of dot-separated numeric components. +-- Lua patterns cannot match an arbitrary number of repeated groups. local function leadingDottedNumber(s) local parts = {} local i = 1 diff --git a/tests/smoke/lua-unit/quarto-version-repr.test.ts b/tests/smoke/lua-unit/quarto-version-repr.test.ts index 12695829589..317f4a594cb 100644 --- a/tests/smoke/lua-unit/quarto-version-repr.test.ts +++ b/tests/smoke/lua-unit/quarto-version-repr.test.ts @@ -1,12 +1,12 @@ /* * quarto-version-repr.test.ts * - * Regression test for quarto.version / quarto.config.version() always being - * a pandoc Version object (table), even when the `quarto-version` filter - * param carries semver build metadata or a prerelease suffix that pandoc's - * dotted-integer Version parser rejects (e.g. "1.9.13+test.20260910" or - * "1.9.13-1"). Such strings reach quarto-version via QUARTO_FORCE_VERSION, - * a packager-appended revision suffix, or CI build-metadata stamping + * Regression test ensuring that quarto.version / quarto.config.version() + * always return a pandoc Version object, even when the `quarto-version` + * filter parameter contains semver build metadata or does not begin with a + * digit. Pandoc's dotted-integer Version parser rejects both forms. Such + * strings can reach quarto-version via QUARTO_FORCE_VERSION, a + * packager-appended revision suffix, or CI build-metadata stamping * (test-smokes-built.yml). * * Unlike tests/smoke/lua-unit/lua-unit.test.ts, this does not go through @@ -85,7 +85,7 @@ for ( "test\n", undefined, undefined, - true, // capture stdout/stderr + true, // forward child output directly to stdout/stderr ); assert( result.success, From 71a7b84cd8e64d95173aeb53f1ac8ef84c42cb6d Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Wed, 16 Sep 2026 12:48:27 +0200 Subject: [PATCH 4/5] refactor: extract pandocDataDirArgs() helper Quarto's own --data-dir resourcePath("pandoc/datadir") pair was hardcoded independently in three places (crossref, pandoc render, and the version-repr test), risking drift if the path ever changes. --- src/command/editor-support/crossref.ts | 9 ++++++--- src/command/render/pandoc.ts | 8 ++++++-- src/core/resources.ts | 8 ++++++++ tests/smoke/lua-unit/quarto-version-repr.test.ts | 11 +++++++---- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/command/editor-support/crossref.ts b/src/command/editor-support/crossref.ts index e0f7d866d3d..b5a834515be 100644 --- a/src/command/editor-support/crossref.ts +++ b/src/command/editor-support/crossref.ts @@ -12,7 +12,11 @@ import { encodeBase64 } from "../../deno_ral/encoding.ts"; import { Command } from "cliffy/command/mod.ts"; import { execProcess } from "../../core/process.ts"; -import { pandocBinaryPath, resourcePath } from "../../core/resources.ts"; +import { + pandocBinaryPath, + pandocDataDirArgs, + resourcePath, +} from "../../core/resources.ts"; import { globalTempContext } from "../../core/temp.ts"; function parseCrossrefFlags(options: any, args: string[]): { @@ -99,8 +103,7 @@ const makeCrossrefCommand = () => { resourcePath("filters/qmd-reader.lua"), "--to", "native", - "--data-dir", - resourcePath("pandoc/datadir"), + ...pandocDataDirArgs(), "--lua-filter", resourcePath("filters/quarto-init/quarto-init.lua"), "--lua-filter", diff --git a/src/command/render/pandoc.ts b/src/command/render/pandoc.ts index 602b9b7be0b..bdd9bfb6443 100644 --- a/src/command/render/pandoc.ts +++ b/src/command/render/pandoc.ts @@ -58,7 +58,11 @@ import { isQuartoMetadata, metadataGetDeep, } from "../../config/metadata.ts"; -import { pandocBinaryPath, resourcePath } from "../../core/resources.ts"; +import { + pandocBinaryPath, + pandocDataDirArgs, + resourcePath, +} from "../../core/resources.ts"; import { getAvailableTypstFonts } from "../../core/typst.ts"; import { filterBundledSubtreeEngines } from "../../extension/extension.ts"; import { pandocAutoIdentifier } from "../../core/pandoc/pandoc-id.ts"; @@ -1071,7 +1075,7 @@ export async function runPandoc( pandocArgs, dataDirArgs, ); - pandocArgs.push("--data-dir", resourcePath("pandoc/datadir")); + pandocArgs.push(...pandocDataDirArgs()); // add any built-in syntax definition files allDefaults[kSyntaxDefinitions] = allDefaults[kSyntaxDefinitions] || []; diff --git a/src/core/resources.ts b/src/core/resources.ts index 1e79964547d..e5c77917b3f 100644 --- a/src/core/resources.ts +++ b/src/core/resources.ts @@ -80,6 +80,14 @@ export function pandocBinaryPath(): string { return isWindows ? toolsPath("pandoc") : architectureToolsPath("pandoc"); } +// Args selecting Quarto's own pandoc data-dir (init.lua and friends under +// src/resources/pandoc/datadir), never the user's default pandoc data +// directory. Not every pandoc invocation uses these -- e.g. `quarto run` +// (src/core/run/lua.ts) intentionally omits --data-dir. +export function pandocDataDirArgs(): string[] { + return ["--data-dir", resourcePath("pandoc/datadir")]; +} + const _r_binary_path: Map = new Map(); export async function rBinaryPath( binary: string, diff --git a/tests/smoke/lua-unit/quarto-version-repr.test.ts b/tests/smoke/lua-unit/quarto-version-repr.test.ts index 317f4a594cb..25e9fe1acdf 100644 --- a/tests/smoke/lua-unit/quarto-version-repr.test.ts +++ b/tests/smoke/lua-unit/quarto-version-repr.test.ts @@ -13,7 +13,7 @@ * `quarto run` -- that invokes pandoc without --data-dir, so init.lua from * src/resources/pandoc/datadir/ (which defines quarto.version / * quarto.config.version()) never loads. Instead this invokes pandoc - * directly with --data-dir pointing at that directory, forcing the + * directly with pandocDataDirArgs() pointing at that directory, forcing the * quarto-version filter param via QUARTO_FILTER_PARAMS -- the same * mechanism a real quarto render uses (src/command/render/filters.ts, * src/command/render/pandoc.ts). @@ -23,7 +23,11 @@ import { encodeBase64 } from "encoding/base64"; import { fromFileUrl, join } from "../../../src/deno_ral/path.ts"; import { assert } from "testing/asserts"; import { execProcess } from "../../../src/core/process.ts"; -import { pandocBinaryPath, resourcePath } from "../../../src/core/resources.ts"; +import { + pandocBinaryPath, + pandocDataDirArgs, + resourcePath, +} from "../../../src/core/resources.ts"; import { unitTest } from "../../test.ts"; const testsDir = fromFileUrl(new URL("../../", import.meta.url)); @@ -67,8 +71,7 @@ for ( { cmd: pandocBinaryPath(), args: [ - "--data-dir", - resourcePath("pandoc/datadir"), + ...pandocDataDirArgs(), "--from", "markdown", "--to", From 1997f94c057a5e7dc5321697e4621602a6dfd960 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Wed, 16 Sep 2026 13:02:24 +0200 Subject: [PATCH 5/5] Add changelog entry for #14894 --- news/changelog-1.11.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/news/changelog-1.11.md b/news/changelog-1.11.md index 616dd6e53ae..cc8a9b8dcdf 100644 --- a/news/changelog-1.11.md +++ b/news/changelog-1.11.md @@ -35,6 +35,10 @@ All changes included in 1.11: - ([#14847](https://github.com/quarto-dev/quarto-cli/pull/14847)): Fix `toc_title` auto-fallback in typst outline template that was ignoring the computed fallback value when `toc_title` is `none`. +## Lua API + +- ([#14894](https://github.com/quarto-dev/quarto-cli/pull/14894)): Fix `quarto.version` and `quarto.config.version()` crashing filters with `table expected, got string` when the version string contains semver build metadata (such as a distro packager's revision suffix) or otherwise does not start with a digit. + ## Commands ### `call`