Prepare 0.3.0 release: land SDK migration on master + release automation#22
Conversation
Ports the build-results tab off the deprecated vss-web-extension-sdk (VSS.SDK.js
+ AMD module loader) to the supported azure-devops-extension-sdk v4 +
azure-devops-extension-api v5. TypeScript, plain DOM, self-contained bundle. No
React (not needed). The /mini viewer and behaviour are preserved.
- info.ts / dialog.ts (was info.js / dialog.js): SDK.init/ready; build context
via BuildPageDataService.getBuildPageData(); attachment listing via
BuildRestClient.getAttachments + download from the attachment _links.self.href
with SDK.getAccessToken(); TestingBot API via ServiceEndpointRestClient
.executeServiceEndpointRequest; embed dialog via
IHostPageLayoutService.openCustomDialog (config passed as { url }, read by the
dialog through SDK.getConfiguration()).
- Drop jQuery for plain document.createElement, and Buffer for btoa; the 180-line
inline MD5 is replaced by the blueimp-md5 package. Keeps the Phase 3 pagination
(stable page size, count=0 guard) and error-state fixes.
- HTML now loads the webpack bundle directly (<script src="scripts/info.js">);
VSS.SDK.js and the AMD loader are gone.
- webpack bundles the SDK/API/md5 in (no AMD externals, no buffer polyfill);
@babel/preset-typescript compiles the tab. babel only strips types, so
`npm run typecheck` (tb-build-info/tsconfig.json) type-checks it; wired into
`npm run package` and the CI build workflow.
- Manifest: embed-dialog uri drops ?url={{url}} (config now via the dialog API);
the lib/ (VSS.SDK.js) entry is removed from files.
- eslint: tab files are now .ts (browser globals, typescript-eslint).
Verified statically: npm run typecheck (tab) clean, npm run lint clean,
npm test 8 passing, npm run package builds a vsix whose tab bundle is
self-contained (66KB, no AMD/define, no VSS.SDK.js).
RUNTIME VALIDATION REQUIRED (cannot be exercised outside a live Azure DevOps org):
attachment download via _links.self.href + access token; getBuildPageData()
replacing the old onBuildChanged (one-shot read on load); openCustomDialog
rendering embedDialog.html and passing { url }. Smoke-test the tab in a real org
before release.
Phase 3.2: migrate the results tab to azure-devops-extension-sdk
The Phase 3.2 SDK migration (#21) merged into the phase4-ci-publishing branch, not master, so master was missing it. This branches off that branch to bring it to master, and adds release automation. - Bump version 0.2.7 -> 0.3.0 (single source of truth; stamped into the manifest and both task.json files). Marks the Phases 1-5 modernization release. - Add .github/workflows/release.yml: on a v* tag (or manual dispatch) it builds the .vsix and attaches it to a *draft* GitHub Release. It does NOT publish to the Marketplace. - Make publish.yml a deliberate manual action (workflow_dispatch only) instead of firing on tags, so tagging a release never auto-publishes to Azure DevOps. Contains the Phase 3.2 tab migration (already reviewed in #21): the results tab on azure-devops-extension-sdk v4 + azure-devops-extension-api v5. Verified: lint, typecheck, tests (8 passing) and `npm run package` all green; produces testingbot.testingbot-tasks-0.3.0.vsix.
📝 WalkthroughWalkthroughThe results tab and dialog were migrated from AMD JavaScript to self-contained TypeScript bundles. Extension packaging, linting, documentation, and dependency versions were updated, alongside CI typechecking and tag-based draft release automation. ChangesResults Tab Modernization
Release Automation
Sequence Diagram(s)sequenceDiagram
participant BuildWorkflow
participant TypeScriptCompiler
participant TestSuite
participant ReleaseWorkflow
participant GitHubRelease
BuildWorkflow->>TypeScriptCompiler: run npm run typecheck
TypeScriptCompiler-->>BuildWorkflow: typecheck result
BuildWorkflow->>TestSuite: run tests
ReleaseWorkflow->>ReleaseWorkflow: install dependencies and package VSIX
ReleaseWorkflow->>GitHubRelease: create draft release with Packages/*.vsix
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/publish.yml (1)
16-18: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winSynchronize the remaining publication documentation.
README.md:70-72andMODERNIZATION.md:26-30still instruct maintainers that pushing av*tag publishes to the Marketplace, but this workflow is now manual-only. Update those references to describeworkflow_dispatch.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/publish.yml around lines 16 - 18, Update the publication instructions in README.md and MODERNIZATION.md to state that Marketplace publishing is triggered manually via workflow_dispatch, replacing references to pushing a v* tag while preserving the surrounding documentation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/release.yml:
- Around line 14-19: Add workflow-level concurrency configuration in release.yml
keyed by the workflow name and ref, and set cancel-in-progress to false. Place
it alongside the top-level permissions/jobs configuration so release runs for
the same ref serialize without canceling earlier runs.
- Line 21: Update the release workflow action references for actions/checkout,
actions/setup-node, and softprops/action-gh-release from version tags to
reviewed, full-length commit SHA pins. Preserve each action’s current version
and configuration while ensuring all release actions are immutable.
- Around line 9-12: Add a required tag input to the workflow_dispatch
configuration, then use that input as the release tag_name and checkout ref for
manual runs. Keep push-triggered releases using their existing tag ref, while
ensuring manually triggered builds and releases target the explicitly provided
v* tag.
In `@tb-build-info/scripts/info.ts`:
- Around line 223-241: Handle startup failures in both entry points: in
tb-build-info/scripts/info.ts, move SDK.init() and SDK.ready() into main()’s
try/catch, and ensure the main() invocation is awaited or has a terminal
rejection handler; in tb-build-info/scripts/dialog.ts, likewise handle the
fired-and-forgotten main() invocation and await SDK.notifyLoadSucceeded() within
its flow. Preserve the existing success and error behavior.
---
Outside diff comments:
In @.github/workflows/publish.yml:
- Around line 16-18: Update the publication instructions in README.md and
MODERNIZATION.md to state that Marketplace publishing is triggered manually via
workflow_dispatch, replacing references to pushing a v* tag while preserving the
surrounding documentation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: bdc68a4c-c3e9-481e-a60d-36c09732a4f4
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (18)
.github/workflows/build.yml.github/workflows/publish.yml.github/workflows/release.ymlCLAUDE.mdMODERNIZATION.mdeslint.config.mjspackage.jsontb-build-info/embedDialog.htmltb-build-info/infoTab.htmltb-build-info/scripts/dialog.jstb-build-info/scripts/dialog.tstb-build-info/scripts/info.jstb-build-info/scripts/info.tstb-build-info/tsconfig.jsontb-main/package.jsontb-stop-tunnel/package.jsonvss-extension.jsonwebpack.config.js
💤 Files with no reviewable changes (2)
- tb-build-info/scripts/dialog.js
- tb-build-info/scripts/info.js
| on: | ||
| push: | ||
| tags: [ 'v*' ] | ||
| workflow_dispatch: |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the workflow file and any nearby release-related files.
git ls-files .github/workflows/release.yml .github/workflows || true
echo '--- release.yml ---'
cat -n .github/workflows/release.yml
echo '--- search for gh-release usage / tag_name / workflow_dispatch inputs ---'
rg -n "softprops/action-gh-release|tag_name|workflow_dispatch|inputs:|release" .github/workflows -SRepository: testingbot/vso-testingbot-plugin
Length of output: 2506
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the exact workflow structure if the first pass is inconclusive.
python3 - <<'PY'
from pathlib import Path
p = Path('.github/workflows/release.yml')
print(p.read_text())
PYRepository: testingbot/vso-testingbot-plugin
Length of output: 1148
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the release workflow with line numbers and surrounding context for the gh-release step.
sed -n '1,220p' .github/workflows/release.ymlRepository: testingbot/vso-testingbot-plugin
Length of output: 1147
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check how workflow_dispatch behaves in GitHub Actions docs locally unavailable;
# instead inspect whether the workflow explicitly binds a ref for release creation.
rg -n "ref:|tag_name:|softprops/action-gh-release|workflow_dispatch" .github/workflows/release.yml -n -A4 -B4Repository: testingbot/vso-testingbot-plugin
Length of output: 850
Provide an explicit tag for manual releases.
workflow_dispatch falls back to the triggering ref, so a manual run from a branch can create a release for that branch instead of a v* tag. Add a required dispatch input and pass it to tag_name (and checkout that ref for the build) so manual releases target the intended tag.
🧰 Tools
🪛 YAMLlint (1.37.1)
[warning] 9-9: truthy value should be one of [false, true]
(truthy)
[error] 11-11: too many spaces inside brackets
(brackets)
[error] 11-11: too many spaces inside brackets
(brackets)
🪛 zizmor (1.26.1)
[warning] 9-12: insufficient job-level concurrency limits (concurrency-limits): workflow is missing concurrency setting
(concurrency-limits)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/release.yml around lines 9 - 12, Add a required tag input
to the workflow_dispatch configuration, then use that input as the release
tag_name and checkout ref for manual runs. Keep push-triggered releases using
their existing tag ref, while ensuring manually triggered builds and releases
target the explicitly provided v* tag.
| permissions: | ||
| contents: write | ||
|
|
||
| jobs: | ||
| release: | ||
| runs-on: ubuntu-latest |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Serialize release runs by ref.
Add a workflow-level concurrency group keyed by the workflow and ref, with cancel-in-progress: false, so repeated manual runs or tag retries cannot race while creating or updating the same draft release.
🧰 Tools
🪛 zizmor (1.26.1)
[error] 15-15: overly broad permissions (excessive-permissions): contents: write is overly broad at the workflow level
(excessive-permissions)
[warning] 15-15: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment
(undocumented-permissions)
[info] 18-18: workflow or action definition without a name (anonymous-definition): this job
(anonymous-definition)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/release.yml around lines 14 - 19, Add workflow-level
concurrency configuration in release.yml keyed by the workflow name and ref, and
set cancel-in-progress to false. Place it alongside the top-level
permissions/jobs configuration so release runs for the same ref serialize
without canceling earlier runs.
Source: Linters/SAST tools
| release: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
sed -n '1,120p' .github/workflows/release.yml | cat -nRepository: testingbot/vso-testingbot-plugin
Length of output: 1434
Pin the release workflow actions to commit SHAs
actions/checkout@v4, actions/setup-node@v4, and softprops/action-gh-release@v2 are tag refs, not immutable pins. Because this workflow has contents: write, pin them to reviewed full-length commit SHAs before shipping release artifacts.
🧰 Tools
🪛 zizmor (1.26.1)
[error] 21-21: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/release.yml at line 21, Update the release workflow action
references for actions/checkout, actions/setup-node, and
softprops/action-gh-release from version tags to reviewed, full-length commit
SHA pins. Preserve each action’s current version and configuration while
ensuring all release actions are immutable.
Source: Linters/SAST tools
| async function main(): Promise<void> { | ||
| await SDK.init({ loaded: false }); | ||
| await SDK.ready(); | ||
|
|
||
| buildClient = getClient(BuildRestClient); | ||
| endpointClient = getClient(ServiceEndpointRestClient); | ||
| projectId = SDK.getWebContext().project.id; | ||
|
|
||
| try { | ||
| await load(); | ||
| } catch (err) { | ||
| console.error('error', err); | ||
| renderMessage('Could not load TestingBot results.'); | ||
| } finally { | ||
| SDK.notifyLoadSucceeded(); | ||
| } | ||
| } | ||
|
|
||
| main(); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== File list ==\n'
git ls-files 'tb-build-info/scripts/*'
printf '\n== info.ts around main ==\n'
nl -ba tb-build-info/scripts/info.ts | sed -n '190,260p'
printf '\n== dialog.ts around main ==\n'
nl -ba tb-build-info/scripts/dialog.ts | sed -n '1,120p'
printf '\n== Search for SDK lifecycle calls ==\n'
rg -n "notifyLoadSucceeded|notifyLoadFailed|main\(\)|SDK\.init|SDK\.ready|Promise\.catch|catch\(" tb-build-info/scriptsRepository: testingbot/vso-testingbot-plugin
Length of output: 316
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
for p in [Path('tb-build-info/scripts/info.ts'), Path('tb-build-info/scripts/dialog.ts')]:
print(f"\n== {p} ==")
text = p.read_text()
for i, line in enumerate(text.splitlines(), 1):
if 'main();' in line or 'notifyLoadSucceeded' in line or 'notifyLoadFailed' in line or 'SDK.init' in line or 'SDK.ready' in line:
start = max(1, i-5)
end = min(len(text.splitlines()), i+5)
for j in range(start, end+1):
print(f"{j:4d}: {text.splitlines()[j-1]}")
print()
PYRepository: testingbot/vso-testingbot-plugin
Length of output: 2331
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
for p in [Path('tb-build-info/scripts/info.ts'), Path('tb-build-info/scripts/dialog.ts')]:
print(f"\n== {p} ==")
lines = p.read_text().splitlines()
for needle in ['async function main', 'SDK.init', 'SDK.ready', 'notifyLoadSucceeded', 'notifyLoadFailed', 'main();']:
hits = [i+1 for i,l in enumerate(lines) if needle in l]
for i in hits:
start = max(1, i-5)
end = min(len(lines), i+5)
print(f"-- context around line {i} ({needle}) --")
for j in range(start, end+1):
print(f"{j:4d}: {lines[j-1]}")
print()
PYRepository: testingbot/vso-testingbot-plugin
Length of output: 3530
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== package manifests mentioning azure-devops-extension-sdk ==\n'
rg -n '"azure-devops-extension-sdk"|azure-devops-extension-sdk' --glob 'package*.json' --glob '!**/node_modules/**'
printf '\n== repo references to notifyLoadSucceeded ==\n'
rg -n "notifyLoadSucceeded" --glob '!**/node_modules/**'
printf '\n== tsconfig / typings hints for tb-build-info ==\n'
rg -n '"types"|typeRoots|azure-devops-extension-sdk' tb-build-info package*.json tsconfig*.json --glob '!**/node_modules/**'Repository: testingbot/vso-testingbot-plugin
Length of output: 360
🌐 Web query:
azure-devops-extension-sdk notifyLoadSucceeded return type
💡 Result:
The return type of the notifyLoadSucceeded method in the azure-devops-extension-sdk is a Promise [1][2]. This method is used to notify the Azure DevOps host that the extension has successfully finished loading and that its UI is ready to be displayed, which is particularly useful when the SDK is initialized with { loaded: false } [1][2][3].
Citations:
- 1: https://learn.microsoft.com/en-us/javascript/api/azure-devops-extension-sdk/
- 2: https://context7.com/microsoft/azure-devops-extension-sdk/llms.txt
- 3: https://github.com/microsoft/azure-devops-extension-sdk/blob/master/README.md?plain=1
Handle the startup promises in both entry points.
tb-build-info/scripts/info.ts:SDK.init()/SDK.ready()happen outside thetry, andmain()is never awaited or caught, so startup failures can surface as unhandled rejections.tb-build-info/scripts/dialog.ts:main()is also fired-and-forgotten, andSDK.notifyLoadSucceeded()returns a promise here as well, so it should be awaited or caught.
📍 Affects 2 files
tb-build-info/scripts/info.ts#L223-L241(this comment)tb-build-info/scripts/dialog.ts#L34-L37
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tb-build-info/scripts/info.ts` around lines 223 - 241, Handle startup
failures in both entry points: in tb-build-info/scripts/info.ts, move SDK.init()
and SDK.ready() into main()’s try/catch, and ensure the main() invocation is
awaited or has a terminal rejection handler; in tb-build-info/scripts/dialog.ts,
likewise handle the fired-and-forgotten main() invocation and await
SDK.notifyLoadSucceeded() within its flow. Preserve the existing success and
error behavior.
Why
The Phase 3.2 SDK migration (#21) merged into the
phase4-ci-publishingbranch (its stacked base), notmaster— somastercurrently still has the oldVSS.SDK/AMD/jQuery tab. This PR lands that work onmasterand adds release automation.What changed
0.2.7→0.3.0(single source of truth; stamped into the manifest + bothtask.jsons). Marks the Phases 1–5 modernization release.release.yml(new): on av*tag or manual dispatch, builds the.vsixand attaches it to a draft GitHub Release. Does not publish to the Marketplace.publish.yml: changed toworkflow_dispatch-only, so tagging a release never auto-publishes to Azure DevOps — marketplace publishing is now a deliberate manual action.Also brings in the Phase 3.2 tab migration already reviewed in #21 (
azure-devops-extension-sdkv4 +azure-devops-extension-apiv5, TypeScript, self-contained bundle).Verified
npm run lint,npm run typecheck,npm test(8 passing) andnpm run packageall green →testingbot.testingbot-tasks-0.3.0.vsix.Summary by CodeRabbit