From c89fe8a388da3ddfc8fbb7d2a3d6b653403791bb Mon Sep 17 00:00:00 2001 From: Harrison Downs Date: Thu, 9 Oct 2025 15:12:04 -0400 Subject: [PATCH 1/2] Add install action for macOS. Adds an install action for macOS. The start/stop actions will be in a followup, once we can test them on VMs. --- .github/workflows/pull_request_validation.yml | 15 ++ README.md | 8 +- action.yml | 13 +- src/mac/Install-DashcamCLI.sh | 81 +++++++++ src/mac/Install-DashcamGUI.sh | 144 ++++++++++++++++ src/mac/Install-Node.sh | 162 ++++++++++++++++++ start/action.yml | 3 +- stop/action.yml | 3 +- 8 files changed, 418 insertions(+), 11 deletions(-) create mode 100755 src/mac/Install-DashcamCLI.sh create mode 100755 src/mac/Install-DashcamGUI.sh create mode 100755 src/mac/Install-Node.sh diff --git a/.github/workflows/pull_request_validation.yml b/.github/workflows/pull_request_validation.yml index dc7823c..db30196 100644 --- a/.github/workflows/pull_request_validation.yml +++ b/.github/workflows/pull_request_validation.yml @@ -36,3 +36,18 @@ jobs: continue-on-error: true with: project-id: "507f1f77bcf86cd799439011" + validate_macos: + runs-on: macos-latest + steps: + - name: Checkout code + uses: actions/checkout@v4.2.2 + + - name: Install Dashcam - MacOS + uses: ./. + with: + version: "1.0.49" + cli-version: "0.8.3" + node-version: "22.19.0" + node-directory: "${{ runner.tmp }}/nodejs22" + node-prefix: "${{ runner.tmp }}/nodejs22/npm-installs" + diff --git a/README.md b/README.md index 9fea735..2271dfe 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,14 @@ GitHub actions for TestDriver Dashcam service # Usage +> **_NOTE:_** These actions are currently only supported on Windows runners. + First step must be to install the Dashcam CLI/GUI on the runner. The GUI is required for the CLI to function correctly. ```yaml - name: Install Dashcam id: install_dashcam continue-on-error: true # Optional - uses: thebrowsercompany/action-dashcam@ + uses: testdriverai/action-dashcam@ with: version: "1.0.49" # See releases here: https://github.com/replayableio/replayable/releases ``` @@ -19,7 +21,7 @@ Right before the step that you want to record you need to Start the Dashcam. id: start_dashcam if: ${{ steps.install_dashcam.outcome == 'success' }} # Only needed if continue-on-error is used on the install_dashcam continue-on-error: true - uses: thebrowsercompany/action-dashcam/start@ + uses: testdriverai/action-dashcam/start@ with: api-key: ${{ secrets.DASHCAM_API_KEY }} ``` @@ -29,7 +31,7 @@ To stop the recording and upload the results use: - name: Submit Dashcam recording if: ${{ always() && steps.start_dashcam.outcome == 'success' }} continue-on-error: true - uses: thebrowsercompany/action-dashcam/stop@dev + uses: testdriverai/action-dashcam/stop@dev with: project-id: "507f1f77bcf86cd799439011" # The project-id value is the 'slug' component of the project URL on the Dashcam website. ``` diff --git a/action.yml b/action.yml index 00a1e8a..8cf05be 100644 --- a/action.yml +++ b/action.yml @@ -65,24 +65,25 @@ runs: shell: bash run: | echo "Installing Dashcam GUI on ${{ runner.os }}" - echo "Not supported yet" - exit 1 + bash "$GITHUB_ACTION_PATH/src/mac/Install-DashcamGUI.sh" --version "${{ inputs.version }}" + echo "Dashcam GUI installed" - name: Install Node.js for Dashcam CLI (macOS) if: ${{ runner.os == 'macOS' }} shell: bash run: | echo "Installing Node.js for Dashcam CLI on ${{ runner.os }}" - echo "Not supported yet" - exit 1 + bash "$GITHUB_ACTION_PATH/src/mac/Install-Node.sh" --version "${{ inputs.node-version }}" --directory "${{ inputs.node-directory }}" --prefix "${{ inputs.node-prefix }}" + echo "Node.js installed to ${{ inputs.node-directory }}" - name: Install Dashcam CLI (macOS) if: ${{ runner.os == 'macOS' }} shell: bash run: | echo "Installing Dashcam CLI on ${{ runner.os }}" - echo "Not supported yet" - exit 1 + bash "$GITHUB_ACTION_PATH/src/mac/Install-DashcamCLI.sh" --version "${{ inputs.cli-version }}" + echo "Dashcam CLI installed" + echo "DASHCAM_NODE_DIR=${{ inputs.node-directory }}" >> $GITHUB_ENV - name: Install Dashcam GUI (Linux) if: ${{ runner.os == 'Linux' }} diff --git a/src/mac/Install-DashcamCLI.sh b/src/mac/Install-DashcamCLI.sh new file mode 100755 index 0000000..f93dd41 --- /dev/null +++ b/src/mac/Install-DashcamCLI.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Install-DashcamCLI.sh +# Usage: Install-DashcamCLI.sh --version + +usage() { + cat <&2; usage; exit 1;; + esac +done + +if [ -z "$VERSION" ]; then + echo "--version is required" >&2 + usage + exit 1 +fi + +echo "Installing Dashcam CLI $VERSION..." + +# Verify npm is available +if ! command -v npm >/dev/null 2>&1; then + echo "npm not found in PATH. Make sure Node.js/npm is installed and on PATH." >&2 + exit 2 +fi + +echo "Using npm: $(npm --version)" + +# Try installing without sudo first. If it fails due to permission, retry with sudo. +if npm install --location=global "dashcam@$VERSION"; then + echo "dashcam installed successfully (no sudo)" +else + echo "Initial npm install failed; retrying with sudo..." + if sudo npm install --location=global "dashcam@$VERSION"; then + echo "dashcam installed successfully (with sudo)" + else + echo "Failed to install dashcam globally via npm." >&2 + exit 3 + fi +fi + +echo "Verifying Dashcam CLI Install..." + +if ! command -v dashcam >/dev/null 2>&1; then + echo "dashcam binary not found in PATH." >&2 + echo "If you set a custom npm prefix, ensure its bin/ directory is on PATH. Example:" + echo " export PATH=\"\${NODE_PREFIX:-/usr/local}/bin:\$PATH\"" + echo "You can also run: npm bin -g to find the global bin directory." + exit 4 +fi + +dashcam --help || { echo "dashcam --help failed" >&2; exit 5; } +dashcam --version || { echo "dashcam --version failed" >&2; exit 6; } + +echo "Dashcam CLI installed and verified." + +exit 0 diff --git a/src/mac/Install-DashcamGUI.sh b/src/mac/Install-DashcamGUI.sh new file mode 100755 index 0000000..adc7283 --- /dev/null +++ b/src/mac/Install-DashcamGUI.sh @@ -0,0 +1,144 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Install-DashcamGUI.sh +# Usage: Install-DashcamGUI.sh --version + +usage() { + cat <&2; usage; exit 1;; + esac +done + +if [ -z "$VERSION" ]; then + echo "--version is required" >&2 + usage + exit 1 +fi + +echo "Installing Dashcam GUI version $VERSION for macOS" + +INSTALL_DIR="$(mktemp -d -t dashcam-gui.XXXXXX)" +echo "Using temporary install directory: $INSTALL_DIR" + +# Download the arm64 zip file +ASSET_URL_BASE="https://github.com/replayableio/replayable/releases/download/v${VERSION}" +FILENAME="Dashcam-${VERSION}-arm64-mac.zip" +DEST="$INSTALL_DIR/$FILENAME" + +echo "Trying $ASSET_URL_BASE/$FILENAME" +if ! curl -fSL -o "$DEST" "$ASSET_URL_BASE/$FILENAME"; then + echo "Failed to download $FILENAME from $ASSET_URL_BASE" >&2 + exit 2 +fi + +DOWNLOADED="$DEST" + +if [ -z "$DOWNLOADED" ]; then + echo "Failed to find a Dashcam portable artifact for version $VERSION" >&2 + exit 2 +fi + +# Zip extraction +echo "Unzipping..." +unzip -q "$DOWNLOADED" -d "$INSTALL_DIR/extracted" +APP_PATH="$(find "$INSTALL_DIR/extracted" -maxdepth 3 -name "*.app" -print -quit || true)" + +if [ -z "$APP_PATH" ]; then + echo "Could not locate Dashcam .app after extraction." >&2 + exit 5 +fi + +echo "Found app at: $APP_PATH" + +# Install to /Applications +TARGET_APP="/Applications/$(basename "$APP_PATH")" +if [ -d "$TARGET_APP" ]; then + echo "Removing existing application at $TARGET_APP" + if [ -w "$TARGET_APP" ]; then + rm -rf "$TARGET_APP" + else + sudo rm -rf "$TARGET_APP" + fi +fi + +echo "Copying app to /Applications" +if [ -w "/Applications" ]; then + cp -R "$APP_PATH" "/Applications/" +else + sudo cp -R "$APP_PATH" "/Applications/" +fi + +# Create user_data directory and files similar to the Windows installer +USER_DATA_DIR="$INSTALL_DIR/user_data" +mkdir -p "$USER_DATA_DIR" + +CONFIG_FILES=( + ".replayable--mainwindow-ever-launched" + ".replayable--has-accepted-tos" + ".replayable--segment-done" + ".replayable--ever-launched" + ".replayable--do-not-update" + ".replayable--do-not-open-browser" +) + +for f in "${CONFIG_FILES[@]}"; do + touch "$USER_DATA_DIR/$f" +done + +cat > "$USER_DATA_DIR/settings.json" <<'JSON' +{ + "EDIT_ON_CLIP": false, + "CAPTURE_ERROR": false, + "DETECT_ERRORS_ONCE": true +} +JSON + +echo "Created user_data at $USER_DATA_DIR" + +# Launch the app +echo "Launching Dashcam.app from /Applications" +if open "/Applications/$(basename "$APP_PATH")"; then + echo "Dashcam started" +else + echo "Failed to launch Dashcam via open; attempting to run from installed bundle" + if [ -d "$TARGET_APP" ]; then + exec "${TARGET_APP}/Contents/MacOS/$(basename "${TARGET_APP%.*}")" & + else + echo "Cannot locate installed app to run." >&2 + exit 6 + fi +fi + +echo "Installation complete. Temporary files are in $INSTALL_DIR (will be removed)." + +# Note: we intentionally do not remove INSTALL_DIR right away so logs and user_data +# are available immediately after the script. Remove if desired. + +exit 0 diff --git a/src/mac/Install-Node.sh b/src/mac/Install-Node.sh new file mode 100755 index 0000000..2c7f155 --- /dev/null +++ b/src/mac/Install-Node.sh @@ -0,0 +1,162 @@ + +#!/usr/bin/env bash +set -euo pipefail + +# Install-Node.sh +# Usage: Install-Node.sh --version --directory --prefix +# Example: ./Install-Node.sh --version 16.20.0 --directory /usr/local/node-16 --prefix /usr/local + +usage() { + cat <&2; usage; exit 1;; + esac +done + +if [ -z "$VERSION" ] || [ -z "$DIRECTORY" ] || [ -z "$PREFIX" ]; then + echo "All of --version, --directory and --prefix are required." >&2 + usage + exit 1 +fi + +# Determine architecture for Node distributions +ARCH_RAW="$(uname -m)" +case "$ARCH_RAW" in + x86_64) ARCH="darwin-x64";; + arm64) ARCH="darwin-arm64";; + *) + echo "Unknown architecture '$ARCH_RAW' — defaulting to darwin-x64" >&2 + ARCH="darwin-x64";; +esac + +TARBALL="node-v${VERSION}-${ARCH}.tar.gz" +TARBALL_URL="https://nodejs.org/dist/v${VERSION}/${TARBALL}" +SHASUM_URL="https://nodejs.org/dist/v${VERSION}/SHASUMS256.txt" + +TMPDIR="$(mktemp -d -t install-node.XXXXXX)" +TARBALL_PATH="$TMPDIR/$TARBALL" +SHASUM_PATH="$TMPDIR/SHASUMS256.txt" + +echo "Downloading Node.js $VERSION for $ARCH..." +curl -fSL -o "$TARBALL_PATH" "$TARBALL_URL" +curl -fSL -o "$SHASUM_PATH" "$SHASUM_URL" + +echo "Verifying SHA256..." +# Extract expected sha from SHASUMS256.txt (match exact filename at line end) +EXPECTED_SHA256="$(awk -v file="$TARBALL" '$2 == file {print $1}' "$SHASUM_PATH" || true)" +if [ -z "$EXPECTED_SHA256" ]; then + echo "Failed to find expected SHA256 for $TARBALL in SHASUMS256.txt" >&2 + rm -rf "$TMPDIR" + exit 1 +fi + +ACTUAL_SHA256="$(shasum -a 256 "$TARBALL_PATH" | awk '{print $1}')" + +if [ "$EXPECTED_SHA256" != "$ACTUAL_SHA256" ]; then + echo "SHA256 mismatch for $TARBALL" >&2 + echo "Expected: $EXPECTED_SHA256" >&2 + echo "Actual: $ACTUAL_SHA256" >&2 + rm -rf "$TMPDIR" + exit 1 +fi + +echo "SHA256 verified. Installing to $DIRECTORY..." + +# Ensure directory exists (use sudo if necessary) +if [ -d "$DIRECTORY" ]; then + if [ ! -w "$DIRECTORY" ]; then + echo "Directory exists but is not writable, will use sudo when extracting." >&2 + NEED_SUDO=1 + else + NEED_SUDO=0 + fi +else + # Try to create it, fall back to sudo + if mkdir -p "$DIRECTORY" 2>/dev/null; then + NEED_SUDO=0 + else + echo "Creating $DIRECTORY requires elevated privileges; using sudo." >&2 + sudo mkdir -p "$DIRECTORY" + NEED_SUDO=1 + fi +fi + +# Extract tarball into DIRECTORY, stripping the top-level folder from the tarball +if [ "$NEED_SUDO" -eq 1 ]; then + sudo tar --numeric-owner --strip-components=1 -xzf "$TARBALL_PATH" -C "$DIRECTORY" +else + tar --strip-components=1 -xzf "$TARBALL_PATH" -C "$DIRECTORY" +fi + +# Ensure bin is in PATH for the remainder of this script +export PATH="$DIRECTORY/bin:$PATH" + +NPM_BIN="$DIRECTORY/bin/npm" +if [ ! -x "$NPM_BIN" ]; then + echo "npm executable not found at $NPM_BIN" >&2 + rm -rf "$TMPDIR" + exit 1 +fi + +echo "Configuring npm prefix to $PREFIX..." +if "$NPM_BIN" config set prefix "$PREFIX" 2>/dev/null; then + echo "npm prefix set successfully (without sudo)" +else + echo "npm prefix set requires elevated privileges; trying with sudo..." + sudo "$NPM_BIN" config set prefix "$PREFIX" +fi + +echo "NPM local configuration:" +"$NPM_BIN" config list || true +echo "NPM global configuration:" +if sudo -n true 2>/dev/null; then + sudo "$NPM_BIN" config list -g || true +else + # If sudo would prompt, run the command normally so user can authenticate + "$NPM_BIN" config list -g || true +fi + +echo "${DIRECTORY}/bin" >> $GITHUB_PATH +echo "${PREFIX}/bin" >> $GITHUB_PATH + +echo "Installation complete." +echo "Add $DIRECTORY/bin to your PATH to use this node install (for example: export PATH=$DIRECTORY/bin:\$PATH)" + +# Cleanup +rm -rf "$TMPDIR" + +exit 0 + diff --git a/start/action.yml b/start/action.yml index dd798a0..1e92266 100644 --- a/start/action.yml +++ b/start/action.yml @@ -16,7 +16,8 @@ inputs: runs: using: "composite" steps: - - name: Start Recording + - name: Start Recording (Windows) + if: ${{ runner.os == 'Windows' }} shell: pwsh env: API_KEY: ${{ inputs.api-key }} diff --git a/stop/action.yml b/stop/action.yml index 32242a3..b299351 100644 --- a/stop/action.yml +++ b/stop/action.yml @@ -9,7 +9,8 @@ inputs: runs: using: "composite" steps: - - name: Stop Recording + - name: Stop Recording (Windows) + if: ${{ runner.os == 'Windows' }} shell: pwsh env: PROJECT_ID: ${{ inputs.project-id }} From 431b13a33942a2a4077f0730b6bf889475ac8047 Mon Sep 17 00:00:00 2001 From: Harrison Downs Date: Wed, 15 Oct 2025 15:12:18 -0400 Subject: [PATCH 2/2] Fix runner.temp typo. The GitHub variable is runner.temp, not runner.tmp >_> --- .github/workflows/pull_request_validation.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request_validation.yml b/.github/workflows/pull_request_validation.yml index db30196..21f8147 100644 --- a/.github/workflows/pull_request_validation.yml +++ b/.github/workflows/pull_request_validation.yml @@ -48,6 +48,6 @@ jobs: version: "1.0.49" cli-version: "0.8.3" node-version: "22.19.0" - node-directory: "${{ runner.tmp }}/nodejs22" - node-prefix: "${{ runner.tmp }}/nodejs22/npm-installs" + node-directory: "${{ runner.temp }}/nodejs22" + node-prefix: "${{ runner.temp }}/nodejs22/npm-installs"