Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
cef0596
Add Chrome for Testing download utilities
cderv Feb 10, 2026
877323b
Add chrome-headless-shell InstallableTool implementation
cderv Feb 10, 2026
6808369
Wire chrome-headless-shell into tool registry, browser discovery, and…
cderv Feb 12, 2026
2254b54
Fix chromium branch in quarto check to await async methods
cderv Feb 12, 2026
d58e156
Guard chromium version output against undefined in quarto check
cderv Feb 12, 2026
37949f8
Add smoke tests for Chrome-based diagram rendering and update CI
cderv Feb 12, 2026
f112028
Verify diagram screenshots in smoke tests, not just error-free render
cderv Feb 12, 2026
cd52e21
Add to changelog
cderv Feb 12, 2026
c100f3c
Change priority order for chromium to use
cderv Feb 12, 2026
efac62a
Add one more related issue to changelog
cderv Feb 12, 2026
fa467da
Correctly handle non existant path in `QUARTO_CHROMIUM`
cderv Feb 12, 2026
aa4b99d
Refactor `chromeCb` in check.ts to separate detection from formatting
cderv Feb 12, 2026
5bde81c
Use registered tool list for update/remove shortcut resolution
cderv Feb 13, 2026
4289d71
Token is not needed in CI
cderv Feb 13, 2026
0218dbf
Update CLI help text to document chrome-headless-shell tool
cderv Feb 13, 2026
b0da17c
Extract shared `detectBrowser()` and bound `findCftExecutable` walk
cderv Feb 13, 2026
5a76ee8
Fix CLI help text consistency and case-insensitive tool routing
cderv Feb 13, 2026
4a25207
Fix `quarto tools` not listing chrome-headless-shell
cderv Feb 13, 2026
e51091e
Use detected platform in CfT test directory names
cderv Feb 13, 2026
4e05aeb
Fix install/uninstall descriptions to not mention extensions
cderv Feb 13, 2026
5fd074a
Clean uninstall to not show remove example
cderv Feb 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/test-smokes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,9 @@ jobs:
id: cache-typst
uses: ./.github/actions/cache-typst

- name: Install Chrome
uses: browser-actions/setup-chrome@latest
- name: Install Chrome Headless Shell
run: |
quarto install chrome-headless-shell --no-prompt
- name: Setup Julia
uses: julia-actions/setup-julia@v2
Expand Down
3 changes: 2 additions & 1 deletion news/changelog-1.9.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,10 @@ All changes included in 1.9:

- (): New `quarto call build-ts-extension` command builds a TypeScript extension, such as an engine extension, and places the artifacts in the `_extensions` directory. See the [engine extension pre-release documentation](https://prerelease.quarto.org/docs/extensions/engine.html) for details.

### `install verapdf`
### `install`

- ([#4426](https://github.com/quarto-dev/quarto-cli/issues/4426)): New `quarto install verapdf` command installs [veraPDF](https://verapdf.org/) for PDF/A and PDF/UA validation. When verapdf is available, PDFs created with the `pdf-standard` option are automatically validated for compliance. Also supports `quarto uninstall verapdf`, `quarto update verapdf`, and `quarto tools`.
- ([#11877](https://github.com/quarto-dev/quarto-cli/issues/11877), [#10961](https://github.com/quarto-dev/quarto-cli/issues/10961), [#6821](https://github.com/quarto-dev/quarto-cli/issues/6821), [#13704](https://github.com/quarto-dev/quarto-cli/issues/13704)): New `quarto install chrome-headless-shell` command downloads [Chrome Headless Shell](https://developer.chrome.com/blog/chrome-headless-shell) from Google's Chrome for Testing API. This is the recommended headless browser for diagram rendering (Mermaid, Graphviz) to non-HTML formats. Smaller and lighter than full Chrome, with fewer system dependencies.

### `preview`

Expand Down
95 changes: 66 additions & 29 deletions src/command/check/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { pandocBinaryPath } from "../../core/resources.ts";
import { lines } from "../../core/text.ts";
import { satisfies } from "semver/mod.ts";
import { dartCommand } from "../../core/dart-sass.ts";
import { allTools } from "../../tools/tools.ts";
import { allTools, installableTool } from "../../tools/tools.ts";
import { texLiveContext, tlVersion } from "../render/latexmk/texlive.ts";
import { which } from "../../core/path.ts";
import { dirname } from "../../deno_ral/path.ts";
Expand All @@ -31,7 +31,7 @@ import { typstBinaryPath } from "../../core/typst.ts";
import { quartoCacheDir } from "../../core/appdirs.ts";
import { isWindows } from "../../deno_ral/platform.ts";
import { makeStringEnumTypeEnforcer } from "../../typing/dynamic.ts";
import { findChrome } from "../../core/puppeteer.ts";
import { detectBrowser } from "../../core/puppeteer.ts";
import { executionEngines } from "../../execute/engine.ts";

export function getTargets(): readonly string[] {
Expand Down Expand Up @@ -436,35 +436,28 @@ async function checkInstall(conf: CheckConfiguration) {
conf.jsonResult.chrome = chromeJson;
}
const chromeCb = async () => {
const chromeDetected = await findChrome();
const chromiumQuarto = tools.installed.find((tool) =>
tool.name === "chromium"
);
if (chromeDetected.path !== undefined) {
chromeHeadlessOutput.push(`${kIndent}Using: Chrome found on system`);
chromeHeadlessOutput.push(
`${kIndent}Path: ${chromeDetected.path}`,
);
if (chromeDetected.source) {
chromeHeadlessOutput.push(`${kIndent}Source: ${chromeDetected.source}`);
const check = await detectChromeForCheck();

if (check.warning) {
chromeHeadlessOutput.push(`${kIndent}NOTE: ${check.warning}`);
chromeJson["warning"] = check.warning;
}

if (check.detected) {
const { label, path, source, displaySource, version } = check.detected;
chromeHeadlessOutput.push(`${kIndent}Using: ${label}`);
if (path) {
chromeHeadlessOutput.push(`${kIndent}Path: ${path}`);
chromeJson["path"] = path;
}
chromeJson["path"] = chromeDetected.path;
chromeJson["source"] = chromeDetected.source;
} else if (chromiumQuarto !== undefined) {
chromeJson["source"] = "quarto";
chromeHeadlessOutput.push(
`${kIndent}Using: Chromium installed by Quarto`,
);
if (chromiumQuarto?.binDir) {
chromeHeadlessOutput.push(
`${kIndent}Path: ${chromiumQuarto?.binDir}`,
);
chromeJson["path"] = chromiumQuarto?.binDir;
chromeJson["source"] = source;
if (displaySource) {
chromeHeadlessOutput.push(`${kIndent}Source: ${displaySource}`);
}
if (version) {
chromeHeadlessOutput.push(`${kIndent}Version: ${version}`);
chromeJson["version"] = version;
}
chromeHeadlessOutput.push(
`${kIndent}Version: ${chromiumQuarto.installedVersion}`,
);
chromeJson["version"] = chromiumQuarto.installedVersion;
} else {
chromeHeadlessOutput.push(`${kIndent}Chrome: (not detected)`);
chromeJson["installed"] = false;
Expand Down Expand Up @@ -524,3 +517,47 @@ title: "Title"
}, markdownRenderCb);
}
}

interface ChromeDetectionResult {
label: string;
path?: string;
source: string;
version?: string;
displaySource?: string;
}

interface ChromeCheckInfo {
warning?: string;
detected?: ChromeDetectionResult;
}

async function detectChromeForCheck(): Promise<ChromeCheckInfo> {
const detection = await detectBrowser();

if (detection.detected) {
return { detected: detection.detected };
}

const result: ChromeCheckInfo = {};
if (detection.warning) {
result.warning = detection.warning;
}

// Legacy: chromium installed by Quarto
const chromiumTool = installableTool("chromium");
if (chromiumTool && await chromiumTool.installed()) {
let path: string | undefined;
if (chromiumTool.binDir) {
path = await chromiumTool.binDir();
}
const version = await chromiumTool.installedVersion();
result.detected = {
label: "Chromium installed by Quarto",
path,
source: "quarto",
version,
};
}

return result;
}
10 changes: 7 additions & 3 deletions src/command/install/cmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
selectTool,
updateOrInstallTool,
} from "../../tools/tools-console.ts";
import { installTool } from "../../tools/tools.ts";
import { installableToolNames, installTool } from "../../tools/tools.ts";
import { resolveCompatibleArgs } from "../remove/cmd.ts";

export const installCommand = new Command()
Expand All @@ -36,14 +36,18 @@ export const installCommand = new Command()
"Update system path when a tool is installed",
)
.description(
"Installs a global dependency (TinyTex or Chromium).",
`Installs a global dependency (${installableToolNames().join(", ")}).`,
)
.example(
"Install TinyTeX",
"quarto install tinytex",
)
.example(
"Install Chromium",
"Install Chrome Headless Shell",
"quarto install chrome-headless-shell",
)
.example(
"Install Chromium (legacy)",
"quarto install chromium",
)
.example(
Expand Down
3 changes: 2 additions & 1 deletion src/command/remove/cmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
removeTool,
selectTool,
} from "../../tools/tools-console.ts";
import { installableTools } from "../../tools/tools.ts";
import { notebookContext } from "../../render/notebook/notebook-context.ts";
import { signalCommandFailure } from "../utils.ts";

Expand Down Expand Up @@ -172,7 +173,7 @@ export const resolveCompatibleArgs = (
return {
action: "extension",
};
} else if (extname === "tinytex" || extname === "chromium") {
} else if (installableTools().includes(extname.toLowerCase())) {
return {
action: "tool",
name: args[0],
Expand Down
15 changes: 12 additions & 3 deletions src/command/uninstall/cmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
removeTool,
selectTool,
} from "../../tools/tools-console.ts";
import { installableToolNames } from "../../tools/tools.ts";

export const uninstallCommand = new Command()
.name("uninstall")
Expand All @@ -26,11 +27,19 @@ export const uninstallCommand = new Command()
"Update system path when a tool is installed",
)
.description(
"Removes an extension.",
`Uninstalls a global dependency (${installableToolNames().join(", ")}).`,
)
.example(
"Remove extension using name",
"quarto remove <extension-name>",
"Uninstall TinyTeX",
"quarto uninstall tinytex",
)
.example(
"Uninstall Chrome Headless Shell",
"quarto uninstall chrome-headless-shell",
)
.example(
"Uninstall Chromium (legacy)",
"quarto uninstall chromium",
)
.action(
async (
Expand Down
9 changes: 7 additions & 2 deletions src/command/update/cmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
selectTool,
updateOrInstallTool,
} from "../../tools/tools-console.ts";
import { installableToolNames } from "../../tools/tools.ts";
import { resolveCompatibleArgs } from "../remove/cmd.ts";

export const updateCommand = new Command()
Expand All @@ -28,7 +29,7 @@ export const updateCommand = new Command()
"Embed this extension within another extension (used when authoring extensions).",
)
.description(
"Updates an extension or global dependency.",
`Updates an extension or global dependency (${installableToolNames().join(", ")}).`,
)
.example(
"Update extension (Github)",
Expand All @@ -47,7 +48,11 @@ export const updateCommand = new Command()
"quarto update tool tinytex",
)
.example(
"Update Chromium",
"Update Chrome Headless Shell",
"quarto update tool chrome-headless-shell",
)
.example(
"Update Chromium (legacy)",
"quarto update tool chromium",
)
.example(
Expand Down
Loading
Loading