From 1476b4a3de3ea463be0333909b10129f6214b0e2 Mon Sep 17 00:00:00 2001 From: Guofang Date: Mon, 2 Nov 2026 20:53:50 +0800 Subject: [PATCH] Prompt step by step for the missing base image, ISO, npm deps and Chromium Offer to build the base QCOW2, download the ISO, run npm ci and install Chromium (from the npmmirror mirror by default). Let Playwright locate its own Chromium so macOS works. Co-Authored-By: Claude Sonnet 5.5 --- build-windows-base.sh | 13 +++++++++ download-images.sh | 54 +++++++++++++++++++++++++++++++++-- nodejs/get-windows-iso-url.js | 23 ++++++++------- 3 files changed, 77 insertions(+), 13 deletions(-) diff --git a/build-windows-base.sh b/build-windows-base.sh index 4288708..a567444 100755 --- a/build-windows-base.sh +++ b/build-windows-base.sh @@ -31,6 +31,8 @@ set -euo pipefail images_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" downloader="${images_dir}/download-images.sh" +# Path lookups below must not offer to run this script again. +export VM_IMAGE_NO_PROMPT=1 win_dir="${images_dir}/windows11" usage() { @@ -116,6 +118,17 @@ if ((!from_img)); then || die "install socat or nc (it presses a key at the boot prompt of the ISO)." fi +# Without the ISO, offer to download it (needs a terminal). +if [[ -z "${WIN11_VM_INSTALL_ISO:-}" ]] && ! "$downloader" --print-path windows11 --iso >/dev/null 2>&1; then + "$downloader" --print-path windows11 --iso >&2 || true + if { : /dev/tty; } 2>/dev/null; then + printf 'Download the Windows 11 ISO now? [y/N] ' >/dev/tty + read -r answer /dev/tty; } 2>/dev/null || return 1 + printf '%s [y/N] ' "$1" >/dev/tty + read -r answer /dev/null || true + if [[ -z "${VM_IMAGE_NO_PROMPT:-}" && -x "$builder" ]] && ask_yes_no "Build the Windows 11 base image now with build-windows-base.sh?"; then + # stdout carries the path, so the build's output goes to the terminal. + "$builder" >/dev/tty 2>&1 /dev/null +} + print_image_path() { local target="$1" local media="${2:-qcow2}" @@ -171,7 +201,7 @@ print_image_path() { if [[ "$media" == iso ]]; then windows_iso_path else - windows_qcow2_path + windows_base_path fi ;; *) @@ -416,7 +446,27 @@ download_windows_iso() { printf 'Error: node is required to get the Windows 11 ISO URL.\n' >&2 exit 1 fi - url="$(node "${images_dir}/nodejs/get-windows-iso-url.js" --arch "$windows_arch")" + if [[ ! -d "${images_dir}/nodejs/node_modules/playwright-core" ]]; then + printf 'Error: the Node.js dependencies are not installed (npm ci has not been run in %s/nodejs).\n' \ + "$images_dir" >&2 + command -v npm >/dev/null 2>&1 || { printf 'Error: npm is required; install Node.js with npm.\n' >&2; exit 1; } + ask_yes_no "Run npm ci in ${images_dir}/nodejs now?" || exit 1 + (cd "${images_dir}/nodejs" && npm ci) || exit 1 + fi + local status=0 + url="$(node "${images_dir}/nodejs/get-windows-iso-url.js" --arch "$windows_arch")" || status=$? + if ((status == 2)) && [[ -z "${CHROME_PATH:-}" ]]; then + # Exit status 2 means Chromium was not found (the script has said so). + ask_yes_no "Install Chromium with playwright-core now?" || exit 1 + # Default to the npmmirror mirror (the official CDN is slow in China); set + # PLAYWRIGHT_DOWNLOAD_HOST to override it. + (cd "${images_dir}/nodejs" \ + && PLAYWRIGHT_DOWNLOAD_HOST="${PLAYWRIGHT_DOWNLOAD_HOST:-https://cdn.npmmirror.com/binaries/playwright}" \ + npx playwright-core install chromium) || exit 1 + url="$(node "${images_dir}/nodejs/get-windows-iso-url.js" --arch "$windows_arch")" || exit 1 + elif ((status != 0)); then + exit "$status" + fi [[ -n "$url" ]] || { printf 'Error: no Windows 11 ISO URL.\n' >&2; exit 1; } name="${url%%\?*}" name="${name##*/}" diff --git a/nodejs/get-windows-iso-url.js b/nodejs/get-windows-iso-url.js index d69e280..807eb16 100755 --- a/nodejs/get-windows-iso-url.js +++ b/nodejs/get-windows-iso-url.js @@ -5,12 +5,11 @@ // Usage: node get-windows-iso-url.js [--arch x64|arm64] [--lang NAME] // Environment: // PLAYWRIGHT_CORE playwright-core directory (default: node_modules here) -// CHROME_PATH Chromium executable (default: newest one in ~/.cache/ms-playwright) +// CHROME_PATH Chromium executable (default: the one Playwright installed) // The URL expires after about 24 hours and may be bound to the IP that // requested it. Only the URL goes to stdout; logs go to stderr. const fs = require('fs'); -const os = require('os'); const path = require('path'); const args = process.argv.slice(2); @@ -28,8 +27,7 @@ Options: Environment: PLAYWRIGHT_CORE playwright-core directory (default: node_modules here) - CHROME_PATH Chromium executable (default: newest one in - ~/.cache/ms-playwright) + CHROME_PATH Chromium executable (default: the one Playwright installed) Examples: node get-windows-iso-url.js --arch x64 --lang "Chinese Simplified" @@ -60,17 +58,20 @@ function findPlaywright() { process.exit(2); } -function findChrome() { - if (process.env.CHROME_PATH) return process.env.CHROME_PATH; - const root = path.join(os.homedir(), '.cache/ms-playwright'); - const dirs = fs.existsSync(root) ? fs.readdirSync(root).filter(d => /^chromium-\d+$/.test(d)).sort() : []; - if (!dirs.length) { console.error('Chromium not found: set CHROME_PATH or run npx playwright-core install chromium'); process.exit(2); } - return path.join(root, dirs.pop(), 'chrome-linux64/chrome'); +// CHROME_PATH wins; otherwise Playwright picks the Chromium it installed (in +// its own per-OS cache, PLAYWRIGHT_BROWSERS_PATH if set). +function launchOptions(chromium) { + if (process.env.CHROME_PATH) return { executablePath: process.env.CHROME_PATH, headless: true }; + if (!fs.existsSync(chromium.executablePath())) { + console.error('Chromium not found: set CHROME_PATH or run npx playwright-core install chromium'); + process.exit(2); + } + return { headless: true }; } (async () => { const { chromium } = require(findPlaywright()); - const browser = await chromium.launch({ executablePath: findChrome(), headless: true }); + const browser = await chromium.launch(launchOptions(chromium)); try { const ctx = await browser.newContext({ locale: 'en-US',