Skip to content

Rteco 813 azure supportability with node js version 22 #69

Rteco 813 azure supportability with node js version 22

Rteco 813 azure supportability with node js version 22 #69

# End-to-end plugin tests — the automated equivalent of the manual sanity
# process described in the release guide.
#
# FLOW (hybrid manual + automated)
# --------------------------------
# 1. build : Build .vsix from the PR branch and upload it
# as a workflow artifact. Embeds clear, one-click
# install instructions into the run summary.
#
# 2. (MANUAL) install : You download the .vsix from the run page and
# upload it via the Marketplace UI. Roughly 3
# minutes, only required when you actually want
# to test the PR's code changes against the
# dev org. Re-trigger the workflow afterwards
# (or just let jobs 3/4 run — they always test
# whatever is currently installed).
#
# 3. sanity-tests : Trigger the ADO sanity pipeline
# (.pipelines/ado-sanity-pipeline.yml).
# Covers: ToolsInstaller, GenericArtifacts,
# Maven, PublishBuildInfo, DiscardBuilds.
#
# 4. oidc-tests : Trigger the OIDC test pipeline.
# Covers all task types with OIDC + non-OIDC.
#
# WHY THE HYBRID?
# ---------------
# Publishing the .vsix to the Marketplace + installing it into the dev org
# is doable from CI, but Microsoft's Marketplace has several long-standing
# quirks (eventual consistency on unpublish, permanent tombstoning of
# (publisher, extension-id) pairs, async validation that races install)
# that make it unreliable to drive from a tight CI loop. Doing the publish
# step manually from the UI is reliable, takes ~3 minutes, and only needs
# to happen when you actually want to test new task code. The ADO test
# pipelines themselves run automatically against whatever is installed.
#
# REQUIRED GITHUB SECRETS
# -----------------------
# ADO_E2E_ORG ADO organisation name (publisher host + test host)
# ADO_E2E_PAT PAT with scopes:
# • Build: Read + Execute
# • Project and Team: Read
# (Marketplace scopes no longer needed in CI —
# you publish via the UI.)
# ADO_SANITY_PROJECT ADO project containing the sanity pipeline
# ADO_SANITY_PIPELINE_ID Definition ID of .pipelines/ado-sanity-pipeline.yml
# ADO_OIDC_PROJECT ADO project containing the OIDC test pipeline
# ADO_OIDC_PIPELINE_ID Definition ID of the OIDC test pipeline
#
# ONE-TIME SETUP (per dev org — done once, not per PR)
# ----------------------------------------------------
# 1. Create a Marketplace publisher: https://aka.ms/vsm-create-publisher
# Name it exactly "<ADO_E2E_ORG>-private"
# 2. Import .pipelines/ado-sanity-pipeline.yml into ADO as a pipeline.
# Note the definition ID → set as ADO_SANITY_PIPELINE_ID secret.
# 3. Create the OIDC test pipeline (see docs/oidc-test-setup.md).
# Note the definition ID → set as ADO_OIDC_PIPELINE_ID secret.
name: E2E Plugin Tests
on:
# Run automatically when a PR receives the "safe to test" label.
pull_request_target:
types: [labeled]
# Allow manual triggering from the Actions UI for debugging.
workflow_dispatch:
inputs:
timeout_minutes:
description: 'Max minutes to wait for each ADO pipeline (default: 60)'
required: false
default: '60'
skip_oidc:
description: 'Skip OIDC tests (true/false)'
required: false
default: 'false'
skip_pipelines:
description: 'Only build the .vsix, do not trigger ADO pipelines'
required: false
default: 'false'
# One E2E run at a time per PR. A new label-trigger on the same PR cancels
# the previous queued/in-progress run so the latest commit is always tested.
# Across different PRs, runs are independent and execute in parallel.
concurrency:
group: e2e-plugin-tests-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
# ======================================================================
# JOB 1 — Build the .vsix from the PR branch and upload as artifact.
# This also validates that the PR compiles cleanly end-to-end.
# ======================================================================
jobs:
build:
name: '1. Build .vsix'
if: |
contains(github.event.pull_request.labels.*.name, 'safe to test') ||
github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 20
outputs:
vsix_version: ${{ steps.build_vsix.outputs.vsix_version }}
vsix_name: ${{ steps.build_vsix.outputs.vsix_name }}
steps:
- name: Checkout PR branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Install dependencies and compile
# build.js installs all task node_modules and compiles TypeScript.
# A failure here is a real PR failure (broken build).
run: npm install --no-fund
- name: Rewrite task GUIDs and names for private build
# The public JFrog extension uses the same task GUIDs and names as
# our source tree. Installing both in the same ADO org causes two
# problems:
# 1. Marketplace rejects publishing a .vsix whose task GUIDs are
# already owned by another extension ("already been uploaded by
# extension 'jfrog-azure-devops-extension'").
# 2. ADO rejects queuing a pipeline run when two installed
# extensions export tasks with the same name ("task name
# JFrogToolsInstaller is ambiguous").
# We fix both by rewriting every task.json:
# • id → deterministic UUIDv5 (namespace + original GUID)
# stable across CI runs; unique vs the public extension.
# • name → original name + "E2E" suffix
# e.g. JFrogToolsInstaller → JFrogToolsInstallerE2E
# The .pipelines/ado-*.yml files reference the E2E names
# so the test pipelines always target this private build,
# never the public one.
run: |
set -euo pipefail
python3 - <<'PYEOF'
import json, uuid, glob
# Fixed namespace — changing this would change all GUIDs.
NS = uuid.UUID('e2e1a2b3-c4d5-6789-abcd-ef0123456789')
paths = sorted(glob.glob('tasks/*/task.json'))
for path in paths:
data = json.load(open(path))
orig_id = data.get('id', '')
orig_name = data.get('name', '')
if not orig_id:
continue
data['id'] = str(uuid.uuid5(NS, orig_id))
data['name'] = orig_name + 'E2E'
json.dump(data, open(path, 'w'), indent=2)
print(f" {orig_name} → {data['name']} | {orig_id} → {data['id']} ({path})")
print(f"Rewrote {len(paths)} task GUIDs and names.")
PYEOF
- name: Package .vsix
id: build_vsix
env:
# Traceable version so we can correlate the installed extension
# back to this exact PR run.
VSIX_VERSION: "0.${{ github.event.pull_request.number || 0 }}.${{ github.run_number }}"
# Bake the real destination publisher into the .vsix at build time.
# Marketplace rejects uploads if the manifest publisher and the
# destination publisher don't match exactly, so we cannot use a
# generic placeholder here.
# ADO_E2E_PUBLISHER_ORG lets you decouple the Marketplace publisher
# org from the ADO test-run org (ADO_E2E_ORG). If not set, falls
# back to ADO_E2E_ORG (the original behaviour for single-org setups).
PUBLISHER: "${{ secrets.ADO_E2E_PUBLISHER_ORG || secrets.ADO_E2E_ORG }}-private"
# Bake a distinct extension id into the .vsix so the upload doesn't
# collide with Marketplace's permanent record of the public id
# "jfrog-azure-devops-extension" (which gets tombstoned forever in
# every publisher that has ever hosted it, blocking subsequent
# creates with "The extension already exists"). Tasks inside the
# .vsix are referenced by their own GUIDs so a different extension
# id is transparent to the ADO pipelines that consume them.
# Override via the ADO_E2E_EXTENSION_ID secret if this id ever gets
# tombstoned in your publisher — pick any unused string, e.g.
# "jfrog-azure-devops-extension-e2etest-v2".
EXTENSION_ID: "${{ secrets.ADO_E2E_EXTENSION_ID || 'jfrog-azure-devops-extension-e2etest' }}"
run: |
set -euo pipefail
if [ -z "${PUBLISHER:-}" ] || [ "$PUBLISHER" = "-private" ]; then
echo "ERROR: ADO_E2E_ORG secret is not set — cannot bake publisher into .vsix." >&2
exit 1
fi
cp vss-extension.json vss-extension-e2e.json
# Rewrite publisher + id in the manifest copy so the resulting .vsix
# can be uploaded straight to the matching Marketplace publisher.
sed -i.bak "s/\"publisher\": *\"[^\"]*\"/\"publisher\": \"$PUBLISHER\"/" vss-extension-e2e.json
sed -i.bak "s/\"id\": *\"jfrog-azure-devops-extension\"/\"id\": \"$EXTENSION_ID\"/" vss-extension-e2e.json
rm -f vss-extension-e2e.json.bak
npx tfx extension create \
--manifest-globs vss-extension-e2e.json \
--publisher "$PUBLISHER" \
--override "{\"public\": false, \"version\": \"$VSIX_VERSION\"}"
vsix_path="$(ls -1 ./*.vsix | head -1)"
vsix_name="$(basename "$vsix_path")"
echo "vsix_name=$vsix_name" >> "$GITHUB_OUTPUT"
echo "vsix_version=$VSIX_VERSION" >> "$GITHUB_OUTPUT"
echo "extension_id=$EXTENSION_ID" >> "$GITHUB_OUTPUT"
echo "Built: $vsix_path ($(du -m "$vsix_path" | cut -f1)MB) — publisher=$PUBLISHER id=$EXTENSION_ID"
- name: Upload .vsix as workflow artifact
uses: actions/upload-artifact@v4
with:
name: jfrog-ext-vsix-${{ github.run_number }}
path: '*.vsix'
retention-days: 14
if-no-files-found: error
- name: Write install instructions to job summary
env:
VSIX_VERSION: ${{ steps.build_vsix.outputs.vsix_version }}
VSIX_NAME: ${{ steps.build_vsix.outputs.vsix_name }}
EXTENSION_ID: ${{ steps.build_vsix.outputs.extension_id }}
ADO_ORG: ${{ secrets.ADO_E2E_ORG }}
run: |
{
echo "## Manual install step (≈3 min, only when testing PR code)"
echo ""
echo "**This PR built:** \`$VSIX_NAME\` (version \`$VSIX_VERSION\`, extension id \`$EXTENSION_ID\`)"
echo ""
echo "If you want the ADO test pipelines (jobs 2 & 3) to actually exercise this PR's code, do the following. If you skip this step the pipelines still run, but against whatever .vsix was installed previously."
echo ""
echo "1. **Download the .vsix** from the *Artifacts* section at the bottom of this run page (artifact: \`jfrog-ext-vsix-${{ github.run_number }}\`). It downloads as a .zip wrapper — unzip it to get the actual .vsix."
echo "2. **Upload to your publisher** at https://marketplace.visualstudio.com/manage/publishers/${ADO_ORG}-private"
echo " - **First time uploading this extension id:** click \`+ New extension\` → \`Azure DevOps\` → drop the .vsix."
echo " - **Subsequent uploads (extension already shows in the publisher row):** click the row's \`···\` menu → \`Update\` → drop the new .vsix. The version is auto-bumped per CI run so updates land cleanly."
echo "3. **Share with the dev org:** \`···\` → \`Share/Unshare\` → add \`${ADO_ORG}\`. (Only needed once per extension id.)"
echo "4. **Install in the dev org:** open https://${ADO_ORG}.visualstudio.com → Settings → Extensions → the shared extension → \`Get it free\` → install into the test project. (Only needed once per extension id.)"
echo "5. **Re-run jobs 2 & 3** from this run page if you want them to test the just-uploaded .vsix; otherwise let them finish against whatever was installed before."
echo ""
echo "If you still see \`The extension already exists\` on step 2, the id \`$EXTENSION_ID\` has been tombstoned in your publisher. Set the \`ADO_E2E_EXTENSION_ID\` repository secret to any unused string (e.g. \`$EXTENSION_ID-v2\`) and re-run the workflow — the next .vsix will use the new id."
} >> "$GITHUB_STEP_SUMMARY"
- name: Print install instructions to log
env:
VSIX_VERSION: ${{ steps.build_vsix.outputs.vsix_version }}
EXTENSION_ID: ${{ steps.build_vsix.outputs.extension_id }}
ADO_ORG: ${{ secrets.ADO_E2E_ORG }}
run: |
cat <<EOF
============================================================
BUILD OK — version=$VSIX_VERSION id=$EXTENSION_ID
============================================================
To exercise THIS PR's code in jobs 2 & 3, manually:
1. Download the 'jfrog-ext-vsix-${{ github.run_number }}'
artifact from this run page; unzip to get the .vsix.
2. Upload at:
https://marketplace.visualstudio.com/manage/publishers/${ADO_ORG}-private
(use 'New extension' the first time, '... -> Update'
on the row afterwards.)
3. Share with org '${ADO_ORG}' and install in the
test project (only on first upload).
4. Re-run jobs 2 & 3 from this run page to pick up the
freshly installed version.
============================================================
EOF
# ======================================================================
# JOB 2 — Sanity tests (non-OIDC, mirrors manual release sanity check).
# Runs against whatever .vsix is currently installed in the dev org.
# Runs in parallel with Job 3 after Job 1 completes.
# ======================================================================
sanity-tests:
name: '2. Sanity Tests (Maven + BuildInfo)'
needs: build
if: ${{ github.event.inputs.skip_pipelines != 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Wait for Marketplace extension to propagate
run: |
echo "Waiting 420s (7 min) for Marketplace to verify and ADO to auto-update the extension..."
sleep 420
echo "Done — proceeding to version check."
- name: Verify extension version on Marketplace
env:
MARKETPLACE_PAT: ${{ secrets.ADO_E2E_MARKETPLACE_PAT }}
PUBLISHER: "${{ secrets.ADO_E2E_PUBLISHER_ORG || secrets.ADO_E2E_ORG }}-private"
EXTENSION_ID: "${{ secrets.ADO_E2E_EXTENSION_ID || 'jfrog-azure-devops-extension-e2etest' }}"
EXPECTED_VERSION: ${{ needs.build.outputs.vsix_version }}
run: |
if [ -z "${MARKETPLACE_PAT:-}" ]; then
echo "ADO_E2E_MARKETPLACE_PAT not set — cannot query Marketplace."
echo "Expected version (built this run): $EXPECTED_VERSION"
exit 0
fi
TFX_OUT=$(npx --yes tfx-cli extension show \
--publisher "$PUBLISHER" \
--extension-id "$EXTENSION_ID" \
--token "$MARKETPLACE_PAT" 2>&1 || true)
echo "tfx output:"
echo "$TFX_OUT"
INSTALLED=$(echo "$TFX_OUT" | python3 -c "import sys,json; raw=sys.stdin.read(); s=raw.find('{'); d=json.loads(raw[s:]) if s>=0 else {}; v=d.get('versions',[]); print(v[0]['version'] if v else 'not-found')" 2>/dev/null || echo 'not-found')
[ -z "$INSTALLED" ] && INSTALLED='not-found'
echo "============================================================"
echo " Expected version (built this run) : $EXPECTED_VERSION"
echo " Published version on Marketplace : $INSTALLED"
if [ "$INSTALLED" = "$EXPECTED_VERSION" ]; then
echo " MATCH — tests will run against the current PR build."
else
echo " MISMATCH — Marketplace has $INSTALLED but expected $EXPECTED_VERSION."
echo " The publish step may have been skipped, or propagation took longer than 7 minutes."
echo " Stopping to avoid running tests against a stale build."
echo "============================================================"
exit 1
fi
echo "============================================================"
- name: Checkout (for trigger script)
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.sha || github.sha }}
- name: Trigger ADO sanity pipeline and wait for result
# The sanity pipeline (.pipelines/ado-sanity-pipeline.yml) tests:
# JFrogToolsInstaller, GenericArtifacts (upload+download),
# JFrogMaven (resolve+deploy), PublishBuildInfo, DiscardBuilds.
env:
ADO_ORG: ${{ secrets.ADO_E2E_ORG }}
ADO_PROJECT: ${{ secrets.ADO_SANITY_PROJECT }}
ADO_PIPELINE_ID: ${{ secrets.ADO_SANITY_PIPELINE_ID }}
ADO_PAT: ${{ secrets.ADO_E2E_PAT }}
GH_PR_NUMBER: ${{ github.event.pull_request.number || 0 }}
GH_COMMIT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
TIMEOUT_MINUTES: ${{ github.event.inputs.timeout_minutes || '60' }}
run: bash buildScripts/trigger-ado-pipeline.sh
# ======================================================================
# JOB 3 — OIDC tests (all task types with OIDC + non-OIDC regression).
# Runs against whatever .vsix is currently installed in the dev org.
# Runs in parallel with Job 2 after Job 1 completes.
# ======================================================================
oidc-tests:
name: '3. OIDC Tests (all task types)'
needs: build
if: ${{ github.event.inputs.skip_pipelines != 'true' && github.event.inputs.skip_oidc != 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Wait for Marketplace extension to propagate
run: |
echo "Waiting 420s (7 min) for Marketplace to verify and ADO to auto-update the extension..."
sleep 420
echo "Done — proceeding to version check."
- name: Verify extension version on Marketplace
env:
MARKETPLACE_PAT: ${{ secrets.ADO_E2E_MARKETPLACE_PAT }}
PUBLISHER: "${{ secrets.ADO_E2E_PUBLISHER_ORG || secrets.ADO_E2E_ORG }}-private"
EXTENSION_ID: "${{ secrets.ADO_E2E_EXTENSION_ID || 'jfrog-azure-devops-extension-e2etest' }}"
EXPECTED_VERSION: ${{ needs.build.outputs.vsix_version }}
run: |
if [ -z "${MARKETPLACE_PAT:-}" ]; then
echo "ADO_E2E_MARKETPLACE_PAT not set — cannot query Marketplace."
echo "Expected version (built this run): $EXPECTED_VERSION"
exit 0
fi
TFX_OUT=$(npx --yes tfx-cli extension show \
--publisher "$PUBLISHER" \
--extension-id "$EXTENSION_ID" \
--token "$MARKETPLACE_PAT" 2>&1 || true)
echo "tfx output:"
echo "$TFX_OUT"
INSTALLED=$(echo "$TFX_OUT" | python3 -c "import sys,json; raw=sys.stdin.read(); s=raw.find('{'); d=json.loads(raw[s:]) if s>=0 else {}; v=d.get('versions',[]); print(v[0]['version'] if v else 'not-found')" 2>/dev/null || echo 'not-found')
[ -z "$INSTALLED" ] && INSTALLED='not-found'
echo "============================================================"
echo " Expected version (built this run) : $EXPECTED_VERSION"
echo " Published version on Marketplace : $INSTALLED"
if [ "$INSTALLED" = "$EXPECTED_VERSION" ]; then
echo " MATCH — tests will run against the current PR build."
else
echo " MISMATCH — Marketplace has $INSTALLED but expected $EXPECTED_VERSION."
echo " The publish step may have been skipped, or propagation took longer than 7 minutes."
echo " Stopping to avoid running tests against a stale build."
echo "============================================================"
exit 1
fi
echo "============================================================"
- name: Checkout (for trigger script)
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.sha || github.sha }}
- name: Trigger ADO OIDC test pipeline and wait for result
# The OIDC pipeline covers:
# A1-A12: all task types with OIDC service connections
# D1-D5: non-OIDC regression guard (API token + basic auth)
# I1: multi-task chain (GenericArtifacts + PublishBuildInfo + Audit)
env:
ADO_ORG: ${{ secrets.ADO_E2E_ORG }}
ADO_PROJECT: ${{ secrets.ADO_OIDC_PROJECT }}
ADO_PIPELINE_ID: ${{ secrets.ADO_OIDC_PIPELINE_ID }}
ADO_PAT: ${{ secrets.ADO_E2E_PAT }}
GH_PR_NUMBER: ${{ github.event.pull_request.number || 0 }}
GH_COMMIT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
TIMEOUT_MINUTES: ${{ github.event.inputs.timeout_minutes || '60' }}
run: bash buildScripts/trigger-ado-pipeline.sh