diff --git a/doc/release_process.md b/doc/release_process.md index c4f09e2..4bccad1 100644 --- a/doc/release_process.md +++ b/doc/release_process.md @@ -86,7 +86,7 @@ After the release PR is merged to `main`, follow these steps to build, sign, and Manually trigger the code mirror pipeline to sync the release to the internal ADO repo: -**Pipeline**: [durabletask-js code mirror](https://dev.azure.com/azfunc/internal/_build?definitionId=1004) +**Pipeline**: [durabletask-js code mirror](https://dev.azure.com/azfunc/internal/_build?definitionId=1757) Run it on the `main` branch (which now contains the release commit). @@ -98,7 +98,7 @@ Trigger the official build pipeline to produce signed `.tgz` artifacts. The offi 1. Click **Run pipeline** on `main` (or locate the `main` run that already contains the release commit) 2. Wait for it to complete -3. A single `main` build packs **all three** packages into one `drop` artifact; which package(s) actually publish is decided later by stage selection in the release pipeline (Step 3). Verify `drop` contains the correctly versioned `.tgz` files: +3. A single `main` build packs **all three** packages into one `drop` artifact; which single package publishes is decided later by the `package` parameter in the release pipeline (Step 3). Verify `drop` contains the correctly versioned `.tgz` files: - `buildoutputs/durabletask-js/microsoft-durabletask-js-X.Y.Z.tgz` - `buildoutputs/durabletask-js-azuremanaged/microsoft-durabletask-js-azuremanaged-X.Y.Z.tgz` - `buildoutputs/azure-functions-durable/durable-functions-X.Y.Z.tgz` @@ -107,21 +107,22 @@ Never publish from the release branch — it exists only to carry the version/ch ### Step 3: Run the Release Pipeline -Trigger the release pipeline to publish the signed packages to npm via ESRP. **This is the sanctioned publish path.** `eng/ci/release.yml` consumes the `durabletask-js.official` build artifact from **`main`** (its pipeline resource is pinned to `branch: main`), so its source is the `main` official build selected in Step 2 — not the release branch. It is a 1ES multi-stage pipeline with **one stage per package**, each publishing that package's `.tgz` from its own `buildoutputs/` folder via the `EsrpRelease@9` task: +Trigger the release pipeline to publish one signed package to npm via ESRP. **This is the sanctioned publish path.** `eng/ci/release.yml` consumes the `durabletask-js.official` build artifact from **`main`** (its pipeline resource is pinned to `branch: main`), so its source is the `main` official build selected in Step 2 — not the release branch. The pipeline's `package` runtime parameter controls which one of these release stages is inserted into the compiled 1ES plan. It defaults to `durabletask-js`, so verify or change it before queueing: a wrong or unchanged default compiles the wrong package stage. ESRP approval remains the final safety gate before publishing. -- `release_durabletask_js` — core `@microsoft/durabletask-js`; has no stage dependency. -- `release_durabletask_js_azuremanaged` — `@microsoft/durabletask-js-azuremanaged`; `dependsOn: release_durabletask_js`. -- `release_durable_functions` — `durable-functions`; `dependsOn: release_durabletask_js`. +- `durabletask-js` inserts `release_durabletask_js` for core `@microsoft/durabletask-js`. +- `durabletask-js-azuremanaged` inserts `release_durabletask_js_azuremanaged` for `@microsoft/durabletask-js-azuremanaged`. +- `azure-functions-durable` inserts `release_durable_functions` for `durable-functions`. -**Pipeline**: `eng/ci/release.yml` (ADO pipeline link: TBD) +**Pipeline**: [durabletask-js.release](https://dev.azure.com/azfunc/internal/_build?definitionId=1686) 1. Click **Run pipeline** 2. Select the **`main` official build from Step 2** (the one containing the release commit) as the source pipeline artifact -3. **Choose which stage(s) to run.** Stages are individually selectable at queue time, so you can release one package at a time. Both dependent stages `dependsOn` the core stage only — they are siblings, not chained to each other. -4. **Respect the ordering rule:** release the core stage before (or in the same run as) `durable-functions`, because compat exact-pins core. Each dependent stage carries an explicit condition — `in(dependencies.release_durabletask_js.result, 'Succeeded', 'SucceededWithIssues', 'Skipped')` — so it still runs when the core stage is **de-selected (Skipped)** at queue time (letting you release that package on its own once core is already on npm), but it will **not** run if the core stage actually **Failed** or was **Canceled**, which preserves the publish ordering. -5. Approve the ESRP release when prompted — each selected stage runs its own `EsrpRelease@9` task with its own approver, so expect one ESRP approval per package. +3. Verify or change the **`package`** parameter to the one package to publish. Exactly one release stage is compiled and exactly one package is published; do not use queue-time stage selection to choose packages. +4. Approve the ESRP release when prompted. -**Two waves for core + compat.** The compat **Prepare Release** run guards on core already being published to **public npm** — it runs `npm view @microsoft/durabletask-js@ --registry https://registry.npmjs.org/` and fails if that exact version is absent (`durable-functions` exact-pins core). So core and compat cannot go out in a single pass: publish core to public npm first, then run compat **Prepare Release** and its release stage. Treat core and `durable-functions` as two separate release waves. (`azuremanaged` depends on core only through a peer floor and has no such constraint.) +This compile-time selection eliminates the failure demonstrated by run **294746**: de-selecting the core stage at queue time did not expose its result to the Azure Managed dependency condition as the expected literal `Skipped`, so the selected package's stage was skipped too. The selected package now has no cross-stage dependency because the other package stages do not exist in that run's compiled plan. + +**Two runs for core + compat.** The compat **Prepare Release** run guards on core already being published to **public npm** — it runs `npm view @microsoft/durabletask-js@ --registry https://registry.npmjs.org/` and fails if that exact version is absent (`durable-functions` exact-pins core). Publish and verify core first; only then prepare `durable-functions` and publish it in a later release-pipeline run. (`azuremanaged` depends on core only through a peer floor that is already satisfied and has no ordering constraint.) > **Open question (B11) — hard blocker for ESRP prereleases:** it is not yet confirmed whether the ESRP release task can set the npm **dist-tag** (e.g. publish a prerelease under `preview` rather than moving `latest`). Publishing a prerelease such as `durable-functions@4.0.0-beta.1` through ESRP is **blocked** until the ESRP / 1ES pipeline owners confirm how to apply the `preview` tag — do **not** assume ESRP applies a dist-tag. The `--tag` guidance in *Quick Reference: npm Dist Tags* applies to a manual `npm publish`. diff --git a/eng/ci/release.yml b/eng/ci/release.yml index 827ba3d..206d4c4 100644 --- a/eng/ci/release.yml +++ b/eng/ci/release.yml @@ -1,6 +1,16 @@ pr: none trigger: none +parameters: +- name: package + displayName: Package to publish (verify before queueing) + type: string + default: durabletask-js + values: + - durabletask-js + - durabletask-js-azuremanaged + - azure-functions-durable + resources: repositories: - repository: 1ESPipelineTemplates @@ -21,127 +31,107 @@ extends: os: linux stages: - - stage: release_durabletask_js - displayName: 'Release @microsoft/durabletask-js' - jobs: - - job: durabletask_js + - ${{ if eq(parameters.package, 'durabletask-js') }}: + - stage: release_durabletask_js displayName: 'Release @microsoft/durabletask-js' - templateContext: - type: releaseJob - isProduction: true - inputs: - - input: pipelineArtifact - pipeline: DurableTaskJSBuildPipeline - artifactName: drop - targetPath: $(System.DefaultWorkingDirectory)/drop + jobs: + - job: durabletask_js + displayName: 'Release @microsoft/durabletask-js' + templateContext: + type: releaseJob + isProduction: true + inputs: + - input: pipelineArtifact + pipeline: DurableTaskJSBuildPipeline + artifactName: drop + targetPath: $(System.DefaultWorkingDirectory)/drop - steps: - - task: SFP.release-tasks.custom-build-release-task.EsrpRelease@9 - displayName: 'ESRP Release @microsoft/durabletask-js' - inputs: - connectedservicename: 'dtfx-internal-esrp-prod' - usemanagedidentity: true - keyvaultname: 'durable-esrp-akv' - signcertname: 'dts-esrp-cert' - clientid: '0b3ed1a4-0727-4a50-b82a-02c2bd9dec89' - intent: 'PackageDistribution' - contenttype: 'npm' - contentsource: 'Folder' - folderlocation: '$(System.DefaultWorkingDirectory)/drop/buildoutputs/durabletask-js' - waitforreleasecompletion: true - owners: 'wangbill@microsoft.com' - approvers: 'kaibocai@microsoft.com' - serviceendpointurl: 'https://api.esrp.microsoft.com' - mainpublisher: 'durabletask-java' - domaintenantid: '33e01921-4d64-4f8c-a055-5bdaffd5e33d' + steps: + - task: SFP.release-tasks.custom-build-release-task.EsrpRelease@9 + displayName: 'ESRP Release @microsoft/durabletask-js' + inputs: + connectedservicename: 'dtfx-internal-esrp-prod' + usemanagedidentity: true + keyvaultname: 'durable-esrp-akv' + signcertname: 'dts-esrp-cert' + clientid: '0b3ed1a4-0727-4a50-b82a-02c2bd9dec89' + intent: 'PackageDistribution' + contenttype: 'npm' + contentsource: 'Folder' + folderlocation: '$(System.DefaultWorkingDirectory)/drop/buildoutputs/durabletask-js' + waitforreleasecompletion: true + owners: 'wangbill@microsoft.com' + approvers: 'kaibocai@microsoft.com' + serviceendpointurl: 'https://api.esrp.microsoft.com' + mainpublisher: 'durabletask-java' + domaintenantid: '33e01921-4d64-4f8c-a055-5bdaffd5e33d' - - stage: release_durabletask_js_azuremanaged - displayName: 'Release @microsoft/durabletask-js-azuremanaged' - # Publish the core durabletask-js stage before this dependent package so its - # "@microsoft/durabletask-js" dependency resolves against the just-published core. - # This depends on the core stage only, not on the other provider stage (they are - # siblings, mirroring durabletask-python). ADO's default stage condition requires the - # core stage to have completed and succeeded, so a de-selected (Skipped) core would - # otherwise skip this stage too; the explicit condition below also accepts a Skipped - # core, which is what lets this package release on its own when core is de-selected at - # queue time. It deliberately does NOT accept Failed or Canceled, so a real core publish - # failure still blocks this stage and the publish ordering is preserved. - dependsOn: release_durabletask_js - condition: in(dependencies.release_durabletask_js.result, 'Succeeded', 'SucceededWithIssues', 'Skipped') - jobs: - - job: durabletask_js_azuremanaged + - ${{ if eq(parameters.package, 'durabletask-js-azuremanaged') }}: + - stage: release_durabletask_js_azuremanaged displayName: 'Release @microsoft/durabletask-js-azuremanaged' - templateContext: - type: releaseJob - isProduction: true - inputs: - - input: pipelineArtifact - pipeline: DurableTaskJSBuildPipeline - artifactName: drop - targetPath: $(System.DefaultWorkingDirectory)/drop + jobs: + - job: durabletask_js_azuremanaged + displayName: 'Release @microsoft/durabletask-js-azuremanaged' + templateContext: + type: releaseJob + isProduction: true + inputs: + - input: pipelineArtifact + pipeline: DurableTaskJSBuildPipeline + artifactName: drop + targetPath: $(System.DefaultWorkingDirectory)/drop - steps: - - task: SFP.release-tasks.custom-build-release-task.EsrpRelease@9 - displayName: 'ESRP Release @microsoft/durabletask-js-azuremanaged' - inputs: - connectedservicename: 'dtfx-internal-esrp-prod' - usemanagedidentity: true - keyvaultname: 'durable-esrp-akv' - signcertname: 'dts-esrp-cert' - clientid: '0b3ed1a4-0727-4a50-b82a-02c2bd9dec89' - intent: 'PackageDistribution' - contenttype: 'npm' - contentsource: 'Folder' - folderlocation: '$(System.DefaultWorkingDirectory)/drop/buildoutputs/durabletask-js-azuremanaged' - waitforreleasecompletion: true - owners: 'wangbill@microsoft.com' - approvers: 'kaibocai@microsoft.com' - serviceendpointurl: 'https://api.esrp.microsoft.com' - mainpublisher: 'durabletask-java' - domaintenantid: '33e01921-4d64-4f8c-a055-5bdaffd5e33d' + steps: + - task: SFP.release-tasks.custom-build-release-task.EsrpRelease@9 + displayName: 'ESRP Release @microsoft/durabletask-js-azuremanaged' + inputs: + connectedservicename: 'dtfx-internal-esrp-prod' + usemanagedidentity: true + keyvaultname: 'durable-esrp-akv' + signcertname: 'dts-esrp-cert' + clientid: '0b3ed1a4-0727-4a50-b82a-02c2bd9dec89' + intent: 'PackageDistribution' + contenttype: 'npm' + contentsource: 'Folder' + folderlocation: '$(System.DefaultWorkingDirectory)/drop/buildoutputs/durabletask-js-azuremanaged' + waitforreleasecompletion: true + owners: 'wangbill@microsoft.com' + approvers: 'kaibocai@microsoft.com' + serviceendpointurl: 'https://api.esrp.microsoft.com' + mainpublisher: 'durabletask-java' + domaintenantid: '33e01921-4d64-4f8c-a055-5bdaffd5e33d' - - stage: release_durable_functions - displayName: 'Release durable-functions' - # Publish the core durabletask-js stage before this dependent package: durable-functions - # pins "@microsoft/durabletask-js" to an EXACT version, so core must be on the registry - # first or the published package is uninstallable. This depends on the core stage only, - # not on the azuremanaged stage (they are siblings, mirroring durabletask-python). ADO's - # default stage condition requires the core stage to have completed and succeeded, so a - # de-selected (Skipped) core would otherwise skip this stage too; the explicit condition - # below also accepts a Skipped core, which is what lets durable-functions release on its - # own when core is de-selected at queue time. It deliberately does NOT accept Failed or - # Canceled, so if the core publish actually fails this stage still will not run and the - # exact-pin ordering guarantee holds. - dependsOn: release_durabletask_js - condition: in(dependencies.release_durabletask_js.result, 'Succeeded', 'SucceededWithIssues', 'Skipped') - jobs: - - job: durable_functions + - ${{ if eq(parameters.package, 'azure-functions-durable') }}: + - stage: release_durable_functions displayName: 'Release durable-functions' - templateContext: - type: releaseJob - isProduction: true - inputs: - - input: pipelineArtifact - pipeline: DurableTaskJSBuildPipeline - artifactName: drop - targetPath: $(System.DefaultWorkingDirectory)/drop + jobs: + - job: durable_functions + displayName: 'Release durable-functions' + templateContext: + type: releaseJob + isProduction: true + inputs: + - input: pipelineArtifact + pipeline: DurableTaskJSBuildPipeline + artifactName: drop + targetPath: $(System.DefaultWorkingDirectory)/drop - steps: - - task: SFP.release-tasks.custom-build-release-task.EsrpRelease@9 - displayName: 'ESRP Release durable-functions' - inputs: - connectedservicename: 'dtfx-internal-esrp-prod' - usemanagedidentity: true - keyvaultname: 'durable-esrp-akv' - signcertname: 'dts-esrp-cert' - clientid: '0b3ed1a4-0727-4a50-b82a-02c2bd9dec89' - intent: 'PackageDistribution' - contenttype: 'npm' - contentsource: 'Folder' - folderlocation: '$(System.DefaultWorkingDirectory)/drop/buildoutputs/azure-functions-durable' - waitforreleasecompletion: true - owners: 'wangbill@microsoft.com' - approvers: 'kaibocai@microsoft.com' - serviceendpointurl: 'https://api.esrp.microsoft.com' - mainpublisher: 'durabletask-java' - domaintenantid: '33e01921-4d64-4f8c-a055-5bdaffd5e33d' + steps: + - task: SFP.release-tasks.custom-build-release-task.EsrpRelease@9 + displayName: 'ESRP Release durable-functions' + inputs: + connectedservicename: 'dtfx-internal-esrp-prod' + usemanagedidentity: true + keyvaultname: 'durable-esrp-akv' + signcertname: 'dts-esrp-cert' + clientid: '0b3ed1a4-0727-4a50-b82a-02c2bd9dec89' + intent: 'PackageDistribution' + contenttype: 'npm' + contentsource: 'Folder' + folderlocation: '$(System.DefaultWorkingDirectory)/drop/buildoutputs/azure-functions-durable' + waitforreleasecompletion: true + owners: 'wangbill@microsoft.com' + approvers: 'kaibocai@microsoft.com' + serviceendpointurl: 'https://api.esrp.microsoft.com' + mainpublisher: 'durabletask-java' + domaintenantid: '33e01921-4d64-4f8c-a055-5bdaffd5e33d'