diff --git a/.github/workflows/run-coder-eval.yml b/.github/workflows/run-coder-eval.yml index e051c7ef3f..a3404cc6ae 100644 --- a/.github/workflows/run-coder-eval.yml +++ b/.github/workflows/run-coder-eval.yml @@ -284,8 +284,9 @@ jobs: echo "${{ secrets.GH_PACKAGES_TOKEN || secrets.GH_PAT }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin docker pull "ghcr.io/uipath/coder-eval-agent:${CE_IMAGE_TAG}" - # Build the skills extension image (`@uipath/cli` + `@uipath/admin-tool` - # on top of coder-eval-agent). Matches smoke-skills.yml + daily.sh. + # Build the skills extension image (`@uipath/cli`, its tool plugins, and + # `@uipath/flow-sdk` on top of coder-eval-agent). Matches + # smoke-skills.yml + daily.sh. - name: Build skills Docker image env: DOCKER_BUILDKIT: "1" diff --git a/.github/workflows/smoke-skills.yml b/.github/workflows/smoke-skills.yml index 9b4b427ee5..b23be6f362 100644 --- a/.github/workflows/smoke-skills.yml +++ b/.github/workflows/smoke-skills.yml @@ -366,12 +366,13 @@ jobs: echo "${{ secrets.GH_PACKAGES_TOKEN || secrets.GH_PAT }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin docker pull ghcr.io/uipath/coder-eval-agent:${{ steps.ceref.outputs.version }} - # Build custom smoke image extending coder-eval-agent with @uipath/cli and - # every whitelisted tool plugin from GitHub Packages @dev (see - # tests/docker/Dockerfile). Every push to `cli/main` publishes a - # `-dev.` prerelease under the `dev` dist-tag (cli PR #2650 renamed the - # old `alpha` tag → `dev` on 2026-06-26), so smoke tracks main — not stale - # `latest` from public npm (which lags by ~2 weeks). + # Build custom smoke image extending coder-eval-agent with @uipath/cli, + # every whitelisted tool plugin from GitHub Packages @dev, and the latest + # published @uipath/flow-sdk (see tests/docker/Dockerfile). Every push to + # `cli/main` publishes a `-dev.` prerelease under the `dev` dist-tag + # (cli PR #2650 renamed the old `alpha` tag → `dev` on 2026-06-26), so + # smoke tracks main — not stale `latest` from public npm (which lags by ~2 + # weeks). - name: Build skills smoke Docker image env: DOCKER_BUILDKIT: "1" diff --git a/tests/docker/Dockerfile b/tests/docker/Dockerfile index 1d1067191b..deeb4df4c2 100644 --- a/tests/docker/Dockerfile +++ b/tests/docker/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1.7 # Custom image for UiPath skills smoke tests -# Extends coder-eval-agent with UiPath CLI tools pre-installed +# Extends coder-eval-agent with UiPath CLI tools and the Flow SDK pre-installed ARG CODER_EVAL_IMAGE=coder-eval-agent:latest FROM ${CODER_EVAL_IMAGE} @@ -17,6 +17,11 @@ ARG NPM_AUTH_TOKEN= # and ETARGETs. A pin freezes the CLI; the tools ride the train's tag. ARG CLI_VERSION=dev +# The builder SDK is released independently from the CLI. Follow its rolling +# release just as CLI_VERSION follows the live CLI train; callers can still +# override this with an exact version for a reproducible comparison. +ARG FLOW_SDK_VERSION=latest + # Floor on the number of tool plugins that must install for the image to be # usable. Discovery returns the CLI's full whitelist; a few may lack a build on # the current train (e.g. model-hub-tool has no `-dev` build), so individual @@ -28,7 +33,10 @@ ARG MIN_TOOLS=25 # CLI, sync would try to re-align tools through the release-channel path (which # can't resolve `-dev` tool builds — see below) and would undo the plugins baked # here. Belt-and-suspenders with the eval harness env. -ENV UIPATH_CLI_DISABLE_VERSION_SYNC=1 +ENV UIPATH_CLI_DISABLE_VERSION_SYNC=1 \ + PREVIEW_FLOW_SDK_ROOT=/opt/preview-flow-sdk \ + PREVIEW_FLOW_SDK_ASSETS_ROOT=/opt/preview-flow-sdk-assets \ + UIP_MAESTRO_REGISTRY_HOME=/opt/preview-flow-sdk-assets/registry # Install the UiPath CLI shell from its public npm release and pre-bake EVERY # whitelisted tool plugin from GitHub Packages. Stable CLI releases such as the @@ -95,6 +103,17 @@ RUN --mount=type=secret,id=npm_auth_token \ if [ "$public_cli" = false ]; then \ npm install -g "@uipath/cli@${CLI_VERSION}"; \ fi && \ + mkdir -p "$PREVIEW_FLOW_SDK_ROOT" && \ + printf '%s\n' '{"private":true,"type":"module"}' \ + > "$PREVIEW_FLOW_SDK_ROOT/package.json" && \ + npm install --prefix "$PREVIEW_FLOW_SDK_ROOT" \ + --save-exact --no-audit --no-fund --userconfig "$npmrc" \ + "@uipath/flow-sdk@${FLOW_SDK_VERSION}" && \ + resolved_sdk_version="$(node -p \ + "require('$PREVIEW_FLOW_SDK_ROOT/node_modules/@uipath/flow-sdk/package.json').version")" && \ + printf '%s\n' "$resolved_sdk_version" > "$PREVIEW_FLOW_SDK_ROOT/flow-sdk.version" && \ + echo "Installed @uipath/flow-sdk@${resolved_sdk_version}" && \ + test -x "$PREVIEW_FLOW_SDK_ROOT/node_modules/.bin/flow-sdk" && \ uip --version && \ uip tools search --output json > /tmp/uip-tools-search.json && \ tools="$(grep -oE '@uipath/[a-z0-9-]+-tool' /tmp/uip-tools-search.json | sort -u || true)" && \ @@ -132,6 +151,29 @@ ENV DOTNET_ROOT=/usr/share/dotnet \ DOTNET_NOLOGO=1 \ DOTNET_CLI_TELEMETRY_OPTOUT=1 +# Install the released connector catalog through the product CLI. The stable +# links keep the authoring paths independent of the catalog's content hash. +RUN cd "$PREVIEW_FLOW_SDK_ROOT" && \ + uip maestro registry pull --force --output json && \ + registry_snapshot="$(jq -er '.snapshotPath' "$UIP_MAESTRO_REGISTRY_HOME/current.json")" && \ + test -f "$registry_snapshot/library-json/index.json" && \ + test -f "$registry_snapshot/library-md/index.json" && \ + mkdir -p "$PREVIEW_FLOW_SDK_ASSETS_ROOT" && \ + ln -s "$registry_snapshot/library-json" "$PREVIEW_FLOW_SDK_ASSETS_ROOT/library-json" && \ + ln -s "$registry_snapshot/library-md" "$PREVIEW_FLOW_SDK_ASSETS_ROOT/library-md" && \ + ln -s "$registry_snapshot/connectors" "$PREVIEW_FLOW_SDK_ASSETS_ROOT/connectors" + +ENV FLOW_SDK_LIBRARY_JSON=/opt/preview-flow-sdk-assets/library-json \ + FLOW_SDK_LIBRARY_MD=/opt/preview-flow-sdk-assets/library-md \ + FLOW_SDK_CONNECTORS_DIR=/opt/preview-flow-sdk-assets/connectors + +# Prove that the installed SDK resolves without registry credentials and that +# the product CLI can check real source against it. +COPY tests/fixtures/maestro-flow-sdk-smoke/ /opt/maestro-flow-sdk-smoke/ +RUN cd /opt/maestro-flow-sdk-smoke && \ + ln -s ${PREVIEW_FLOW_SDK_ROOT}/node_modules node_modules && \ + uip maestro flow check Smoke.flow.ts --source + # Pre-install Python packages needed by coded-agent sandbox fixtures so agents # don't trigger a live uv-sync download (which takes 1-2 min and causes retry # loops in the turn timeline). uipath-langchain is declared in the diff --git a/tests/docker/maestro-flow-sdk.Dockerfile b/tests/docker/maestro-flow-sdk.Dockerfile deleted file mode 100644 index 4c699ce76c..0000000000 --- a/tests/docker/maestro-flow-sdk.Dockerfile +++ /dev/null @@ -1,73 +0,0 @@ -# syntax=docker/dockerfile:1.7 -ARG BASE_IMAGE=skills-image:base - -FROM node:22-bookworm-slim AS preview_flow_sdk_package - -ARG FLOW_SDK_VERSION -ARG FLOW_BUILDER_SDK_SHA -WORKDIR /src -COPY --from=flow_builder_sdk typescript/sdk/ ./typescript/sdk/ -COPY --from=flow_builder_sdk integrations/scripts/ ./integrations/scripts/ -RUN mkdir -p /pkg && \ - cd typescript/sdk && \ - npm ci --no-audit --no-fund && \ - npm pkg set \ - version="${FLOW_SDK_VERSION:?FLOW_SDK_VERSION is required}" \ - gitref="${FLOW_BUILDER_SDK_SHA:?FLOW_BUILDER_SDK_SHA is required}" && \ - npm pack --pack-destination /pkg && \ - mv /pkg/uipath-flow-sdk-*.tgz /pkg/uipath-flow-sdk.tgz - -FROM ${BASE_IMAGE} - -ARG FLOW_SDK_VERSION -ARG FLOW_BUILDER_SDK_SHA -ENV PREVIEW_FLOW_SDK_ROOT=/opt/preview-flow-sdk -ENV PREVIEW_FLOW_SDK_ASSETS_ROOT=/opt/preview-flow-sdk-assets - -RUN apt-get update && apt-get install -y --no-install-recommends unzip - -# Build and install the SDK from the same pinned source that supplies the -# connector library. This makes an SDK/compiler remediation measurable before -# its next package release without leaving package credentials in the image. -COPY --from=preview_flow_sdk_package \ - /pkg/uipath-flow-sdk.tgz /opt/preview-flow-sdk-pkg/uipath-flow-sdk.tgz -RUN set -euo pipefail && \ - mkdir -p "$PREVIEW_FLOW_SDK_ROOT" && \ - cd "$PREVIEW_FLOW_SDK_ROOT" && \ - printf '%s\n' '{"private":true,"type":"module"}' > package.json && \ - npm install --save-exact --no-audit --no-fund \ - /opt/preview-flow-sdk-pkg/uipath-flow-sdk.tgz && \ - node -e "const p=require('./node_modules/@uipath/flow-sdk/package.json'); console.log('Installed', p.name+'@'+p.version)" && \ - printf '%s\n' "$FLOW_SDK_VERSION" > flow-sdk.version && \ - test -x node_modules/.bin/flow-sdk - -# Connector descriptors are kept at the preview skill's provenance pin so -# connector authoring and SDK signatures describe the same source revision. -COPY --from=flow_builder_sdk typescript/sdk/lib/ ${PREVIEW_FLOW_SDK_ASSETS_ROOT}/typescript/sdk/lib/ -COPY --from=flow_builder_sdk integrations/ ${PREVIEW_FLOW_SDK_ASSETS_ROOT}/integrations/ - -RUN FLOW_SDK_ROOT=${PREVIEW_FLOW_SDK_ASSETS_ROOT} \ - bash ${PREVIEW_FLOW_SDK_ASSETS_ROOT}/typescript/sdk/lib/scripts/unpack-library.sh && \ - python3 ${PREVIEW_FLOW_SDK_ASSETS_ROOT}/integrations/scripts/generate_connectors_ts.py \ - --library ${PREVIEW_FLOW_SDK_ASSETS_ROOT}/typescript/sdk/lib/library-json \ - --output ${PREVIEW_FLOW_SDK_ASSETS_ROOT}/connectors \ - --import @uipath/flow-sdk && \ - chmod +x ${PREVIEW_FLOW_SDK_ASSETS_ROOT}/integrations/scripts/prepare_connector.py && \ - printf '#!/bin/sh\nexec python3 %s/integrations/scripts/prepare_connector.py "$@" --import @uipath/flow-sdk\n' \ - "${PREVIEW_FLOW_SDK_ASSETS_ROOT}" > /usr/local/bin/prepare-connector && \ - chmod +x /usr/local/bin/prepare-connector && \ - prepare-connector --help >/dev/null && \ - printf '%s\n' "${FLOW_BUILDER_SDK_SHA:?FLOW_BUILDER_SDK_SHA is required}" \ - > ${PREVIEW_FLOW_SDK_ASSETS_ROOT}/flow-builder-sdk.sha - -ENV FLOW_SDK_LIBRARY_JSON=${PREVIEW_FLOW_SDK_ASSETS_ROOT}/typescript/sdk/lib/library-json \ - FLOW_SDK_LIBRARY_MD=${PREVIEW_FLOW_SDK_ASSETS_ROOT}/typescript/sdk/lib/library-md \ - FLOW_SDK_CONNECTORS_DIR=${PREVIEW_FLOW_SDK_ASSETS_ROOT}/connectors - -# This assertion runs after the secret-bearing layer has ended. It proves that -# the pinned SDK resolves and the product CLI can check real source without any -# registry credential at runtime. -COPY tests/fixtures/maestro-flow-sdk-smoke/ /opt/maestro-flow-sdk-smoke/ -RUN cd /opt/maestro-flow-sdk-smoke && \ - ln -s ${PREVIEW_FLOW_SDK_ROOT}/node_modules node_modules && \ - uip maestro flow check Smoke.flow.ts --source diff --git a/tests/docker/maestro-flow-sdk.env b/tests/docker/maestro-flow-sdk.env deleted file mode 100644 index df83bc5cb4..0000000000 --- a/tests/docker/maestro-flow-sdk.env +++ /dev/null @@ -1,2 +0,0 @@ -FLOW_SDK_VERSION=3.20.1-remediation.1 -FLOW_BUILDER_SDK_SHA=21c339a032d52200e0cdf7ad072a769b6a3d65ba diff --git a/tests/scripts/stage-preview-sdk-workspace.sh b/tests/scripts/stage-preview-sdk-workspace.sh index c627bca5e3..d3c9da0b03 100755 --- a/tests/scripts/stage-preview-sdk-workspace.sh +++ b/tests/scripts/stage-preview-sdk-workspace.sh @@ -4,7 +4,9 @@ set -euo pipefail sdk_root=${PREVIEW_FLOW_SDK_ROOT:-/opt/preview-flow-sdk} assets_root=${PREVIEW_FLOW_SDK_ASSETS_ROOT:-/opt/preview-flow-sdk-assets} package_dir=$sdk_root/node_modules/@uipath/flow-sdk -library_json=${FLOW_SDK_LIBRARY_JSON:-$assets_root/typescript/sdk/lib/library-json} +library_json=${FLOW_SDK_LIBRARY_JSON:-$assets_root/library-json} +registry_root=${UIP_MAESTRO_REGISTRY_HOME:-$assets_root/registry} +registry_current=$registry_root/current.json [[ -f "$package_dir/package.json" ]] || { echo "stage-preview-sdk-workspace: missing $package_dir/package.json" >&2 @@ -18,10 +20,8 @@ library_json=${FLOW_SDK_LIBRARY_JSON:-$assets_root/typescript/sdk/lib/library-js echo "stage-preview-sdk-workspace: missing connector library index at $library_json" >&2 exit 1 } - -flow_builder_sdk_commit=$(tr -d '[:space:]' < "$assets_root/flow-builder-sdk.sha") -[[ -n "$flow_builder_sdk_commit" ]] || { - echo "stage-preview-sdk-workspace: missing flow-builder-sdk provenance" >&2 +[[ -f "$registry_current" ]] || { + echo "stage-preview-sdk-workspace: missing connector registry provenance at $registry_current" >&2 exit 1 } @@ -39,12 +39,27 @@ const fs = require('node:fs'); console.log(JSON.parse(fs.readFileSync(process.argv[2], 'utf8')).version); NODE ) +sdk_gitref=$(node - "$package_dir/package.json" <<'NODE' +const fs = require('node:fs'); +console.log(JSON.parse(fs.readFileSync(process.argv[2], 'utf8')).gitref || ''); +NODE +) +registry_hash=$(node - "$registry_current" <<'NODE' +const fs = require('node:fs'); +const {libraryHash} = JSON.parse(fs.readFileSync(process.argv[2], 'utf8')); +if (typeof libraryHash !== 'string' || !libraryHash) { + throw new Error('connector registry provenance is missing libraryHash'); +} +console.log(libraryHash); +NODE +) ln -s "$sdk_root/node_modules" node_modules -node - "$sdk_version" "$flow_builder_sdk_commit" <<'NODE' +node - "$sdk_version" "$sdk_gitref" "$registry_hash" <<'NODE' const fs = require('node:fs'); const version = process.argv[2]; -const flowBuilderSdkCommit = process.argv[3]; +const gitref = process.argv[3]; +const connectorLibraryHash = process.argv[4]; let packageJson = {}; if (fs.existsSync('package.json')) { packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')); @@ -61,14 +76,14 @@ fs.writeFileSync( `${JSON.stringify({ package: '@uipath/flow-sdk', version, - flow_builder_sdk_commit: flowBuilderSdkCommit, + gitref: gitref || null, connector_library: true, + connector_library_hash: connectorLibraryHash, }, null, 2)}\n`, ); NODE node -e "import('@uipath/flow-sdk')" uip maestro flow compile --help >/dev/null -prepare-connector --help >/dev/null -printf 'stage-preview-sdk-workspace: @uipath/flow-sdk@%s, flow-builder-sdk@%s, connector library\n' \ - "$sdk_version" "$flow_builder_sdk_commit" +printf 'stage-preview-sdk-workspace: @uipath/flow-sdk@%s, connector library %s\n' \ + "$sdk_version" "$registry_hash" diff --git a/tests/scripts/test_stage_preview_sdk_workspace.py b/tests/scripts/test_stage_preview_sdk_workspace.py index a0eea5f7b3..9625c10f5e 100644 --- a/tests/scripts/test_stage_preview_sdk_workspace.py +++ b/tests/scripts/test_stage_preview_sdk_workspace.py @@ -23,6 +23,7 @@ def test_stages_credential_free_sdk_workspace(tmp_path: Path) -> None: { "name": "@uipath/flow-sdk", "version": "3.20.0", + "gitref": "efd27ce4c90ad76fc7f3c9b67f4920997b2cf0a8", "type": "module", "exports": "./index.js", } @@ -36,8 +37,12 @@ def test_stages_credential_free_sdk_workspace(tmp_path: Path) -> None: (library_json / "index.json").write_text( json.dumps({"entries": [{"nodeType": REQUIRED_CONNECTOR}]}) ) - commit = "efd27ce4c90ad76fc7f3c9b67f4920997b2cf0a8" - (assets_root / "flow-builder-sdk.sha").write_text(f"{commit}\n") + registry_root = assets_root / "registry" + registry_root.mkdir() + registry_hash = "d34db33f" + (registry_root / "current.json").write_text( + json.dumps({"libraryHash": registry_hash}) + ) bin_dir = tmp_path / "bin" bin_dir.mkdir() @@ -45,11 +50,6 @@ def test_stages_credential_free_sdk_workspace(tmp_path: Path) -> None: bin_dir / "uip", "#!/bin/sh\n[ \"$*\" = \"maestro flow compile --help\" ]\n", ) - _write_executable( - bin_dir / "prepare-connector", - "#!/bin/sh\n[ \"$*\" = \"--help\" ]\n", - ) - workspace = tmp_path / "workspace" workspace.mkdir() (workspace / "package.json").write_text( @@ -60,6 +60,7 @@ def test_stages_credential_free_sdk_workspace(tmp_path: Path) -> None: "PATH": f"{bin_dir}:{os.environ['PATH']}", "PREVIEW_FLOW_SDK_ROOT": str(sdk_root), "PREVIEW_FLOW_SDK_ASSETS_ROOT": str(assets_root), + "UIP_MAESTRO_REGISTRY_HOME": str(registry_root), "FLOW_SDK_LIBRARY_JSON": str(library_json), } @@ -82,6 +83,7 @@ def test_stages_credential_free_sdk_workspace(tmp_path: Path) -> None: assert json.loads((workspace / "preview-sdk-provenance.json").read_text()) == { "package": "@uipath/flow-sdk", "version": "3.20.0", - "flow_builder_sdk_commit": commit, + "gitref": "efd27ce4c90ad76fc7f3c9b67f4920997b2cf0a8", "connector_library": True, + "connector_library_hash": registry_hash, }