From d384c519f05b9f32023d5285a3fbbf8de03d2597 Mon Sep 17 00:00:00 2001 From: wangbill Date: Wed, 29 Jul 2026 15:43:36 -0700 Subject: [PATCH 1/4] docs(release): rewrite release_process.md for the per-package model Split out of #335 as a standalone, independently reviewable change. The doc/release_process.md content here is byte-identical to that branch's runbook commit (c602302). Rewrites the release runbook for the per-package release model: - per-package independent versioning, changelogs, and tags - the one hard ordering constraint: core must publish before durable-functions (compat exact-pins @microsoft/durabletask-js), while azuremanaged is unordered - ESRP via eng/ci/release.yml documented as the sanctioned publish path, with its three individually-selectable per-package stages - the required `package` workflow input and prefix-derived branch/tag names - the durable-functions@preview dist-tag Whether ESRP can set an npm dist-tag is left as an explicit open action item, not asserted as fact. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d86f5f69-4e1a-4c1d-b017-5e290c85cc05 --- doc/release_process.md | 132 +++++++++++++++++++++++++++-------------- 1 file changed, 88 insertions(+), 44 deletions(-) diff --git a/doc/release_process.md b/doc/release_process.md index 81535285..b39d5798 100644 --- a/doc/release_process.md +++ b/doc/release_process.md @@ -1,19 +1,25 @@ # Release Process -This document describes how to cut a new release of the `@microsoft/durabletask-js` and `@microsoft/durabletask-js-azuremanaged` npm packages. +This document describes how to cut a new release of the three npm packages published from this monorepo: `@microsoft/durabletask-js` (core), `@microsoft/durabletask-js-azuremanaged`, and `durable-functions` (the Azure Functions v4 compatibility provider, whose source lives in `packages/azure-functions-durable`). ## Overview -| Package | npm | -|---|---| -| `@microsoft/durabletask-js` | [npmjs](https://www.npmjs.com/package/@microsoft/durabletask-js) | -| `@microsoft/durabletask-js-azuremanaged` | [npmjs](https://www.npmjs.com/package/@microsoft/durabletask-js-azuremanaged) | +| Package (npm name) | Directory | npm | +|---|---|---| +| `@microsoft/durabletask-js` (core) | `packages/durabletask-js` | [npmjs](https://www.npmjs.com/package/@microsoft/durabletask-js) | +| `@microsoft/durabletask-js-azuremanaged` | `packages/durabletask-js-azuremanaged` | [npmjs](https://www.npmjs.com/package/@microsoft/durabletask-js-azuremanaged) | +| `durable-functions` | `packages/azure-functions-durable` | [npmjs](https://www.npmjs.com/package/durable-functions) | -This is an **npm workspaces** monorepo. There is no automated publish pipeline — the official build (`eng/ci/official-build.yml`) produces signed `.tgz` artifacts which are then published to npm manually. +This is an **npm workspaces** monorepo. The official build (`eng/ci/official-build.yml`) produces signed `.tgz` artifacts, and the **sanctioned way to publish them to npm is the ESRP release pipeline** (`eng/ci/release.yml`, see Step 3 below). A manual `npm publish` from the package directory is documented as an alternative for maintainers who need it. ### Versioning Policy -**All packages are released with the same version number.** This simplifies dependency management and makes it clear which versions are compatible. When cutting a release, both packages are bumped to the same version regardless of whether both have changes. +**Each package is versioned, changelogged, and tagged independently.** A release covers exactly one package; its version number, its changelog, and its git tag advance on their own and do **not** have to match the other packages. + +There is one hard ordering constraint between packages: + +- **`@microsoft/durabletask-js` (core) must be published before `durable-functions`.** The compat package declares an **exact** dependency on core (currently `"@microsoft/durabletask-js": "0.4.0"`). If `durable-functions` is published while that exact core version is not yet on npm, the published compat package is **uninstallable**. +- **`@microsoft/durabletask-js-azuremanaged` is unordered relative to `durable-functions`.** It depends on core only through a peer **floor** (`"@microsoft/durabletask-js": ">=0.3.0"`), which is already satisfied by the published core, so it can be released before or after the other packages. ## Versioning Scheme @@ -30,6 +36,8 @@ X.Y.Z-alpha.N → X.Y.Z-beta.N → X.Y.Z-rc.N → X.Y.Z (stable) | Release Candidate | `0.1.0-rc.1` | `--tag next` | | Stable | `0.1.0` | *(no tag, becomes `latest`)* | +> Note: current tooling overrides the beta/rc rows above for prereleases. The **Prepare Release** workflow publishes every prerelease under the single `preview` dist-tag, and `durable-functions` 4.x previews ship under `preview` (`npm install durable-functions@preview`). See *Quick Reference: npm Dist Tags* below. + ## Automated Release Preparation (Recommended) Use the **Prepare Release** GitHub Action to automate the release preparation process. @@ -38,25 +46,28 @@ Use the **Prepare Release** GitHub Action to automate the release preparation pr 1. Go to **Actions** → **Prepare Release** in GitHub 2. Click **Run workflow** -3. Optionally specify a version (e.g., `0.2.0-beta.1`). Leave empty to auto-increment. -4. Click **Run workflow** +3. **Select the `package` to release** (required) — one of `durabletask-js`, `durabletask-js-azuremanaged`, or `azure-functions-durable`. Each run releases exactly one package. +4. Optionally specify a version (e.g., `0.2.0-beta.1` for core, or `4.0.0-beta.1` for `durable-functions`). Leave empty to auto-increment the selected package's current version (bumps the prerelease number for a prerelease, otherwise the patch). +5. Click **Run workflow** ### What the Workflow Does -1. **Determines the next version**: If not specified, auto-increments the current version -2. **Generates changelog**: Uses `git diff` to find changes between `main` and the last release tag -3. **Updates package versions**: Bumps `version` in all `package.json` files to the same version -4. **Updates CHANGELOG.md**: Adds a new version section with the discovered changes -5. **Creates a release branch**: `release/vX.Y.Z` -6. **Creates a release tag**: `vX.Y.Z` +For the **one package you selected** (and only that package): + +1. **Determines the next version**: uses the `version` input, or auto-increments the selected package's current version +2. **Generates a changelog**: lists commits since that package's last release tag, scoped to the package's directory (`git log ..HEAD -- `), so only commits that touched that package are included +3. **Bumps the version**: updates `version` in that package's own `package.json` +4. **Updates that package's changelog**: core and azuremanaged write the repo-root `CHANGELOG.md`; `durable-functions` writes `packages/azure-functions-durable/CHANGELOG.md` +5. **Creates a release branch and a package-scoped tag**: tag `` and branch `release/`, where the prefix is `v` (core), `azuremanaged-v`, or `durable-functions-v` +6. **For `durable-functions` only**: verifies the exact-pinned `@microsoft/durabletask-js` version is already published on public npm, and fails the run if it is not (guards the uninstallable-dependency case) ### After the Workflow Completes -The workflow summary will include a link to create a PR. You must **manually create a pull request** from the release branch (`release/vX.Y.Z`) to `main`: +The workflow summary includes a **Create PR** link and the exact `npm publish` command to run later. You must **manually create a pull request** from the release branch to `main`. The branch is `release/` where `` is the package-scoped tag — e.g. `release/durable-functions-v4.0.0-beta.1`, `release/azuremanaged-v0.3.0`, or `release/v0.4.0`: -1. Go to the workflow run summary and click the **Create PR** link, or navigate to: `https://github.com/microsoft/durabletask-js/compare/main...release/vX.Y.Z` -2. Set the PR title to `Release vX.Y.Z` -3. Review the version bumps and changelog updates +1. Go to the workflow run summary and click the **Create PR** link +2. Set the PR title to `Release @` (e.g. `Release durable-functions@4.0.0-beta.1`) +3. Review the version bump and changelog update — only the released package should change 4. Merge the PR after CI passes After the PR is merged, follow the **Publishing** steps below to build and publish. @@ -80,37 +91,50 @@ Trigger the official build pipeline on the release tag/commit/branch to produce **Pipeline**: [durabletask-js.official](https://dev.azure.com/azfunc/internal/_build?definitionId=1012&_a=summary) 1. Click **Run pipeline** -2. Select the release branch or tag (e.g., `release/vX.Y.Z` or tag `vX.Y.Z`) +2. Select the release branch or tag created by the release — the package-scoped `release/` branch or `` tag (e.g., `release/v0.4.0` or tag `v0.4.0`) 3. Wait for it to complete -4. Verify the `drop` artifact contains correctly versioned `.tgz` files: +4. Verify the `drop` artifact contains the correctly versioned `.tgz` files (the official build packs all three packages): - `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` ### Step 3: Run the Release Pipeline -Trigger the release pipeline to publish the signed packages to npm via ESRP. The pipeline has separate release jobs for each package: +Trigger the release pipeline to publish the signed packages to npm via ESRP. **This is the sanctioned publish path.** `eng/ci/release.yml` 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: -**Pipeline**: eng/ci/release.yml (link to update soon) +- `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`. + +**Pipeline**: `eng/ci/release.yml` (ADO pipeline link: TBD) 1. Click **Run pipeline** 2. Select the build from Step 2 as the source pipeline artifact -3. Approve the ESRP release when prompted (one approval per package) +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. + +> **Open question (B11):** 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`). This must be confirmed with the ESRP / 1ES pipeline owners before publishing a prerelease through ESRP — do **not** assume ESRP applies a dist-tag. The `--tag` guidance in *Quick Reference: npm Dist Tags* applies to a manual `npm publish`. ### Step 4: Verify npm Publish ```bash npm view @microsoft/durabletask-js versions npm view @microsoft/durabletask-js-azuremanaged versions +npm view durable-functions versions + +# For a prerelease, also confirm which dist-tag it landed under (and that `latest` did not move): +npm view durable-functions dist-tags ``` ### Step 5: Create a GitHub Release Go to [GitHub Releases](https://github.com/microsoft/durabletask-js/releases) and create a new release: -- **Tag**: `vX.Y.Z` -- **Title**: `vX.Y.Z` -- **Description**: Copy the relevant section from `CHANGELOG.md` -- **Pre-release**: Check this box for alpha/beta/rc releases +- **Tag**: the package-scoped tag created by the release — e.g. `durable-functions-v4.0.0-beta.1`, `azuremanaged-v0.3.0`, or `v0.4.0` +- **Title**: the same tag, or `@` +- **Description**: copy the relevant section from that package's changelog (`CHANGELOG.md` for core / azuremanaged, `packages/azure-functions-durable/CHANGELOG.md` for `durable-functions`) +- **Pre-release**: check this box for alpha/beta/rc/preview releases ## Manual Release Process (Alternative) @@ -120,26 +144,37 @@ If you prefer to prepare the release manually, follow these steps. Review merged PRs since the last release tag to understand what's shipping. Choose an appropriate version number following semver. -### 2. Update Package Versions +### 2. Update the Package Version -Bump the `"version"` field in **both** package.json files to the **same version**: +Bump the `"version"` field in **only the package you are releasing**: ```bash -# Edit packages/durabletask-js/package.json → "version": "X.Y.Z" -# Edit packages/durabletask-js-azuremanaged/package.json → "version": "X.Y.Z" +# core: packages/durabletask-js/package.json → "version": "X.Y.Z" +# azuremanaged: packages/durabletask-js-azuremanaged/package.json → "version": "X.Y.Z" +# compat: packages/azure-functions-durable/package.json → "version": "X.Y.Z" ``` -Also update the peer dependency in `packages/durabletask-js-azuremanaged/package.json`: +Then update the affected cross-package dependency **for the package you are releasing**: -```jsonc -"peerDependencies": { - "@microsoft/durabletask-js": ">=X.Y.Z" // ← same version -} -``` +- **`@microsoft/durabletask-js-azuremanaged`** declares a peer **floor** on core. Only raise it if this release actually requires a newer core — it does not have to equal the release version: + + ```jsonc + "peerDependencies": { + "@microsoft/durabletask-js": ">=X.Y.Z" + } + ``` + +- **`durable-functions`** pins core to an **exact** version. This is the single most consequential dependency edit: if core is being bumped, set this pin to the exact core version you will publish, and publish that core version **first** (otherwise the published compat package is uninstallable): -### 3. Update CHANGELOG.md + ```jsonc + "dependencies": { + "@microsoft/durabletask-js": "X.Y.Z" // exact — no range; must be published on npm first + } + ``` -Move items from the `## Upcoming` section into a new versioned section. Follow the existing format: +### 3. Update the Changelog + +Move items from the `## Upcoming` section of the package's changelog into a new versioned section, following the existing format. Use the repo-root `CHANGELOG.md` for `@microsoft/durabletask-js` and `@microsoft/durabletask-js-azuremanaged`, or `packages/azure-functions-durable/CHANGELOG.md` for `durable-functions`: ```markdown ## Upcoming @@ -170,23 +205,26 @@ npm run lint ### 5. Create a Release PR -Create a branch (e.g., `release/vX.Y.Z`), commit the version bumps and changelog update, and open a PR against `main`. The PR title should follow: `Release vX.Y.Z`. +Create a release branch named `release/`, where `` is the package-scoped tag `` (prefix `v`, `azuremanaged-v`, or `durable-functions-v`) — e.g. `release/durable-functions-v4.0.0-beta.1`. Commit the version bump and changelog update for that one package, and open a PR against `main`. The PR title should follow: `Release @`. ### 6. Merge and Tag -After the PR is approved and merged to `main`: +After the PR is approved and merged to `main`, create the package-scoped tag (``): ```bash git checkout main git pull -git tag vX.Y.Z -git push origin vX.Y.Z +# e.g. durable-functions-v4.0.0-beta.1, azuremanaged-v0.3.0, or v0.4.0 +git tag +git push origin ``` Then follow the **Publishing** steps above (Steps 1-5). ## Quick Reference: npm Dist Tags +The general semver → dist-tag convention: + | Tag | When to use | |---|---| | `--tag alpha` | For `X.Y.Z-alpha.N` versions | @@ -194,6 +232,12 @@ Then follow the **Publishing** steps above (Steps 1-5). | `--tag next` | For release candidates (`X.Y.Z-rc.N`) | | *(no tag / `latest`)* | For stable GA releases only | +**Current tooling and package-specific rules take precedence where they differ from the table above:** + +- The **Prepare Release** workflow publishes *every* prerelease (any version containing `-`) under the single **`preview`** dist-tag — its run summary suggests `npm publish --registry https://registry.npmjs.org/ --tag preview` — so a prerelease never moves `latest`. +- **`durable-functions` 4.x previews ship under `preview`.** Its documented install line is `npm install durable-functions@preview` (see `packages/azure-functions-durable/CHANGELOG.md`); publish these with `npm publish --tag preview`. +- Whether the **ESRP** pipeline (Step 3) can set a dist-tag is an open question (B11); the `--tag` guidance here applies to a manual `npm publish`. + ## Rolling Back a Release If a release has critical issues: From 211843c7f9874efbc333db1b2f8c060873eaae96 Mon Sep 17 00:00:00 2001 From: wangbill Date: Wed, 29 Jul 2026 15:56:50 -0700 Subject: [PATCH 2/4] docs(release): correct dist-tag note - workflow emits, not publishes The Prepare Release workflow does not publish anything. It computes an `npm publish ... --tag preview` command (for any prerelease, i.e. any version containing "-") and writes it into the run summary for a human to run after the release PR merges. Reword the dist-tag note under the table, and the matching Quick Reference bullet, so both say the workflow emits/prescribes that command rather than publishing it. Keeps the "any version containing -" trigger, the preview-overrides-beta/rc point, and the durable-functions@preview example. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d86f5f69-4e1a-4c1d-b017-5e290c85cc05 --- doc/release_process.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/release_process.md b/doc/release_process.md index b39d5798..4f4ecb66 100644 --- a/doc/release_process.md +++ b/doc/release_process.md @@ -36,7 +36,7 @@ X.Y.Z-alpha.N → X.Y.Z-beta.N → X.Y.Z-rc.N → X.Y.Z (stable) | Release Candidate | `0.1.0-rc.1` | `--tag next` | | Stable | `0.1.0` | *(no tag, becomes `latest`)* | -> Note: current tooling overrides the beta/rc rows above for prereleases. The **Prepare Release** workflow publishes every prerelease under the single `preview` dist-tag, and `durable-functions` 4.x previews ship under `preview` (`npm install durable-functions@preview`). See *Quick Reference: npm Dist Tags* below. +> Note: current tooling overrides the beta/rc rows above for prereleases. The **Prepare Release** workflow emits an `npm publish … --tag preview` command (in its run summary, to run after the release PR merges) for **any** prerelease version — any version containing `-` — so prereleases go out under the single `preview` dist-tag rather than `beta`/`next`; `durable-functions` 4.x previews ship under `preview` (`npm install durable-functions@preview`). See *Quick Reference: npm Dist Tags* below. ## Automated Release Preparation (Recommended) @@ -234,7 +234,7 @@ The general semver → dist-tag convention: **Current tooling and package-specific rules take precedence where they differ from the table above:** -- The **Prepare Release** workflow publishes *every* prerelease (any version containing `-`) under the single **`preview`** dist-tag — its run summary suggests `npm publish --registry https://registry.npmjs.org/ --tag preview` — so a prerelease never moves `latest`. +- The **Prepare Release** workflow prescribes publishing *every* prerelease (any version containing `-`) under the single **`preview`** dist-tag: its run summary emits `npm publish --registry https://registry.npmjs.org/ --tag preview` for you to run after the release PR merges — so a prerelease never moves `latest`. - **`durable-functions` 4.x previews ship under `preview`.** Its documented install line is `npm install durable-functions@preview` (see `packages/azure-functions-durable/CHANGELOG.md`); publish these with `npm publish --tag preview`. - Whether the **ESRP** pipeline (Step 3) can set a dist-tag is an open question (B11); the `--tag` guidance here applies to a manual `npm publish`. From 54d5ae04b84f61de5ddaa62a23f9bc3d7de42a97 Mon Sep 17 00:00:00 2001 From: wangbill Date: Thu, 30 Jul 2026 12:12:02 -0700 Subject: [PATCH 3/4] docs(release): align runbook with merged automation and owner decisions Follow-up on the rebase onto main (which now contains #337 + #339). Corrects doc/release_process.md so it matches the merged per-package release automation and the owner's #339 changelog decisions: - dist-tags: replace the misleading per-stage beta/next table + override note with the actual rule -- every prerelease publishes under a single `preview` dist-tag; a stable GA omits `--tag` and moves `latest`; git tags (v.../azuremanaged-v.../durable-functions-v...) are separate from npm dist-tags. Applied to both the Versioning Scheme and Quick Reference sections. - Prepare Release only prepares a branch/tag/changelog; its emitted `npm publish` command is a MANUAL FALLBACK. The sanctioned publish path is the ADO official build + ESRP release pipeline. - add a one-time azuremanaged tag preflight (seed azuremanaged-v0.3.0 from v0.3.0) so per-package changelog generation starts at v0.3.0 instead of the repo root -- no azuremanaged-v* tag exists yet, so the first run would otherwise fall back to the initial commit and dump the entire history. - Publishing Step 2: the official build (official-build.yml) triggers on and builds `main` -- it has no branch/tag selector and never builds the release branch; one main build packs all three .tgz into one drop; stage selection decides which package publishes. - Publishing Step 3: source artifact is the selected `main` official build (release.yml pins its build resource to branch: main); make explicit that core + compat need two release waves because the compat Prepare Release guard queries public npm for the exact core pin. - changelog wording: generated `### Changes` commit/PR entries, not hand-authored `### Breaking Changes`; preview/migration narrative lives in packages/azure-functions-durable/README.md; Quick Reference citation moved from the compat CHANGELOG to the README. - keep B11 as an explicit hard blocker for publishing durable-functions@ 4.0.0-beta.1 via ESRP; do not claim ESRP supports npm dist-tags. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d86f5f69-4e1a-4c1d-b017-5e290c85cc05 --- doc/release_process.md | 81 ++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/doc/release_process.md b/doc/release_process.md index 4f4ecb66..1d450443 100644 --- a/doc/release_process.md +++ b/doc/release_process.md @@ -29,19 +29,30 @@ We follow [semver](https://semver.org/) with optional pre-release tags: X.Y.Z-alpha.N → X.Y.Z-beta.N → X.Y.Z-rc.N → X.Y.Z (stable) ``` -| Version Type | Example | npm Tag | -|---|---|---| -| Alpha | `0.1.0-alpha.1` | `--tag alpha` | -| Beta | `0.1.0-beta.1` | `--tag beta` | -| Release Candidate | `0.1.0-rc.1` | `--tag next` | -| Stable | `0.1.0` | *(no tag, becomes `latest`)* | +| Version Type | Example | +|---|---| +| Alpha | `0.1.0-alpha.1` | +| Beta | `0.1.0-beta.1` | +| Release Candidate | `0.1.0-rc.1` | +| Stable | `0.1.0` | -> Note: current tooling overrides the beta/rc rows above for prereleases. The **Prepare Release** workflow emits an `npm publish … --tag preview` command (in its run summary, to run after the release PR merges) for **any** prerelease version — any version containing `-` — so prereleases go out under the single `preview` dist-tag rather than `beta`/`next`; `durable-functions` 4.x previews ship under `preview` (`npm install durable-functions@preview`). See *Quick Reference: npm Dist Tags* below. +The scheme above describes the version **string**. How a version maps to an **npm dist-tag** is a separate concern and does **not** follow a per-stage `beta`/`next` convention: the release tooling publishes every prerelease under a single `preview` dist-tag, and only a stable GA moves `latest`. See *Quick Reference: npm Dist Tags* below for the authoritative rule. ## Automated Release Preparation (Recommended) Use the **Prepare Release** GitHub Action to automate the release preparation process. +### One-Time Preflight: Seed the `azuremanaged` Release Tag + +The changelog step lists commits since the released package's **last package-scoped tag** (`git log ..HEAD -- `); if no tag with that package's prefix exists, it falls back to the repo's initial commit and would dump the entire history into the changelog. `@microsoft/durabletask-js-azuremanaged` uses the `azuremanaged-v` prefix and has no such tag yet — the last lockstep release is the unprefixed `v0.3.0`. **Before the first independent `azuremanaged` release**, seed its tag so changelog generation starts at `v0.3.0` instead of the repo root: + +```bash +git tag azuremanaged-v0.3.0 v0.3.0 +git push origin azuremanaged-v0.3.0 +``` + +Do this once (not as part of a normal release run). + ### Running the Workflow 1. Go to **Actions** → **Prepare Release** in GitHub @@ -63,7 +74,9 @@ For the **one package you selected** (and only that package): ### After the Workflow Completes -The workflow summary includes a **Create PR** link and the exact `npm publish` command to run later. You must **manually create a pull request** from the release branch to `main`. The branch is `release/` where `` is the package-scoped tag — e.g. `release/durable-functions-v4.0.0-beta.1`, `release/azuremanaged-v0.3.0`, or `release/v0.4.0`: +The workflow only **prepares** a release — it bumps the version, generates the changelog, and creates the release branch and package-scoped tag. It does **not** publish to npm. Its run summary includes a **Create PR** link and an `npm publish` command; that command is a **manual fallback** for maintainers, and the sanctioned publish path is the ADO official build + ESRP release pipeline (see **Publishing** below). + +You must **manually create a pull request** from the release branch to `main`. The branch is `release/` where `` is the package-scoped tag — e.g. `release/durable-functions-v4.0.0-beta.1`, `release/azuremanaged-v0.3.0`, or `release/v0.4.0`: 1. Go to the workflow run summary and click the **Create PR** link 2. Set the PR title to `Release @` (e.g. `Release durable-functions@4.0.0-beta.1`) @@ -86,21 +99,22 @@ Run it on the `main` branch (which now contains the release commit). ### Step 2: Run the Official Build Pipeline -Trigger the official build pipeline on the release tag/commit/branch to produce signed `.tgz` artifacts: +Trigger the official build pipeline to produce signed `.tgz` artifacts. The official build (`eng/ci/official-build.yml`) triggers on and builds **`main`** — it has no branch/tag selector and never builds the short-lived release branch. Use (or pick the automatic run of) the official build whose source commit **includes the merged release commit(s)**: **Pipeline**: [durabletask-js.official](https://dev.azure.com/azfunc/internal/_build?definitionId=1012&_a=summary) -1. Click **Run pipeline** -2. Select the release branch or tag created by the release — the package-scoped `release/` branch or `` tag (e.g., `release/v0.4.0` or tag `v0.4.0`) -3. Wait for it to complete -4. Verify the `drop` artifact contains the correctly versioned `.tgz` files (the official build packs all three packages): +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: - `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` +Never publish from the release branch — it exists only to carry the version/changelog PR into `main`. + ### 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` 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 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: - `release_durabletask_js` — core `@microsoft/durabletask-js`; has no stage dependency. - `release_durabletask_js_azuremanaged` — `@microsoft/durabletask-js-azuremanaged`; `dependsOn: release_durabletask_js`. @@ -109,12 +123,14 @@ Trigger the release pipeline to publish the signed packages to npm via ESRP. **T **Pipeline**: `eng/ci/release.yml` (ADO pipeline link: TBD) 1. Click **Run pipeline** -2. Select the build from Step 2 as the source pipeline artifact +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. -> **Open question (B11):** 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`). This must be confirmed with the ESRP / 1ES pipeline owners before publishing a prerelease through ESRP — do **not** assume ESRP applies a dist-tag. The `--tag` guidance in *Quick Reference: npm Dist Tags* applies to a manual `npm publish`. +**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.) + +> **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`. ### Step 4: Verify npm Publish @@ -174,24 +190,20 @@ Then update the affected cross-package dependency **for the package you are rele ### 3. Update the Changelog -Move items from the `## Upcoming` section of the package's changelog into a new versioned section, following the existing format. Use the repo-root `CHANGELOG.md` for `@microsoft/durabletask-js` and `@microsoft/durabletask-js-azuremanaged`, or `packages/azure-functions-durable/CHANGELOG.md` for `durable-functions`: +Move items from the `## Upcoming` section of the package's changelog into a new versioned section. The changelog is a **generated** list of the released package's commit messages / PR links — not a place for hand-authored breaking-change narrative; detailed preview and migration guidance lives in `packages/azure-functions-durable/README.md` (for `durable-functions`), not the changelog. To reproduce the tooling manually, promote any curated `## Upcoming` notes into the new section and add one `### Changes` entry per commit, mirroring `git log ..HEAD -- ` (each commit subject with its `(#NN)` linked). Use the repo-root `CHANGELOG.md` for `@microsoft/durabletask-js` and `@microsoft/durabletask-js-azuremanaged`, or `packages/azure-functions-durable/CHANGELOG.md` for `durable-functions`: ```markdown ## Upcoming ### New - - -## vX.Y.Z - -### New -- Feature A ([#NN](https://github.com/microsoft/durabletask-js/pull/NN)) ### Fixes -- Fix B ([#NN](https://github.com/microsoft/durabletask-js/pull/NN)) -### Breaking Changes -- Breaking change C ([#NN](https://github.com/microsoft/durabletask-js/pull/NN)) +## vX.Y.Z (YYYY-MM-DD) + +### Changes +- Some change ([#NN](https://github.com/microsoft/durabletask-js/pull/NN)) +- Another change ([#NN](https://github.com/microsoft/durabletask-js/pull/NN)) ``` ### 4. Verify Build & Tests Pass Locally @@ -223,20 +235,11 @@ Then follow the **Publishing** steps above (Steps 1-5). ## Quick Reference: npm Dist Tags -The general semver → dist-tag convention: - -| Tag | When to use | -|---|---| -| `--tag alpha` | For `X.Y.Z-alpha.N` versions | -| `--tag beta` | For `X.Y.Z-beta.N` versions | -| `--tag next` | For release candidates (`X.Y.Z-rc.N`) | -| *(no tag / `latest`)* | For stable GA releases only | - -**Current tooling and package-specific rules take precedence where they differ from the table above:** +npm **dist-tags** are separate from the git tags this repo pushes (`v...`, `azuremanaged-v...`, `durable-functions-v...`); a git tag only names a release commit and never moves an npm dist-tag. This repo uses one simple rule, not the per-stage `alpha`/`beta`/`next` convention: -- The **Prepare Release** workflow prescribes publishing *every* prerelease (any version containing `-`) under the single **`preview`** dist-tag: its run summary emits `npm publish --registry https://registry.npmjs.org/ --tag preview` for you to run after the release PR merges — so a prerelease never moves `latest`. -- **`durable-functions` 4.x previews ship under `preview`.** Its documented install line is `npm install durable-functions@preview` (see `packages/azure-functions-durable/CHANGELOG.md`); publish these with `npm publish --tag preview`. -- Whether the **ESRP** pipeline (Step 3) can set a dist-tag is an open question (B11); the `--tag` guidance here applies to a manual `npm publish`. +- **Prerelease** — any version containing `-` (e.g. `0.4.0-beta.1`, `4.0.0-beta.1`). The **Prepare Release** workflow emits `npm publish --registry https://registry.npmjs.org/ --tag preview` in its run summary as a **manual fallback** to run after the release PR merges. Every prerelease publishes under the single `preview` dist-tag, so it never moves `latest`. `durable-functions` 4.x previews install with `npm install durable-functions@preview` (see `packages/azure-functions-durable/README.md`). +- **Stable GA** — no `-`. The emitted command omits `--tag`, so the publish moves `latest`. +- **ESRP path (sanctioned, Step 3):** whether the ESRP release task can set an npm dist-tag is an **open question (B11)** and a **hard blocker** for publishing a prerelease such as `durable-functions@4.0.0-beta.1` through ESRP — confirm with the ESRP / 1ES owners how to apply the `preview` tag before publishing any prerelease via ESRP. Do **not** assume ESRP applies a dist-tag; the `--tag preview` guidance above is for a manual `npm publish`. ## Rolling Back a Release From 6a842c4b2ff0def3f09e6ea7821ae4a82482e26b Mon Sep 17 00:00:00 2001 From: wangbill Date: Thu, 30 Jul 2026 12:21:10 -0700 Subject: [PATCH 4/4] docs(release): scope single-preview dist-tag to the manual fallback (fix B11 contradiction) The Versioning Scheme and Quick Reference sections asserted (unqualified) that "every prerelease publishes under a single preview dist-tag", which contradicts the B11 note that the sanctioned ESRP path's dist-tag behavior is unknown and blocked. Only #337's emitted MANUAL FALLBACK `npm publish` command is known to use `--tag preview`; ESRP is not. - Versioning Scheme: scope the preview mapping to the Prepare Release workflow's emitted manual fallback command, and state that sanctioned prerelease publishing via ESRP is blocked pending B11. - Quick Reference prerelease bullet: "Every prerelease publishes..." -> "When using this manual fallback, publish every prerelease..."; `latest` protection and the README install citation are preserved. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d86f5f69-4e1a-4c1d-b017-5e290c85cc05 --- doc/release_process.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/release_process.md b/doc/release_process.md index 1d450443..33030087 100644 --- a/doc/release_process.md +++ b/doc/release_process.md @@ -36,7 +36,7 @@ X.Y.Z-alpha.N → X.Y.Z-beta.N → X.Y.Z-rc.N → X.Y.Z (stable) | Release Candidate | `0.1.0-rc.1` | | Stable | `0.1.0` | -The scheme above describes the version **string**. How a version maps to an **npm dist-tag** is a separate concern and does **not** follow a per-stage `beta`/`next` convention: the release tooling publishes every prerelease under a single `preview` dist-tag, and only a stable GA moves `latest`. See *Quick Reference: npm Dist Tags* below for the authoritative rule. +The scheme above describes the version **string**. Mapping a version to an **npm dist-tag** is a separate concern and does **not** follow a per-stage `beta`/`next` convention: the **Prepare Release** workflow's emitted **manual fallback** `npm publish` command tags every prerelease `preview` and omits `--tag` for a stable GA (so only a GA moves `latest`). Publishing a prerelease through the sanctioned ESRP path is **blocked pending B11** until owners confirm whether the ESRP task can set that tag. See *Quick Reference: npm Dist Tags* below for the authoritative rule. ## Automated Release Preparation (Recommended) @@ -237,7 +237,7 @@ Then follow the **Publishing** steps above (Steps 1-5). npm **dist-tags** are separate from the git tags this repo pushes (`v...`, `azuremanaged-v...`, `durable-functions-v...`); a git tag only names a release commit and never moves an npm dist-tag. This repo uses one simple rule, not the per-stage `alpha`/`beta`/`next` convention: -- **Prerelease** — any version containing `-` (e.g. `0.4.0-beta.1`, `4.0.0-beta.1`). The **Prepare Release** workflow emits `npm publish --registry https://registry.npmjs.org/ --tag preview` in its run summary as a **manual fallback** to run after the release PR merges. Every prerelease publishes under the single `preview` dist-tag, so it never moves `latest`. `durable-functions` 4.x previews install with `npm install durable-functions@preview` (see `packages/azure-functions-durable/README.md`). +- **Prerelease** — any version containing `-` (e.g. `0.4.0-beta.1`, `4.0.0-beta.1`). The **Prepare Release** workflow emits `npm publish --registry https://registry.npmjs.org/ --tag preview` in its run summary as a **manual fallback** to run after the release PR merges. When using this manual fallback, publish every prerelease under the single `preview` dist-tag, so it never moves `latest`. `durable-functions` 4.x previews install with `npm install durable-functions@preview` (see `packages/azure-functions-durable/README.md`). - **Stable GA** — no `-`. The emitted command omits `--tag`, so the publish moves `latest`. - **ESRP path (sanctioned, Step 3):** whether the ESRP release task can set an npm dist-tag is an **open question (B11)** and a **hard blocker** for publishing a prerelease such as `durable-functions@4.0.0-beta.1` through ESRP — confirm with the ESRP / 1ES owners how to apply the `preview` tag before publishing any prerelease via ESRP. Do **not** assume ESRP applies a dist-tag; the `--tag preview` guidance above is for a manual `npm publish`.