Era 4 DevOps
An Azure DevOps extension to make logging hours easier.
Development
Local hot reload
Publish the dev extension once through the scriptor through the devops pipeline:
pnpm run publish:dev -- --token YOUR_PAT --share-with YOUR_ORGANIZATION
Not for powershell wrap -- in quotes '--'
Replace YOUR_PAT with an Azure DevOps PAT (personal access token) with the Marketplace (Publish) scope and access set to All accessible organizations, and YOUR_ORGANIZATION with your Azure DevOps organization name. The publisher and extension ID are defined by the manifest and dev configuration. For more information about PATs, see Authenticate access with personal access tokens.
- Install the published extension in your Azure DevOps organization.
- Download the certificate for the extension from its Azure DevOps extension details.
- Copy the certificate value from the downloaded certificate file.
- In the Key Vault used by the ERA API, create or update the
AuthenticationAzureAdEra4DevOpsExtensionKey secret with that value.
Review permissions after deployment
After deploying or updating the DevOps extension, review its permissions in the Azure DevOps organization where it is installed. Confirm that the requested access is expected and approve only the permissions required for the extension's intended functionality. Repeat this review after every deployment, since an update may change the permissions requested.
Debug in VS Code
Start the webpack-dev-server with:
pnpm run start:dev
Open https://localhost:3000 and accept the local certificate.
Keep npm run start:dev running.
Press F5 in VS Code (Firefox launch profile).
Open the extension in Azure DevOps and debug as usual.
Build & publish scripts
Script overview
| Script |
Target extension |
Backend |
pnpm publish:test |
era4devopsTest-test (TEST) |
Remote TEST |
pnpm publish:acc |
era4devopsTest (ACCE) |
Remote ACCE |
pnpm publish:prod |
era4devops (PROD) |
Remote PROD |
pnpm publish:dev |
era4devopsTest-test (TEST) |
localhost:3000 (dev server) |
pnpm start:dev |
— |
Remote TEST backend |
pnpm start:local |
— |
Local backend (localhost:44346) |
publish:dev publishes the TEST extension with baseUri pointing to https://localhost:3000. Run start:dev or start:local alongside it to serve assets locally.
Build/package by environment
pnpm run package:test
pnpm run package:acc
pnpm run package:prod
Publish by environment
pnpm run publish:test
pnpm run publish:acc
pnpm run publish:prod
These publish scripts use scripts/create-publish-overrides.js to generate configs/runtime.publish.json from:
- environment defaults (
configs/test.json, configs/acc.json, configs/prod.json)
- optional runtime overrides:
ExtensionId
ExtensionName
PublishExtensionPublic
ExtensionVersion
When publishing locally without ExtensionVersion set, scripts/bump-patch-version.js is not called automatically — you need to bump the version in vss-extension.json manually before running a publish script.
CI/CD pipeline
Pipeline files:
.azure/azure-pipelines.yml
.azure/templates/environment-deployment.yml
Automatic version numbering
The pipeline determines the next version by querying the VS Marketplace REST API for the extension's currently published version, then incrementing the patch number. This keeps versioning in sync with what is actually live — no pipeline counter drift, no manual seed adjustments, and no commit-back to the repo required.
The logic lives in scripts/bump-patch-version.js:
- On startup it resolves the extension ID from the
ExtensionId environment variable (set per environment by the variable group) or falls back to the id in vss-extension.json.
- It calls
GET https://marketplace.visualstudio.com/_apis/public/gallery/publishers/{publisher}/extensions/{extensionId}?api-version=7.2-preview.1 — authenticated with AzureDevOps_Publish_PAT for private extensions.
- If the extension is already published, it takes
major.minor from vss-extension.json and computes patch = currentPublishedPatch + 1.
- If the extension has never been published (HTTP 404), it uses the version in
vss-extension.json as-is.
- It emits the resolved version as an Azure Pipelines job variable (
ExtensionVersion) consumed by the publish step.
In local mode (when TF_BUILD is not set), the script writes the bumped version back to vss-extension.json. If the Marketplace is unreachable locally, it falls back to incrementing the patch from the manifest.
The version field in vss-extension.json tracks major.minor and is the source of truth for any major or minor bumps — those must be updated manually.
Required variable group variables per environment
AzureDevOps_Publish_PAT (secret)
ExtensionId
ExtensionName
PublishExtensionPublic
⚠️ Version bump before local publish
When publishing locally (outside the pipeline), scripts/bump-patch-version.js queries the Marketplace and writes the bumped version back to vss-extension.json automatically. Run it before the publish script:
node scripts/bump-patch-version.js
If the Marketplace is unreachable, it falls back to incrementing the patch in vss-extension.json directly. Either way, ensure the version has been updated before publishing — the Marketplace will reject a publish if that version already exists.
Common publish failure
If you see a Marketplace error about an existing extension version, the resolved version was already published. This can happen if:
- Pipeline: the Marketplace API call returned a stale or unexpected version. Check the "Determine extension version" step logs.
- Local: run
node scripts/bump-patch-version.js again, or manually increment vss-extension.json version.
Enabling Era 4 DevOps per project
Azure DevOps installs extensions per organization, so the extension is visible in every project. Era 4 DevOps therefore ships with a project allow-list:
- An organization administrator selects the projects in Organization settings > Era 4 DevOps (
src/settings).
- The selection is stored in the extension data service (collection
era4devops-settings, document project-access).
- The hub checks the project of the page. The work item group/tab check the project of the work item itself (
System.TeamProject), because a work item can be opened on a page of another project (src/common/ProjectAccessGate.tsx). In projects that are not enabled they show a single line of small grey text and make no ERA API calls.
- Projects are disabled by default: after releasing this version, enable the projects that should keep using the extension.
- The nav entry and work item tab header remain visible in other projects; hide the work item group/page per process in the process layout editor if needed.
- This is a UI gate only; enforce access in the ERA backend for real protection.
- The extension now requests the
vso.project scope (to list projects), so the organization must re-authorize it on update.