A customized proxy builder to build and publish upm package using Azure Pipelines.
The pipeline uses a containerized BuildPackage stage and then routes the
packed tarball into one of two publish stages:
- normal publish:
PublishPackagepublishes to OpenUPM - e2e publish:
PublishE2EPackagepublishes to a local Verdaccio registry
Both publish stages consume the tarball artifact and use
npm publish --ignore-scripts.
This keeps compatibility with packages that rely on prepack or
prepublishOnly while keeping the OpenUPM publish credential out of the
untrusted build stage.
Prepare an npm service connection:
- Visit https://dev.azure.com/openupm/openupm
- Project settings > Service connections > New service connection > npm
- Connection name,
openupm - Registry URL,
https://package.openupm.com - Personal Token...
Required parameters payload for the default git source mode:
{
"repoUrl": "https://...",
"repoBranch": "master",
"packageName": "com.yourcompany.package...",
"packageVersion": "1.2.3",
"packageSource": "git",
"e2eTest": "false"
}packageSource is optional and defaults to "git". In git mode the pipeline
clones repoUrl at repoBranch, finds packageName, packs it, and publishes
the generated tarball.
In git source mode, package repositories are cloned with Git LFS smudge disabled and LFS objects are fetched in explicit follow-up steps. This keeps clone and LFS failures visible in separate Azure log sections.
For public repositories hosted on github.com, the pipeline configures the
cloned repository and its submodules to fetch LFS objects from GitHub's HTTPS
LFS endpoint:
https://github.com/<owner>/<repo>.git/info/lfs
This deliberately avoids SSH host-key and SSH-key requirements for upstream
package repositories. It also handles package repositories that include a
repository .lfsconfig pointing at ssh://git@github.com/..., while keeping
non-GitHub remotes unchanged.
For GitHub Release asset mode, OpenUPM queue resolves the release and asset before queueing Azure. The pipeline receives the exact public asset URL and filename, downloads that file without GitHub authentication, validates it, and publishes it unchanged:
{
"repoUrl": "https://github.com/owner/repo",
"repoBranch": "v1.2.3",
"packageName": "com.yourcompany.package",
"packageVersion": "1.2.3",
"packageSource": "githubRelease",
"packageAssetUrl": "https://github.com/owner/repo/releases/download/v1.2.3/com.yourcompany.package-1.2.3.tgz",
"packageAssetName": "com.yourcompany.package-1.2.3.tgz",
"e2eTest": "false"
}e2eTest is optional and defaults to false. Set it to "true" to route the
run to the Verdaccio-based end-to-end publish test instead of the normal
OpenUPM publish stage.
If no variables are provided, the build is aborted as failed via
vstsabort.
API reference:
azure-devops-rest-5.1
Set your Azure DevOps Personal Access Token in the environment before calling the API:
export AZURE_DEVOPS_TOKEN_OPENUPM_PIPELINE="your-personal-access-token"curl --verbose \
--user ":$AZURE_DEVOPS_TOKEN_OPENUPM_PIPELINE" \
--request POST \
"https://dev.azure.com/openupm/openupm/_apis/build/builds?api-version=5.1" \
--json '{
"definition": { "id": 1 },
"parameters": "{\"repoUrl\":\"https://...\",\"repoBranch\":\"master\",\"packageName\":\"com.yourcompany.package...\",\"packageVersion\":\"1.2.3\",\"packageSource\":\"git\",\"e2eTest\":\"false\"}"
}'The parameters argument is a stringified dictionary.
When queueing the pipeline through the REST API, the run uses the pipeline's
default branch unless you override sourceBranch. This is important when
testing changes from a non-default branch of openupm-pipelines:
{
"definition": { "id": 1 },
"sourceBranch": "refs/heads/your-branch-name",
"parameters": "{\"repoUrl\":\"https://...\",\"repoBranch\":\"master\",\"packageName\":\"com.yourcompany.package...\",\"packageVersion\":\"1.2.3\",\"packageSource\":\"git\",\"e2eTest\":\"false\"}"
}Use this fixture for manual end-to-end testing:
{
"repoUrl": "https://github.com/favoyang/com.example.nuget-consumer",
"repoBranch": "1.0.1",
"packageName": "com.example.nuget-consumer",
"packageVersion": "1.0.1",
"e2eTest": "true"
}When testing pipeline changes from a branch, queue the run via REST API and set
sourceBranch to that branch so Azure uses your branch version of
azure-pipelines.yml.
When running the e2e path, the pipeline publishes the tarball to a local Verdaccio instance with anonymous publish access and then prints the published metadata plus the Verdaccio storage contents into the job log.
To queue that documented fixture from the current branch and stream the relevant Azure logs automatically, run:
npm run test:e2e:azureThis helper expects AZURE_DEVOPS_TOKEN_OPENUPM_PIPELINE in the environment.
It defaults to the documented e2eTest=true fixture and uses the current Git
branch as sourceBranch. To run the normal OpenUPM validation instead, use:
node scripts/runAzureFixture.js --e2e-test falseThat mode expects the duplicate-version 409 Conflict failure and exits
successfully only when it observes that result.
GitHub Actions also runs this helper in a separate Azure E2E job when the
repository secret AZURE_DEVOPS_TOKEN_OPENUPM_PIPELINE is configured. That job
is intentionally separate from the normal unit-test job because it is a
credentialed integration test against Azure Pipelines.
For a manual check of the normal OpenUPM path, queue the same package/version
with e2eTest=false only when that version is already published. The expected
result for that validation run is an OpenUPM publish failure with HTTP 409 Conflict, which confirms the pipeline stayed on the real registry path.
https://github.com/Microsoft/azure-devops-node-api
const azureDevops = require("azure-devops-node-api");
const token = process.env.AZURE_DEVOPS_TOKEN_OPENUPM_PIPELINE;
const endpoint = 'https://dev.azure.com/openupm';
const definitionId = 1;
const project = 'openupm';
const buildPipelines = async function () {
let authHandler = azureDevops.getPersonalAccessTokenHandler(token);
let conn = new azureDevops.WebApi(endpoint, authHandler);
var buildApi = await conn.getBuildApi();
let build = await buildApi.queueBuild({
definition: {
id: definitionId
},
sourceBranch: 'refs/heads/your-branch-name',
parameters:
JSON.stringify(
{
repoUrl: 'https://...',
repoBranch: 'master',
packageName: 'com.yourcompany.package...',
packageVersion: '1.2.3',
e2eTest: 'false',
...
}
)
}, project);
console.log(build);
};Favo Yang 💻 🚧 |
Pavel "am1goo" Shestakov 💻 |
James Frowen 🐛 |