Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9f5da42
ci(ci): replace vercel preview deployments
fikewa-olatunji Jul 1, 2026
89da62e
ci(ci): pin published UiPath CLI version
fikewa-olatunji Jul 1, 2026
a70e4af
ci(ci): read UiPath client ID from variables
fikewa-olatunji Jul 1, 2026
9a68957
ci(ci): improve coded app preview links
fikewa-olatunji Jul 2, 2026
9661d15
ci(ci): fix apollo design logo preview path
fikewa-olatunji Jul 2, 2026
d4db8bd
ci(ci): address copilot preview workflow feedback
fikewa-olatunji Jul 2, 2026
9278101
ci(ci): smooth coded app docs navigation
fikewa-olatunji Jul 2, 2026
e644a24
ci(ci): remove failed coded app nav shim
fikewa-olatunji Jul 2, 2026
8d62e23
ci(ci): harden coded app preview workflow
fikewa-olatunji Jul 2, 2026
7ca7bb4
ci(ci): try coded app previews through uip-go
fikewa-olatunji Jul 2, 2026
371279a
ci(ci): isolate uip-go package registry config
fikewa-olatunji Jul 2, 2026
41548f9
ci(ci): make uip-go the coded app preview path
fikewa-olatunji Jul 2, 2026
f63f009
ci(ci): link deploying status to preview run
fikewa-olatunji Jul 2, 2026
bc74b5b
ci(ci): harden uip-go preview workflow permissions
fikewa-olatunji Jul 2, 2026
34e3ae4
ci(ci): rely on bundled uip-go cli
fikewa-olatunji Jul 2, 2026
3799592
ci(ci): use uip-go codedapp tool bundle
fikewa-olatunji Jul 2, 2026
c6ef045
ci(ci): address preview workflow review feedback
fikewa-olatunji Jul 2, 2026
a47f137
ci(ci): paginate preview comment lookup
fikewa-olatunji Jul 2, 2026
e39a833
ci(ci): use uip-go search fix
fikewa-olatunji Jul 2, 2026
0a853ca
ci(ci): use uip-go pagefind fix
fikewa-olatunji Jul 2, 2026
a67b6cd
ci(ci): continue preview deployments after failure
fikewa-olatunji Jul 2, 2026
aed0552
ci(ci): allow client id secret fallback
fikewa-olatunji Jul 2, 2026
f57262c
ci(ci): enable vertex ai chat previews
fikewa-olatunji Jul 3, 2026
a3f2e41
ci(ci): use uip-go 0.1.11
fikewa-olatunji Jul 3, 2026
fa87fa6
test(ci): exercise coded app preview ai chat
fikewa-olatunji Jul 4, 2026
b9a4442
test(ci): allow preview comment on draft test PR
fikewa-olatunji Jul 4, 2026
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
351 changes: 351 additions & 0 deletions .github/workflows/preview-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,351 @@
name: Coded App Preview Deployments

on:
Comment on lines +1 to +3
Comment on lines +1 to +3
pull_request:
types: [opened, synchronize, reopened]
branches:
- main
- 'support/**'
Comment on lines +1 to +8

# Deny-all default; jobs grant the minimum they need.
permissions: {}

env:
TURBO_TELEMETRY_DISABLED: 1
DO_NOT_TRACK: 1
UIP_GO_VERSION: 0.1.11

concurrency:
group: coded-app-preview-pr-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
initialize-comment:
name: Initialize Apollo Coded App Preview Comment
if: github.event.pull_request.head.repo.fork == false
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write

Comment on lines +27 to +30
steps:
- name: Initialize PR comment
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
const identifier = '<!-- apollo-coded-app-preview-comment -->';
const timestamp = new Date().toLocaleString('en-US', {
timeZone: 'America/Los_Angeles',
year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: true
});
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const deployingLink = `[Deploying...](${runUrl})`;
const logsLink = `[Logs](${runUrl})`;
const projects = ['apollo-design', 'apollo-docs', 'apollo-landing', 'apollo-vertex'];
const body = [
identifier,
'Apollo Coded App preview deployments are running.',
'',
'| Project | Status | Preview | Updated (PT) |',
'|---------|--------|---------|--------------|',
...projects.map(project => `| ${project} | ${deployingLink} | ${logsLink} | ${timestamp} |`)
].join('\n');

const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
per_page: 100,
});
const existing = comments.find(comment =>
comment.body?.includes(identifier) &&
comment.user?.type === 'Bot' &&
['github-actions[bot]', 'github-actions'].includes(comment.user?.login)
);
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}

deploy:
name: Deploy Apollo Coded App Previews
needs: initialize-comment
if: ${{ !cancelled() && needs.initialize-comment.result == 'success' && github.event.pull_request.head.repo.fork == false }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
outputs:
design_url: ${{ steps.deploy.outputs.design_url }}
docs_url: ${{ steps.deploy.outputs.docs_url }}
landing_url: ${{ steps.deploy.outputs.landing_url }}
vertex_url: ${{ steps.deploy.outputs.vertex_url }}

steps:
- name: Checkout code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
persist-credentials: false

- name: Install Node dependencies
uses: ./.github/actions/install-node-deps

- name: Restore Turborepo cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.ref_name }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-${{ github.ref_name }}-

- name: Deploy Coded App previews with uip-go
id: deploy
env:
GH_NPM_REGISTRY_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
UIPATH_BASE_URL: ${{ vars.UIPATH_BASE_URL || secrets.UIPATH_BASE_URL }}
UIPATH_CLIENT_ID: ${{ vars.UIPATH_CLIENT_ID || secrets.UIPATH_CLIENT_ID }}
UIPATH_CLIENT_SECRET: ${{ secrets.UIPATH_CLIENT_SECRET }}
UIPATH_FOLDER_KEY: ${{ vars.UIPATH_FOLDER_KEY || secrets.UIPATH_FOLDER_KEY }}
UIPATH_ORG_NAME: ${{ vars.UIPATH_ORG_NAME || secrets.UIPATH_ORG_NAME }}
UIPATH_TENANT_NAME: ${{ vars.UIPATH_TENANT_NAME || secrets.UIPATH_TENANT_NAME }}
APOLLO_VERTEX_AICHAT_CLIENT_ID: ${{ vars.APOLLO_VERTEX_AICHAT_CLIENT_ID || secrets.APOLLO_VERTEX_AICHAT_CLIENT_ID }}
run: |
set -euo pipefail

missing=0
for name in GH_NPM_REGISTRY_TOKEN UIPATH_BASE_URL UIPATH_CLIENT_ID UIPATH_CLIENT_SECRET UIPATH_FOLDER_KEY UIPATH_ORG_NAME UIPATH_TENANT_NAME UIP_GO_VERSION APOLLO_VERTEX_AICHAT_CLIENT_ID; do
if [ -z "${!name:-}" ]; then
echo "::error::$name is required for uip-go Coded App preview deployments."
missing=1
fi
done
if [ "$missing" -ne 0 ]; then
exit 1
fi

short_sha="${GITHUB_SHA:0:7}"
version="0.1.0-pr${PR_NUMBER}.${short_sha}.${GITHUB_RUN_ATTEMPT}"
landing_app="apollo-landing-pr-${PR_NUMBER}"
docs_app="apollo-docs-pr-${PR_NUMBER}"
design_app="apollo-design-pr-${PR_NUMBER}"
vertex_app="apollo-vertex-pr-${PR_NUMBER}"
uip_go_npmrc="${RUNNER_TEMP}/uip-go.npmrc"
uip_go_prefix="${RUNNER_TEMP}/uip-go"
{
printf '@uipath:registry=https://npm.pkg.github.com\n'
printf '//npm.pkg.github.com/:_authToken=%s\n' "$GH_NPM_REGISTRY_TOKEN"
} > "$uip_go_npmrc"
NPM_CONFIG_USERCONFIG="$uip_go_npmrc" npm install --prefix "$uip_go_prefix" "@uipath/uip-go@${UIP_GO_VERSION}"
UIP_GO="${uip_go_prefix}/node_modules/.bin/uip-go"

write_output() {
local name="$1"
local value="$2"
local delimiter
delimiter="ghadelim_$(openssl rand -hex 16)"
{
printf '%s<<%s\n' "$name" "$delimiter"
printf '%s\n' "$value"
printf '%s\n' "$delimiter"
} >> "$GITHUB_OUTPUT"
}

derive_app_url() {
local app_name="$1"
APP_NAME="$app_name" node <<'NODE'
const baseUrl = process.env.UIPATH_BASE_URL || '';
const orgName = process.env.UIPATH_ORG_NAME || '';
const appName = process.env.APP_NAME || '';

let environment = '';
try {
const hostname = new URL(baseUrl).hostname;
let inferred = hostname.replace(/\.uipath\.com$/, '');
inferred = inferred.replace(/^api\./, '').replace(/\.api$/, '');
environment = inferred && inferred !== 'api' && inferred !== 'cloud' ? inferred : '';
} catch {
environment = '';
}

const suffix = environment ? `.${environment}` : '';
console.log(`https://${orgName}${suffix}.uipath.host/${appName}`);
NODE
}

run_uip_go() {
local label="$1"
local app_name="$2"
local output_name="${label#apollo-}_url"
local output_file
local command_exit
local app_url
local extra_args=()

output_file="$(mktemp)"
trap 'rm -f "$output_file"; trap - RETURN' RETURN
if [ "$label" = "apollo-vertex" ]; then
extra_args+=(--auth-client-id "$APOLLO_VERTEX_AICHAT_CLIENT_ID")
fi
echo "::group::uip-go ${label}"
set +e
"$UIP_GO" "$label" \
--name "$app_name" \
--path-name "$app_name" \
--version "$version" \
--folder-key "$UIPATH_FOLDER_KEY" \
--base-url "$UIPATH_BASE_URL" \
--org-name "$UIPATH_ORG_NAME" \
--tenant-name "$UIPATH_TENANT_NAME" \
--deploy-retries 3 \
--deploy-retry-delay-ms 10000 \
"${extra_args[@]}" 2>&1 | tee "$output_file"
command_exit=${PIPESTATUS[0]}
set -e
echo "::endgroup::"

if [ "$command_exit" -ne 0 ]; then
echo "::error::uip-go ${label} failed."
return "$command_exit"
fi

app_url="$(sed -nE 's/^[[:space:]]*App URL:[[:space:]]*//p' "$output_file" | tail -n 1)"
if [ -z "$app_url" ]; then
app_url="$(derive_app_url "$app_name")"
fi
case "$app_url" in
*/) ;;
*) app_url="${app_url}/" ;;
esac
write_output "$output_name" "$app_url"
}

failed_apps=()
deploy_or_record_failure() {
local label="$1"
local app_name="$2"

if run_uip_go "$label" "$app_name"; then
return 0
fi

failed_apps+=("$label")
return 0
}

deploy_or_record_failure "apollo-landing" "$landing_app"
deploy_or_record_failure "apollo-docs" "$docs_app"
deploy_or_record_failure "apollo-design" "$design_app"
deploy_or_record_failure "apollo-vertex" "$vertex_app"

if [ "${#failed_apps[@]}" -ne 0 ]; then
echo "::error::uip-go deployment failed for: ${failed_apps[*]}"
exit 1
fi

- name: Save Turborepo cache
if: always()
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.ref_name }}-${{ github.sha }}

update-comment:
name: Update Apollo Coded App Preview Comment
needs: [initialize-comment, deploy]
if: ${{ always() && github.event.pull_request.head.repo.fork == false && needs.initialize-comment.result != 'skipped' }}
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write

Comment on lines +276 to +279
steps:
- name: Update PR comment
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
DEPLOY_OUTCOME: ${{ needs.deploy.result }}
APOLLO_DESIGN_URL: ${{ needs.deploy.outputs.design_url }}
APOLLO_DOCS_URL: ${{ needs.deploy.outputs.docs_url }}
APOLLO_LANDING_URL: ${{ needs.deploy.outputs.landing_url }}
APOLLO_VERTEX_URL: ${{ needs.deploy.outputs.vertex_url }}
with:
script: |
const identifier = '<!-- apollo-coded-app-preview-comment -->';
const timestamp = new Date().toLocaleString('en-US', {
timeZone: 'America/Los_Angeles',
year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: true
});
const logsLink = `[Logs](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})`;
const deploySucceeded = process.env.DEPLOY_OUTCOME === 'success';
const rows = [
['apollo-design', process.env.APOLLO_DESIGN_URL],
['apollo-docs', process.env.APOLLO_DOCS_URL],
['apollo-landing', process.env.APOLLO_LANDING_URL],
['apollo-vertex', process.env.APOLLO_VERTEX_URL],
].map(([project, url]) => {
const ready = Boolean(url);
const status = ready ? 'Ready' : deploySucceeded ? 'Skipped' : 'Failed';
const preview = ready ? `[Preview](${url}) · ${logsLink}` : logsLink;
return `| ${project} | ${status} | ${preview} | ${timestamp} |`;
});
const body = [
identifier,
deploySucceeded
? 'Apollo Coded App preview deployments are ready.'
: 'Apollo Coded App preview deployments did not complete.',
'',
'| Project | Status | Preview | Updated (PT) |',
'|---------|--------|---------|--------------|',
...rows
].join('\n');

const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
per_page: 100,
});
const existing = comments.find(comment =>
comment.body?.includes(identifier) &&
comment.user?.type === 'Bot' &&
['github-actions[bot]', 'github-actions'].includes(comment.user?.login)
);
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}
Loading
Loading