diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c2ecef6e023..82f921c982c 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -16,9 +16,9 @@ jobs:
- name: dotnet publish
run: |
- dotnet publish src/docfx -f net8.0 -c Release /p:Version=${GITHUB_REF_NAME#v} --self-contained -r win-x64 -o drop/publish/win-x64 /p:PlaywrightPlatform=win
- dotnet publish src/docfx -f net8.0 -c Release /p:Version=${GITHUB_REF_NAME#v} --self-contained -r linux-x64 -o drop/publish/linux-x64 /p:PlaywrightPlatform=linux
- dotnet publish src/docfx -f net8.0 -c Release /p:Version=${GITHUB_REF_NAME#v} --self-contained -r osx-x64 -o drop/publish/osx-x64 /p:PlaywrightPlatform=osx
+ dotnet publish src/docfx -f net8.0 -c Release /p:Version=${GITHUB_REF_NAME#v} --self-contained -r win-x64 -o drop/publish/win-x64
+ dotnet publish src/docfx -f net8.0 -c Release /p:Version=${GITHUB_REF_NAME#v} --self-contained -r linux-x64 -o drop/publish/linux-x64
+ dotnet publish src/docfx -f net8.0 -c Release /p:Version=${GITHUB_REF_NAME#v} --self-contained -r osx-x64 -o drop/publish/osx-x64
mkdir -p drop/bin
- run: zip -r ../../bin/docfx-win-x64-${GITHUB_REF_NAME}.zip .
diff --git a/Directory.Build.props b/Directory.Build.props
index cddf0d2c372..46f3e4a1dc1 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -59,4 +59,12 @@
+
+
+
+
+
+
+
+
diff --git a/docs/reference/docfx-environment-variables-reference.md b/docs/reference/docfx-environment-variables-reference.md
index e48301347d1..ad8ed1945bc 100644
--- a/docs/reference/docfx-environment-variables-reference.md
+++ b/docs/reference/docfx-environment-variables-reference.md
@@ -26,4 +26,9 @@ This setting is intended to be used on offline environment.
## `DOCFX_PDF_TIMEOUT`
-Maximum time in milliseconds to override the default [Playwright timeout](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) for PDF generation.
\ No newline at end of file
+Maximum time in milliseconds to override the default [Playwright timeout](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) for PDF generation.
+
+## `PLAYWRIGHT_NODEJS_PATH`
+
+Custom Node.js executable path that will be used by the `docfx pdf' command.
+By default, docfx automatically detect installed Node.js from `PATH`.
diff --git a/src/Docfx.App/Docfx.App.csproj b/src/Docfx.App/Docfx.App.csproj
index 8a0ae58e539..609196c0de2 100644
--- a/src/Docfx.App/Docfx.App.csproj
+++ b/src/Docfx.App/Docfx.App.csproj
@@ -13,6 +13,7 @@
+
diff --git a/src/Docfx.App/Helpers/PlaywrightHelper.cs b/src/Docfx.App/Helpers/PlaywrightHelper.cs
new file mode 100644
index 00000000000..57e08f25112
--- /dev/null
+++ b/src/Docfx.App/Helpers/PlaywrightHelper.cs
@@ -0,0 +1,80 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.InteropServices;
+using Docfx.Common;
+using Docfx.Exceptions;
+
+#nullable enable
+
+namespace Docfx;
+
+internal static class PlaywrightHelper
+{
+ public static void EnsurePlaywrightNodeJsPath()
+ {
+ // Skip if playwright environment variable exists.
+ if (Environment.GetEnvironmentVariable("PLAYWRIGHT_DRIVER_SEARCH_PATH") != null)
+ return;
+
+ if (Environment.GetEnvironmentVariable("PLAYWRIGHT_NODEJS_PATH") != null)
+ return;
+
+ if (!TryFindNodeExecutable(out var exePath, out var nodeVersion))
+ {
+ throw new DocfxException("Node.js executable is not found. Try to install Node.js or set the `PLAYWRIGHT_NODEJS_PATH` environment variable.");
+ }
+
+ Logger.LogInfo($"Using Node.js {nodeVersion} executable.");
+ Logger.LogVerbose($"Path: {exePath}");
+
+ Environment.SetEnvironmentVariable("PLAYWRIGHT_NODEJS_PATH", exePath, EnvironmentVariableTarget.Process);
+ }
+
+ private static bool TryFindNodeExecutable(out string exePath, out string nodeVersion)
+ {
+ // Find Node.js executable installation path from PATHs.
+ string exeName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "node.exe" : "node";
+
+ var pathEnv = Environment.GetEnvironmentVariable("PATH");
+ if (pathEnv == null)
+ throw new DocfxException("Failed to get `PATH` environment variable.");
+
+ var paths = pathEnv.Split(Path.PathSeparator);
+ foreach (var path in paths)
+ {
+ string fullPath = Path.GetFullPath(Path.Combine(path, exeName));
+
+ if (File.Exists(fullPath))
+ {
+ exePath = fullPath;
+ nodeVersion = GetNodeVersion(exePath);
+ return true;
+ }
+ }
+
+ exePath = "";
+ nodeVersion = "";
+ return false;
+ }
+
+ ///
+ /// Returns `node --version` command result
+ ///
+ private static string GetNodeVersion(string exePath)
+ {
+ using var memoryStream = new MemoryStream();
+ using var stdoutWriter = new StreamWriter(memoryStream);
+
+ var exitCode = CommandUtility.RunCommand(new CommandInfo { Name = exePath, Arguments = "--version" }, stdoutWriter);
+
+ if (exitCode != 0)
+ return "";
+
+ stdoutWriter.Flush();
+ memoryStream.Position = 0;
+
+ using var streamReader = new StreamReader(memoryStream);
+ return streamReader.ReadLine() ?? "";
+ }
+}
diff --git a/src/Docfx.App/PdfBuilder.cs b/src/Docfx.App/PdfBuilder.cs
index 25b7e225467..0b8240a2ef3 100644
--- a/src/Docfx.App/PdfBuilder.cs
+++ b/src/Docfx.App/PdfBuilder.cs
@@ -47,6 +47,11 @@ class Outline
public string? pdfFooterTemplate { get; init; }
}
+ static PdfBuilder()
+ {
+ PlaywrightHelper.EnsurePlaywrightNodeJsPath();
+ }
+
public static Task Run(BuildJsonConfig config, string configDirectory, string? outputDirectory = null)
{
var outputFolder = Path.GetFullPath(Path.Combine(
diff --git a/test/docfx.Snapshot.Tests/PercyTest.cs b/test/docfx.Snapshot.Tests/PercyTest.cs
index 59a2b58261c..7f28fc88e8e 100644
--- a/test/docfx.Snapshot.Tests/PercyTest.cs
+++ b/test/docfx.Snapshot.Tests/PercyTest.cs
@@ -38,6 +38,7 @@ public PercyFactAttribute()
static PercyTest()
{
+ PlaywrightHelper.EnsurePlaywrightNodeJsPath();
Microsoft.Playwright.Program.Main(["install", "chromium"]);
}