| // Copyright (C) 2007-2025 Apple Inc. All rights reserved. |
| |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions |
| // are met: |
| // 1. Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // 2. Redistributions in binary form must reproduce the above copyright |
| // notice, this list of conditions and the following disclaimer in the |
| // documentation and/or other materials provided with the distribution. |
| |
| // THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| // BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| // THE POSSIBILITY OF SUCH DAMAGE. |
| |
| import core from "@actions/core"; |
| import { spawn } from "child_process"; |
| import commandLineUsage from "command-line-usage"; |
| import { styleText } from "node:util"; |
| |
| export const GITHUB_ACTIONS_OUTPUT = ("GITHUB_ACTIONS_OUTPUT" in process.env) || ("GITHUB_EVENT_PATH" in process.env); |
| |
| export function logInfo(...args) { |
| const text = args.join(" ") |
| if (GITHUB_ACTIONS_OUTPUT) |
| core.info(styleText("yellow", text)); |
| else |
| console.log(styleText("yellow", text)); |
| } |
| |
| export function logError(...args) { |
| let error; |
| if (args.length == 1 && args[0] instanceof Error) |
| error = args[0]; |
| const text = args.join(" "); |
| if (GITHUB_ACTIONS_OUTPUT) { |
| if (error?.stack) |
| core.error(error.stack); |
| else |
| core.error(styleText("red", text)); |
| } else { |
| if (error?.stack) |
| console.error(styleText("red", error.stack)); |
| else |
| console.error(styleText("red", text)); |
| } |
| } |
| |
| export function logCommand(...args) { |
| const cmd = args.join(" "); |
| if (GITHUB_ACTIONS_OUTPUT) { |
| core.notice(styleText("blue", cmd)); |
| } else { |
| console.log(styleText("blue", cmd)); |
| } |
| } |
| |
| |
| export async function logGroup(name, body) { |
| if (GITHUB_ACTIONS_OUTPUT) { |
| core.startGroup(name); |
| } else { |
| logInfo("=".repeat(80)); |
| logInfo(name); |
| logInfo(".".repeat(80)); |
| } |
| try { |
| return await body(); |
| } finally { |
| if (GITHUB_ACTIONS_OUTPUT) |
| core.endGroup(); |
| } |
| } |
| |
| |
| export function printHelp(message = "", optionDefinitions) { |
| const usage = commandLineUsage([ |
| { |
| header: "Run all tests", |
| }, |
| { |
| header: "Options", |
| optionList: optionDefinitions, |
| }, |
| ]); |
| if (!message) { |
| console.log(usage); |
| process.exit(0); |
| } else { |
| console.error(message); |
| console.error(); |
| console.error(usage); |
| process.exit(1); |
| } |
| } |
| |
| |
| export async function runTest(label, testFunction) { |
| try { |
| await logGroup(label, testFunction); |
| logInfo("✅ Test completed!"); |
| } catch(e) { |
| logError("❌ Test failed!"); |
| logError(e); |
| return false; |
| } |
| return true; |
| } |
| |
| |
| export async function sh(binary, ...args) { |
| const cmd = `${binary} ${args.join(" ")}`; |
| if (GITHUB_ACTIONS_OUTPUT) |
| core.startGroup(binary); |
| logCommand(cmd); |
| try { |
| return await spawnCaptureStdout(binary, args); |
| } catch(e) { |
| logError(e.stdoutString); |
| throw e; |
| } finally { |
| if (GITHUB_ACTIONS_OUTPUT) |
| core.endGroup(); |
| } |
| } |
| |
| const SPAWN_OPTIONS = Object.freeze({ |
| stdio: ["inherit", "pipe", "inherit"] |
| }); |
| |
| async function spawnCaptureStdout(binary, args, options={}) { |
| options = Object.assign(options, SPAWN_OPTIONS); |
| const childProcess = spawn(binary, args, options); |
| childProcess.stdout.pipe(process.stdout); |
| return new Promise((resolve, reject) => { |
| childProcess.stdoutString = ""; |
| childProcess.stdio[1].on("data", (data) => { |
| childProcess.stdoutString += data.toString(); |
| }); |
| childProcess.on("close", (code) => { |
| if (code === 0) { |
| resolve(childProcess); |
| } else { |
| // Reject the Promise with an Error on failure |
| const error = new Error(`Command failed with exit code ${code}: ${binary} ${args.join(" ")}`); |
| error.process = childProcess; |
| error.stdout = childProcess.stdoutString; |
| error.exitCode = code; |
| reject(error); |
| } |
| }); |
| childProcess.on("error", reject); |
| }) |
| } |