From d00b502d848ad66296812e886d66765952d7c337 Mon Sep 17 00:00:00 2001 From: wangbill Date: Wed, 29 Jul 2026 15:01:10 -0700 Subject: [PATCH 1/2] ci(release): split ADO release into per-package stages; build on Node 22 B13: eng/ci/release.yml published all three npm packages from a single `- stage: release`. In Azure DevOps only STAGES are individually selectable at queue time ("Stages to run") -- jobs are not -- so queuing a release published core, azuremanaged and durable-functions together. durable-functions pins "@microsoft/durabletask-js" to an EXACT version, so core must reach the registry first; a combined publish would put an uninstallable compat package on npm. Split the single stage into three, one job each, mirroring durabletask-python: - release_durabletask_js (core; no dependency) - release_durabletask_js_azuremanaged (dependsOn: release_durabletask_js) - release_durable_functions (dependsOn: release_durabletask_js) The two dependents depend on CORE only, not on each other (siblings, not chained). Because ADO skips a de-selected stage at queue time, core can be released on its own and a dependent released later. Every EsrpRelease@9 input and each job's templateContext are preserved byte-for-byte; no environment/condition/input added. B12: eng/templates/build.yml built the shipped .tgz artifacts on Node 20 (NodeTool@0 versionSpec: 20.x) while all packages declare engines node >=22.0.0 and GitHub CI only tests 22.x/24.x -- so the runtime that builds the released artifacts was never covered by the green CI signal and ran a Node the packages call unsupported. Bump the official build to 22.x, the conservative choice already exercised by CI. No other change to that file. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d86f5f69-4e1a-4c1d-b017-5e290c85cc05 --- eng/ci/release.yml | 21 ++++++++++++++++++++- eng/templates/build.yml | 2 +- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/eng/ci/release.yml b/eng/ci/release.yml index 084871df..d83cdd7e 100644 --- a/eng/ci/release.yml +++ b/eng/ci/release.yml @@ -21,7 +21,8 @@ extends: os: linux stages: - - stage: release + - stage: release_durabletask_js + displayName: 'Release @microsoft/durabletask-js' jobs: - job: durabletask_js displayName: 'Release @microsoft/durabletask-js' @@ -54,6 +55,15 @@ extends: 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). If the core stage is de-selected at + # queue time, ADO skips it and this stage runs on its own. + dependsOn: release_durabletask_js + jobs: - job: durabletask_js_azuremanaged displayName: 'Release @microsoft/durabletask-js-azuremanaged' templateContext: @@ -85,6 +95,15 @@ extends: 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). If the + # core stage is de-selected at queue time, ADO skips it and this stage runs on its own. + dependsOn: release_durabletask_js + jobs: - job: durable_functions displayName: 'Release durable-functions' templateContext: diff --git a/eng/templates/build.yml b/eng/templates/build.yml index c747c8f9..06c1ab26 100644 --- a/eng/templates/build.yml +++ b/eng/templates/build.yml @@ -13,7 +13,7 @@ jobs: - checkout: self - task: NodeTool@0 inputs: - versionSpec: 20.x + versionSpec: 22.x displayName: "Install Node.js" - script: node scripts/setupNpmrc.js From 463d46512106e458be5796b64eae2db52d8180af Mon Sep 17 00:00:00 2001 From: wangbill Date: Wed, 29 Jul 2026 15:20:47 -0700 Subject: [PATCH 2/2] ci(release): allow dependent stages to run when core stage is de-selected The Copilot PR reviewer flagged that with `dependsOn: release_durabletask_js` alone, ADO's DEFAULT stage condition requires the core stage to have completed and succeeded, so de-selecting the core stage at queue time (to release only a dependent package) would SKIP the dependent stage too. That contradicts the adjacent comment claiming the stage "runs on its own": the comment copied from durabletask-python is wrong -- verified against Microsoft Learn, ADO does not run a stage whose dependency was Skipped unless an explicit condition allows it. Add an explicit stage-level condition to BOTH dependent stages: condition: in(dependencies.release_durabletask_js.result, 'Succeeded', 'SucceededWithIssues', 'Skipped') This accepts a Skipped core stage (enabling independent release when core is de-selected) while still rejecting Failed and Canceled, so a real core publish FAILURE still blocks the dependent stages. That matters most for durable-functions, whose exact pin "@microsoft/durabletask-js": "0.4.0" would otherwise ship an uninstallable package if core failed. The fix is strictly safe: if de-selection already worked the condition changes nothing; if it did not, it fixes it. The core stage keeps ADO's default (no dependsOn, no condition). Both stage comments are corrected to describe the real behavior. Only eng/ci/release.yml changes; ESRP inputs and templateContext blocks are byte-identical to d00b502. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d86f5f69-4e1a-4c1d-b017-5e290c85cc05 --- eng/ci/release.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/eng/ci/release.yml b/eng/ci/release.yml index d83cdd7e..827ba3dd 100644 --- a/eng/ci/release.yml +++ b/eng/ci/release.yml @@ -60,9 +60,14 @@ extends: # 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). If the core stage is de-selected at - # queue time, ADO skips it and this stage runs on its own. + # 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 displayName: 'Release @microsoft/durabletask-js-azuremanaged' @@ -100,9 +105,15 @@ extends: # 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). If the - # core stage is de-selected at queue time, ADO skips it and this stage runs on its own. + # 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 displayName: 'Release durable-functions'