- 概述
- 开始使用
- 概念
- Using UiPath CLI
- 操作指南
- CI/CD recipes
- Azure DevOps
- GitHub Actions
- Jenkins
- GitLab CI
- 命令参考
- 概述
- 退出代码
- Global options
- uip codedagent
- uip docsai
- add-test-data-entity
- add-test-data-queue
- add-test-data-variation
- analyze
- build
- 创建项目
- diff
- find-activities
- get-analyzer-rules
- get-default-activity-xaml
- get-errors
- get-manual-test-cases
- get-manual-test-steps
- get-versions
- get-workflow-example
- indicate-application
- indicate-element
- inspect-package
- install-data-fabric-entities
- install-or-update-packages
- list-data-fabric-entities
- list-workflow-examples
- pack
- restore
- run-file
- search-templates
- start-studio
- stop-execution
- uia
- uip traces
- 迁移
- Reference & support
UiPath CLI user guide
This page gives you a complete .gitlab-ci.yml that installs the CLI, authenticates with an External Application, packs and publishes a UiPath Solution, deploys it to Orchestrator across multiple tenants (via a matrix), and runs a Test Manager suite. Drop it into the root of your repo, set three CI/CD variables, and it runs.
For the underlying principles — auth, caching, tool pre-install, version pinning — see How-to: deploy to Orchestrator from CI. This page focuses on the GitLab syntax, including the features (cache with a keyed scope, parallel: matrix) that are GitLab-specific.
先决条件
Before copying the YAML below:
- Create an External Application in UiPath with the
OR.*scopes your pipeline needs. See Authentication — Flow 2. - Store the secrets as CI/CD variables:
- Project → Settings → CI/CD → Variables.
- Add
UIPATH_CLIENT_IDandUIPATH_CLIENT_SECRET. Mark both as Masked and Protected (so they only expose on protected branches / tags). - Add
UIPATH_TENANT_DEV,UIPATH_TENANT_STAGE,UIPATH_TENANT_PRODwith the tenant names (not masked — tenant names are not sensitive).
- Provision a Test Manager project and test set if you want the test job. Add
TEST_SET_KEYandPROJECT_KEYas regular variables.
.gitlab-ci.yml
# -----------------------------------------------------------------------------
# Deploy UiPath Solution
# -----------------------------------------------------------------------------
# Auth: External Application, env.VAR_NAME prefix (never the literal value).
# Cache: npm global node_modules, keyed by CLI version.
# Matrix: deploy job fans out across dev / stage / prod tenants.
# -----------------------------------------------------------------------------
image: node:20
stages:
- build
- deploy
- test
variables:
CLI_VERSION: '1.0.0'
SOLUTION_NAME: 'my-solution'
SOLUTION_DIR: './my-solution'
OUTPUT_DIR: './dist'
SOLUTION_VERSION: '1.2.0-ci.$CI_PIPELINE_IID'
# Workspace-local npm prefix so installs need no sudo and are cacheable.
NPM_PREFIX: "$CI_PROJECT_DIR/.npm-global"
# Re-usable install block. GitLab does not have anchors for script:; we use
# YAML anchors on a hidden job and extend from it.
.install-uip: &install-uip |
set -euo pipefail
mkdir -p "$NPM_PREFIX"
npm config set prefix "$NPM_PREFIX"
export PATH="$NPM_PREFIX/bin:$PATH"
if ! command -v uip >/dev/null; then
npm install -g "@uipath/cli@$CLI_VERSION"
uip tools install \
@uipath/orchestrator-tool \
@uipath/solution-tool \
@uipath/test-manager-tool
fi
uip --version
cache:
key: "uip-$CLI_VERSION"
paths:
- .npm-global/lib/node_modules
policy: pull-push
# -----------------------------------------------------------------------------
# Stage: build
# -----------------------------------------------------------------------------
pack:
stage: build
script:
- *install-uip
- mkdir -p "$OUTPUT_DIR"
- |
uip solution pack "$SOLUTION_DIR" "$OUTPUT_DIR" \
--name "$SOLUTION_NAME" \
--version "$SOLUTION_VERSION"
artifacts:
paths:
- "$OUTPUT_DIR/$SOLUTION_NAME.$SOLUTION_VERSION.zip"
expire_in: 30 days
# -----------------------------------------------------------------------------
# Stage: deploy — matrix across environments
# -----------------------------------------------------------------------------
deploy:
stage: deploy
needs:
- job: pack
artifacts: true
parallel:
matrix:
- ENVIRONMENT: dev
TENANT_VAR: UIPATH_TENANT_DEV
- ENVIRONMENT: stage
TENANT_VAR: UIPATH_TENANT_STAGE
- ENVIRONMENT: prod
TENANT_VAR: UIPATH_TENANT_PROD
environment:
name: uipath/$ENVIRONMENT
rules:
# Prod only on protected branches — set protection under Settings → Repository.
- if: '$ENVIRONMENT == "prod" && $CI_COMMIT_REF_PROTECTED != "true"'
when: never
- when: on_success
script:
- *install-uip
- |
# Resolve the per-environment tenant from the matrix variable.
UIPATH_TENANT="${!TENANT_VAR}"
if [ -z "$UIPATH_TENANT" ]; then
echo "Tenant variable $TENANT_VAR is empty; set it in CI/CD settings." >&2
exit 3
fi
uip login \
--client-id env.UIPATH_CLIENT_ID \
--client-secret env.UIPATH_CLIENT_SECRET \
--tenant "$UIPATH_TENANT"
uip solution publish "$OUTPUT_DIR/$SOLUTION_NAME.$SOLUTION_VERSION.zip"
uip solution deploy run \
--name "$SOLUTION_NAME-$ENVIRONMENT-$CI_PIPELINE_IID" \
--package-name "$SOLUTION_NAME" \
--package-version "$SOLUTION_VERSION" \
--folder-name MySolution \
--folder-path Shared
# -----------------------------------------------------------------------------
# Stage: test
# -----------------------------------------------------------------------------
test:
stage: test
needs:
- job: "deploy: [dev, UIPATH_TENANT_DEV]" # depend on the dev leg of the matrix
optional: true
rules:
- if: '$TEST_SET_KEY == null || $TEST_SET_KEY == ""'
when: never
- when: on_success
script:
- *install-uip
- |
uip login \
--client-id env.UIPATH_CLIENT_ID \
--client-secret env.UIPATH_CLIENT_SECRET \
--tenant "$UIPATH_TENANT_DEV"
EXECUTION_ID=$(uip tm testset execute \
--test-set-key "$TEST_SET_KEY" \
--output-filter "Data.ExecutionId" \
--output plain)
echo "started execution $EXECUTION_ID"
if ! uip tm wait \
--execution-id "$EXECUTION_ID" \
--project-key "$PROJECT_KEY" \
--timeout 1800; then
code=$?
case "$code" in
2) echo "test run did not finish within 30 minutes" >&2; exit 2 ;;
*) echo "wait failed (exit $code)" >&2; exit "$code" ;;
esac
fi
uip tm report get \
--execution-id "$EXECUTION_ID" \
--project-key "$PROJECT_KEY"
FAILED=$(uip tm report get \
--execution-id "$EXECUTION_ID" \
--project-key "$PROJECT_KEY" \
--output-filter "Data.Failed" \
--output plain)
if [ "$FAILED" -gt 0 ]; then
echo "$FAILED test case(s) failed" >&2
exit 1
fi
# -----------------------------------------------------------------------------
# Deploy UiPath Solution
# -----------------------------------------------------------------------------
# Auth: External Application, env.VAR_NAME prefix (never the literal value).
# Cache: npm global node_modules, keyed by CLI version.
# Matrix: deploy job fans out across dev / stage / prod tenants.
# -----------------------------------------------------------------------------
image: node:20
stages:
- build
- deploy
- test
variables:
CLI_VERSION: '1.0.0'
SOLUTION_NAME: 'my-solution'
SOLUTION_DIR: './my-solution'
OUTPUT_DIR: './dist'
SOLUTION_VERSION: '1.2.0-ci.$CI_PIPELINE_IID'
# Workspace-local npm prefix so installs need no sudo and are cacheable.
NPM_PREFIX: "$CI_PROJECT_DIR/.npm-global"
# Re-usable install block. GitLab does not have anchors for script:; we use
# YAML anchors on a hidden job and extend from it.
.install-uip: &install-uip |
set -euo pipefail
mkdir -p "$NPM_PREFIX"
npm config set prefix "$NPM_PREFIX"
export PATH="$NPM_PREFIX/bin:$PATH"
if ! command -v uip >/dev/null; then
npm install -g "@uipath/cli@$CLI_VERSION"
uip tools install \
@uipath/orchestrator-tool \
@uipath/solution-tool \
@uipath/test-manager-tool
fi
uip --version
cache:
key: "uip-$CLI_VERSION"
paths:
- .npm-global/lib/node_modules
policy: pull-push
# -----------------------------------------------------------------------------
# Stage: build
# -----------------------------------------------------------------------------
pack:
stage: build
script:
- *install-uip
- mkdir -p "$OUTPUT_DIR"
- |
uip solution pack "$SOLUTION_DIR" "$OUTPUT_DIR" \
--name "$SOLUTION_NAME" \
--version "$SOLUTION_VERSION"
artifacts:
paths:
- "$OUTPUT_DIR/$SOLUTION_NAME.$SOLUTION_VERSION.zip"
expire_in: 30 days
# -----------------------------------------------------------------------------
# Stage: deploy — matrix across environments
# -----------------------------------------------------------------------------
deploy:
stage: deploy
needs:
- job: pack
artifacts: true
parallel:
matrix:
- ENVIRONMENT: dev
TENANT_VAR: UIPATH_TENANT_DEV
- ENVIRONMENT: stage
TENANT_VAR: UIPATH_TENANT_STAGE
- ENVIRONMENT: prod
TENANT_VAR: UIPATH_TENANT_PROD
environment:
name: uipath/$ENVIRONMENT
rules:
# Prod only on protected branches — set protection under Settings → Repository.
- if: '$ENVIRONMENT == "prod" && $CI_COMMIT_REF_PROTECTED != "true"'
when: never
- when: on_success
script:
- *install-uip
- |
# Resolve the per-environment tenant from the matrix variable.
UIPATH_TENANT="${!TENANT_VAR}"
if [ -z "$UIPATH_TENANT" ]; then
echo "Tenant variable $TENANT_VAR is empty; set it in CI/CD settings." >&2
exit 3
fi
uip login \
--client-id env.UIPATH_CLIENT_ID \
--client-secret env.UIPATH_CLIENT_SECRET \
--tenant "$UIPATH_TENANT"
uip solution publish "$OUTPUT_DIR/$SOLUTION_NAME.$SOLUTION_VERSION.zip"
uip solution deploy run \
--name "$SOLUTION_NAME-$ENVIRONMENT-$CI_PIPELINE_IID" \
--package-name "$SOLUTION_NAME" \
--package-version "$SOLUTION_VERSION" \
--folder-name MySolution \
--folder-path Shared
# -----------------------------------------------------------------------------
# Stage: test
# -----------------------------------------------------------------------------
test:
stage: test
needs:
- job: "deploy: [dev, UIPATH_TENANT_DEV]" # depend on the dev leg of the matrix
optional: true
rules:
- if: '$TEST_SET_KEY == null || $TEST_SET_KEY == ""'
when: never
- when: on_success
script:
- *install-uip
- |
uip login \
--client-id env.UIPATH_CLIENT_ID \
--client-secret env.UIPATH_CLIENT_SECRET \
--tenant "$UIPATH_TENANT_DEV"
EXECUTION_ID=$(uip tm testset execute \
--test-set-key "$TEST_SET_KEY" \
--output-filter "Data.ExecutionId" \
--output plain)
echo "started execution $EXECUTION_ID"
if ! uip tm wait \
--execution-id "$EXECUTION_ID" \
--project-key "$PROJECT_KEY" \
--timeout 1800; then
code=$?
case "$code" in
2) echo "test run did not finish within 30 minutes" >&2; exit 2 ;;
*) echo "wait failed (exit $code)" >&2; exit "$code" ;;
esac
fi
uip tm report get \
--execution-id "$EXECUTION_ID" \
--project-key "$PROJECT_KEY"
FAILED=$(uip tm report get \
--execution-id "$EXECUTION_ID" \
--project-key "$PROJECT_KEY" \
--output-filter "Data.Failed" \
--output plain)
if [ "$FAILED" -gt 0 ]; then
echo "$FAILED test case(s) failed" >&2
exit 1
fi
Walkthrough
Top-level setup
image: node:20— every job runs in the official Node.js 20 image. The CLI requires Node 18+. If your GitLab runner already has Node installed and you don't need a container, you can remove this and use ashellexecutor instead.variables:— the pipeline-wide values.SOLUTION_VERSIONinterpolates$CI_PIPELINE_IID(the incremental, project-scoped pipeline number — better for versioning than$CI_JOB_ID, which is global and non-monotonic)..install-uipanchor — GitLab does not let you anchorscript:blocks directly, but you can anchor a YAML node containing a shell string and splice it in with- *install-uip. Same install guard as the other recipes: workspace-local prefix, conditional install, pre-installed tools.cache:— the keyuip-$CLI_VERSIONensures a CLI version bump invalidates the cache cleanly.policy: pull-pushreads on entry and writes on successful job exit. If you run at scale and want to shave seconds off every job, split into a dedicated "seed the cache" job that runspull-pushand have all other jobs usepolicy: pullonly.
pack job
- Installs the CLI via the shared anchor.
uip solution packwith an explicit version — seeuip solution pack.artifacts:carries the.zipto the next stage.expire_in: 30 daysprevents GitLab's artifact storage from growing unbounded; bump it if you need longer traceability.
deploy job with parallel: matrix
The matrix expands into three jobs — deploy: [dev, UIPATH_TENANT_DEV], deploy: [stage, UIPATH_TENANT_STAGE], deploy: [prod, UIPATH_TENANT_PROD] — that run in parallel. Each gets a different $ENVIRONMENT and $TENANT_VAR, and uses bash indirect expansion (${!TENANT_VAR}) to read the per-environment tenant from the right CI/CD variable.
environment: name: uipath/$ENVIRONMENT— GitLab tracks deployments in its Environments view, so every tenant gets a per-environment history with rollback buttons.rules:— the first rule blocksprodfrom non-protected branches. Combined with Settings → Repository → Protected branches (where you markmainprotected), this is how you stop a feature branch from accidentally deploying to production. TheUIPATH_CLIENT_*variables should also be marked Protected so they only resolve on protected refs.uip login --client-id env.UIPATH_CLIENT_ID --client-secret env.UIPATH_CLIENT_SECRET— theenv.VAR_NAMEprefix is the supported way to pass a secret to the CLI without it ever appearing in the shell command line. GitLab masks variables in logs when marked Masked, but theenv.prefix is a defense-in-depth anyway. See Authentication — the env.VAR_NAME prefix.- Deployment name —
$SOLUTION_NAME-$ENVIRONMENT-$CI_PIPELINE_IIDmakes each deploy traceable to a specific pipeline run and environment.
In parallel-matrix jobs, if one leg fails, the others keep running by default. If you want prod to wait for dev and stage, turn the matrix into three sequential jobs (or use needs: between them) instead.
test job
needs:references the matrix leg by its expanded name —"deploy: [dev, UIPATH_TENANT_DEV]". Theoptional: truemakes the dependency non-fatal if the dev leg was skipped by therules:block.rules:skips the job whenTEST_SET_KEYis unset, same pattern as the other recipes.- Launch → wait → verify — the canonical test pattern from How-to: run tests from the CLI. Exit
2fromuip tm waitmeans timeout (not auth failure).
Common variations
Pin tool versions too
For patch-level reproducibility, replace the install anchor with pinned versions:
.install-uip: &install-uip |
# …same prefix setup…
if ! command -v uip >/dev/null; then
npm install -g "@uipath/cli@$CLI_VERSION"
uip tools install \
@uipath/orchestrator-tool@1.0.2 \
@uipath/solution-tool@1.0.2 \
@uipath/test-manager-tool@1.0.2
fi
.install-uip: &install-uip |
# …same prefix setup…
if ! command -v uip >/dev/null; then
npm install -g "@uipath/cli@$CLI_VERSION"
uip tools install \
@uipath/orchestrator-tool@1.0.2 \
@uipath/solution-tool@1.0.2 \
@uipath/test-manager-tool@1.0.2
fi
Serial promotion with manual gate
If you want prod to require a manual click rather than protected-branch gating, split the matrix into three jobs and add when: manual to the prod one:
deploy-dev:
stage: deploy
# …as deploy above, fixed to UIPATH_TENANT_DEV…
deploy-stage:
stage: deploy
needs: [ pack, deploy-dev ]
# …as deploy above, fixed to UIPATH_TENANT_STAGE…
deploy-prod:
stage: deploy
needs: [ pack, deploy-stage ]
when: manual # requires a reviewer to click "Play"
allow_failure: false
environment:
name: uipath/prod
# …as deploy above, fixed to UIPATH_TENANT_PROD…
deploy-dev:
stage: deploy
# …as deploy above, fixed to UIPATH_TENANT_DEV…
deploy-stage:
stage: deploy
needs: [ pack, deploy-dev ]
# …as deploy above, fixed to UIPATH_TENANT_STAGE…
deploy-prod:
stage: deploy
needs: [ pack, deploy-stage ]
when: manual # requires a reviewer to click "Play"
allow_failure: false
environment:
name: uipath/prod
# …as deploy above, fixed to UIPATH_TENANT_PROD…
Deeper coverage in How-to: pack and publish a Solution — promote one package across tenants.
回滚
Trigger manually via a separate job with a pipeline variable:
rollback:
stage: deploy
when: manual
rules:
- if: '$ROLLBACK_VERSION != null && $ROLLBACK_VERSION != ""'
script:
- *install-uip
- |
uip login \
--client-id env.UIPATH_CLIENT_ID \
--client-secret env.UIPATH_CLIENT_SECRET \
--tenant "$UIPATH_TENANT_PROD"
uip solution deploy run \
--name "$SOLUTION_NAME-rollback" \
--package-name "$SOLUTION_NAME" \
--package-version "$ROLLBACK_VERSION" \
--folder-name MySolution \
--folder-path Shared
rollback:
stage: deploy
when: manual
rules:
- if: '$ROLLBACK_VERSION != null && $ROLLBACK_VERSION != ""'
script:
- *install-uip
- |
uip login \
--client-id env.UIPATH_CLIENT_ID \
--client-secret env.UIPATH_CLIENT_SECRET \
--tenant "$UIPATH_TENANT_PROD"
uip solution deploy run \
--name "$SOLUTION_NAME-rollback" \
--package-name "$SOLUTION_NAME" \
--package-version "$ROLLBACK_VERSION" \
--folder-name MySolution \
--folder-path Shared
Start the pipeline from CI/CD → Pipelines → Run pipeline and set ROLLBACK_VERSION to the target version (for example, 1.1.9). For destructive rollback (uninstall + delete artifact), see How-to: pack and publish a Solution — rollback.
Skip tests
Leave TEST_SET_KEY unset in the CI/CD variables. The rules: block on the test job skips it cleanly.
Common pitfalls
- Masked != Protected. A masked variable hides the value in logs but is still available on all branches (including short-lived feature branches). A protected variable only exposes on protected refs. You want both for auth secrets — otherwise a pushed feature branch could run
uip loginagainst prod. - Indirect expansion needs bash.
${!TENANT_VAR}is a bash feature; the defaultshin some minimal images doesn't support it. Thenode:20image includes bash by default; onalpine-based images, addapk add bashor switch to a case statement over explicit per-environment variables. - Matrix job names contain spaces.
needs: - job: "deploy: [dev, UIPATH_TENANT_DEV]"— the name includes: [, so quote it in YAML. - Cache paths are workspace-relative. The cache entry
.npm-global/lib/node_modulesworks becauseNPM_PREFIX="$CI_PROJECT_DIR/.npm-global"puts it inside the workspace. If you move the prefix outside, the cache stops working. set -euo pipefailmust be at the top of every multi-line script. Without it, a failing pack can be followed by a "successful" publish of a stale artifact. See Scripting patterns — strict shell options.
另请参阅
- How-to: deploy to Orchestrator from CI — platform-agnostic guidance.
- How-to: pack and publish a Solution — versioning and rollback.
- How-to: run tests from the CLI — the launch → wait → verify pattern.
- CI/CD recipe: Azure Pipelines, GitHub Actions, Jenkins — the same pipeline in other platforms.