Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .github/actions/build-cloudxr-web-client/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ runs:
working-directory: deps/cloudxr/webxr_client
env:
SDK_VERSION: ${{ steps.sdk-version.outputs.version }}
CLIENT_GIT_REF: ${{ github.ref_name }}
CLIENT_GIT_SHA: ${{ github.sha }}
run: |
npm install "../nvidia-cloudxr-${SDK_VERSION}.tgz"
USE_LOCAL_WEBXR_ASSETS=0 npm run build
114 changes: 98 additions & 16 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
# SPDX-License-Identifier: Apache-2.0
#
# Build docs and Teleop web app, then deploy to GitHub Pages.
# - Push to main/release (canonical repo): deploy docs at site root, web app at /client/.
# - Pull request from a same-repo branch: auto-deploy preview to preview/pr-<N>/.
# - Pull request from a fork: build only (skip deploy). A maintainer can deploy
# the preview manually via the `/preview-docs` slash command — see
# docs-preview-on-demand.yaml.
# - Push to main/release (canonical repo): deploy docs at site root, web app at /client/<slug>/.
# - Same-repo PR: auto-deploy preview to preview/pr-<N>/.
# - Fork PR: build only; a maintainer can deploy via `/preview-docs` (see
# docs-preview-on-demand.yaml).
#
# PR: docs always built; web client (npm) built with public SDK for all, or private SDK only for allowlisted authors.
# Main/release (push): key used → private NGC for SDK.
# Allowlist for CXR.js SDK on PRs: Repo Settings → Variables → ALLOWED_CLOUDXR_JS_PR_AUTHORS (comma-separated GitHub logins).
# Builds are gated on changed paths by the `changes` job; gh-pages
# `keep_files: true` preserves whichever artifact was skipped.
#
# Web client uses the private CloudXR SDK for pushes and for PRs whose
# author is in ALLOWED_CLOUDXR_JS_PR_AUTHORS (Repo Settings → Variables);
# all other PRs fall back to the public SDK.

name: Build & deploy docs

Expand All @@ -24,6 +26,7 @@ on:
paths:
- "docs/**"
- ".github/workflows/docs.yaml"
- ".github/actions/build-cloudxr-web-client/**"
- "deps/cloudxr/webxr_client/**"
- "deps/cloudxr/.env.default"

Expand Down Expand Up @@ -64,10 +67,54 @@ jobs:
esac
fi

# gh-pages keep_files preserves whichever artifact isn't rebuilt this run.
changes:
name: Detect changed paths
runs-on: ubuntu-latest
permissions: {}
outputs:
docs: ${{ steps.filter.outputs.docs }}
webapp: ${{ steps.filter.outputs.webapp }}
steps:
- name: Checkout (with history)
uses: actions/checkout@v6
with:
fetch-depth: 0
- id: filter
env:
EVENT: ${{ github.event_name }}
BEFORE: ${{ github.event.before }}
BASE_REF: ${{ github.base_ref }}
run: |
set -euo pipefail
if [ "$EVENT" = 'pull_request' ]; then
git fetch --no-tags --depth=1 origin "$BASE_REF"
base="$(git merge-base FETCH_HEAD HEAD || true)"
elif [ -n "$BEFORE" ] && [ "$BEFORE" != '0000000000000000000000000000000000000000' ]; then
base="$BEFORE"
else
base="" # new branch / force push: treat everything as changed
fi
files="$([ -n "$base" ] && git diff --name-only "$base" HEAD || git ls-tree -r --name-only HEAD)"
docs=false; webapp=false
while IFS= read -r f; do
case "$f" in
docs/*|.github/workflows/docs.yaml) docs=true ;;
esac
case "$f" in
deps/cloudxr/webxr_client/*|deps/cloudxr/.env.default|.github/actions/build-cloudxr-web-client/*|.github/workflows/docs.yaml)
webapp=true ;;
esac
done <<< "$files"
echo "docs=$docs" >> "$GITHUB_OUTPUT"
echo "webapp=$webapp" >> "$GITHUB_OUTPUT"
echo "::notice::changes: docs=$docs webapp=$webapp (base=${base:-<none>})"

build-docs:
name: Build Docs
runs-on: ubuntu-latest
needs: [check-repo]
needs: [check-repo, changes]
if: needs.changes.outputs.docs == 'true'
steps:
- name: Checkout code
uses: actions/checkout@v6
Expand Down Expand Up @@ -101,7 +148,8 @@ jobs:
build-app:
name: Build Teleop Web App
runs-on: ubuntu-latest
needs: [check-repo]
needs: [check-repo, changes]
if: needs.changes.outputs.webapp == 'true'
steps:
- name: Checkout code
uses: actions/checkout@v6
Expand All @@ -121,32 +169,66 @@ jobs:
deploy-docs:
name: Deploy docs
runs-on: ubuntu-latest
needs: [check-repo, build-docs, build-app]
if: needs.check-repo.outputs.is-canonical-repo == 'true' && (needs.check-repo.outputs.is-deploy-branch == 'true' || github.event_name == 'pull_request')
needs: [check-repo, changes, build-docs, build-app]
if: >-
!cancelled()
&& needs.check-repo.outputs.is-canonical-repo == 'true'
&& (needs.check-repo.outputs.is-deploy-branch == 'true' || github.event_name == 'pull_request')
&& needs.build-docs.result != 'failure'
&& needs.build-app.result != 'failure'
&& (needs.build-docs.result == 'success' || needs.build-app.result == 'success')
permissions:
contents: write # push to gh-pages
steps:
- name: Download docs artifact
if: needs.build-docs.result == 'success'
uses: actions/download-artifact@v7
with:
name: docs-html
path: ./docs/build

- name: Download web app artifact
if: needs.build-app.result == 'success'
uses: actions/download-artifact@v7
with:
name: webapp
path: ./webapp

- name: Place web app under subpath
# Each push lands at client/<slug>/ so a release-branch deploy can no
# longer trample the canonical /client/, which is just a redirect to
# ./main/.
- name: Place web app under versioned subpath
if: needs.build-app.result == 'success'
run: |
set -euo pipefail
if [ "${{ github.event_name }}" = 'pull_request' ]; then
# PR previews live under preview/pr-<N>/ and are pinned to the PR,
# so they don't need the multi-version layout.
CLIENT_DIR=./docs/build/current/client
mkdir -p "$CLIENT_DIR"
cp -r ./webapp/. "$CLIENT_DIR/"
else
CLIENT_DIR=./docs/build/client
slug="${GITHUB_REF_NAME//\//-}"
CLIENT_BASE=./docs/build/client
mkdir -p "$CLIENT_BASE/$slug"
cp -r ./webapp/. "$CLIENT_BASE/$slug/"
cat > "$CLIENT_BASE/index.html" <<'HTML'
<!-- SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -->
<!-- SPDX-License-Identifier: Apache-2.0 -->
<!DOCTYPE html>
<html>
<head>
<title>Redirecting to the latest Isaac Teleop web client</title>
<meta charset="utf-8">
<meta http-equiv="refresh" content="0; url=./main/">
</head>
<body>
<p>Redirecting to <a href="./main/">the latest Isaac Teleop web client</a>.</p>
</body>
</html>
HTML
echo "Deployed web client to /client/$slug/"
fi
mkdir -p "$CLIENT_DIR"
cp -r ./webapp/. "$CLIENT_DIR/"

- name: Deploy to gh-pages (main branch)
if: github.event_name == 'push' && needs.check-repo.outputs.is-deploy-branch == 'true'
Expand Down
33 changes: 33 additions & 0 deletions deps/cloudxr/webxr_client/src/BuildInfoOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

const BUILD_INFO = {
teleopVersion: process.env.CLIENT_TELEOP_VERSION,
sdkVersion: process.env.CLIENT_SDK_VERSION,
gitRef: process.env.CLIENT_GIT_REF,
gitSha: process.env.CLIENT_GIT_SHA,
buildTime: process.env.CLIENT_BUILD_TIME,
};

console.info(
`[Isaac Teleop Web Client] teleop=${BUILD_INFO.teleopVersion} sdk=${BUILD_INFO.sdkVersion} ` +
`ref=${BUILD_INFO.gitRef}@${BUILD_INFO.gitSha} built=${BUILD_INFO.buildTime}`
);

export function mountBuildInfoOverlayIfRequested(): void {
if (new URLSearchParams(window.location.search).get('showVersion') !== '1') return;
const el = document.createElement('div');
el.id = 'teleop-build-info-overlay';
el.style.cssText =
'position:fixed;left:8px;bottom:8px;z-index:99999;padding:8px 10px;' +
'font:12px/1.4 ui-monospace,Menlo,Consolas,monospace;color:#fff;' +
'background:rgba(0,0,0,0.78);border:1px solid #76b900;border-radius:4px;' +
'pointer-events:none;white-space:pre';
el.textContent =
`Teleop ${BUILD_INFO.teleopVersion} · SDK ${BUILD_INFO.sdkVersion}\n` +
`${BUILD_INFO.gitRef}@${BUILD_INFO.gitSha}\n` +
`built ${BUILD_INFO.buildTime}`;
document.body.appendChild(el);
}
3 changes: 3 additions & 0 deletions deps/cloudxr/webxr_client/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import React from 'react';
import ReactDOM from 'react-dom/client';

import App from './App';
import { mountBuildInfoOverlayIfRequested } from './BuildInfoOverlay';

// Start the React app immediately in the 3d-ui container
function startApp() {
Expand All @@ -34,6 +35,8 @@ function startApp() {
} else {
console.error('3d-ui container not found');
}

mountBuildInfoOverlayIfRequested();
}

// Initialize the app when DOM is ready
Expand Down
26 changes: 26 additions & 0 deletions deps/cloudxr/webxr_client/webpack.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,31 @@
*/

const path = require('path');
const fs = require('fs');
const { execSync } = require('child_process');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const webpack = require('webpack');

function git(cmd) {
try {
return execSync(`git ${cmd}`, { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
} catch {
return '';
}
}
let TELEOP_VERSION = '';
try {
TELEOP_VERSION = fs.readFileSync(path.resolve(__dirname, '../../../VERSION'), 'utf8').trim();
} catch {}
// Source of truth for the CloudXR SDK is deps/cloudxr/.env.default's
// CXR_WEB_SDK_VERSION (also controls which tarball `npm install`
// consumes); package.json.version is just a local-dev fallback.
const CLIENT_SDK_VERSION = process.env.SDK_VERSION || require('./package.json').version;
const CLIENT_GIT_REF = process.env.CLIENT_GIT_REF || git('rev-parse --abbrev-ref HEAD') || 'unknown';
const CLIENT_GIT_SHA = (process.env.CLIENT_GIT_SHA || git('rev-parse HEAD') || 'unknown').slice(0, 12);
const CLIENT_BUILD_TIME = new Date().toISOString();

// WebXR input profile assets are used by default when @webxr-input-profiles/assets is installed.
// Set USE_LOCAL_WEBXR_ASSETS=0 to skip bundling local assets (build needs internet at runtime to load assets).
const useLocalWebxrAssets = process.env.USE_LOCAL_WEBXR_ASSETS !== '0';
Expand Down Expand Up @@ -95,6 +116,11 @@ module.exports = {
// Inject environment variables
new webpack.DefinePlugin({
'process.env.WEBXR_ASSETS_VERSION': JSON.stringify(WEBXR_ASSETS_VERSION),
'process.env.CLIENT_TELEOP_VERSION': JSON.stringify(TELEOP_VERSION),
'process.env.CLIENT_SDK_VERSION': JSON.stringify(CLIENT_SDK_VERSION),
'process.env.CLIENT_GIT_REF': JSON.stringify(CLIENT_GIT_REF),
'process.env.CLIENT_GIT_SHA': JSON.stringify(CLIENT_GIT_SHA),
'process.env.CLIENT_BUILD_TIME': JSON.stringify(CLIENT_BUILD_TIME),
}),

// Copies WebXR input profile assets when available; always copies public and favicon
Expand Down
Loading