Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
786 changes: 746 additions & 40 deletions .github/workflows/release-v2.yml

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"packages/graph": "1.11.3",
"packages/scheduler": "0.0.0"
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
"@commitlint/cli": "^19.5.0",
"@commitlint/config-conventional": "^19.5.0",
"@gravity-ui/prettier-config": "^1.1.0",
"prettier": "^3.0.0"
"prettier": "^3.0.0",
"release-please": "17.6.0",
"yaml": "2.8.1"
}
}
133 changes: 122 additions & 11 deletions packages/graph/docs/v1-v2-transition.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,114 @@ The release channels are:
| `v2` | `2.x` prereleases | `next` |

The existing v1 release workflow continues to handle `main` and maintenance
release branches. V2 prereleases are started manually with the `Release v2
prerelease` workflow. On the first run from the current v1 version, select
`premajor` to publish `2.0.0-next.0`. Select `prerelease` on later runs to
increment the prerelease version. The workflow runs only from the `v2` branch
and publishes under `next`.
release branches. V2 uses Release Please in manifest mode. Every push to `v2`
asks Release Please to create or update one combined, reviewed release PR from
`release-please-config.json` and `.release-please-manifest.json`. That action
only prepares versions, changelogs, and the manifest; it cannot publish npm or
create release tags.

Release Please keeps the combined PR labeled `autorelease: pending` so later
pushes update both its files and its description. A merged PR keeps that label
until publication and all component tags/releases succeed. The final workflow
step replaces it with `autorelease: tagged`, allowing the next release PR.
Recovery repeats this step safely if publication succeeded but label cleanup
failed.

The first Graph prerelease is bootstrapped by the temporary scoped
`"release-as": "2.0.0-next.0"` option under `packages/graph`. After the release
PR containing `2.0.0-next.0` has merged, remove that option in a normal reviewed
PR before landing more releasable changes. Later Graph prereleases are derived
from the manifest and increment exactly as `2.x.y-next.N`.

The private scheduler is also a manifest component so Release Please can assign
ownership correctly and the `node-workspace` plugin can propagate a scheduler
change into dependent Graph release metadata. Its version and changelog are
internal bookkeeping. The release workflow creates its component Git tag
because Release Please needs tags to locate previous component releases, but it
never publishes the scheduler to npm and never creates a public GitHub Release
for it.

## Releasing a prerelease

1. Let the automatic manifest release PR collect the intended conventional
commits. Review the exact package versions, changelog entries, dependency
updates, and `.release-please-manifest.json` together. Merging this PR is the
release approval.
2. The push workflow accepts only the exact merge commit of the combined
`release-please--branches--v2` PR authored by the trusted repository App. It
derives changed components from the current and first-parent manifests and
rejects any extra file in that release PR.
3. Private components are removed from the npm plan. Public components are
ordered dependency-first. Before any external write, the workflow validates
the branch, versions, channels, package boundaries, changelogs, npm state,
unit and static checks, Storybook, repository E2E, and each package's
isolated package contract. Every public tarball is built and verified first.
4. The workflow publishes only those exact tarballs under `next`, in dependency
order. Because every publish command uses the explicit `next` tag, Graph
`latest` remains on stable `1.x` throughout the transition.
5. After all public packages have been published, the GitHub App creates the
component tags on the exact release commit. Public components also receive
non-draft GitHub prereleases with the reviewed component changelog entry as
release notes. The App installation must grant `Contents: write`,
`Pull requests: write`, and `Workflows: write`; the workflow requests that token before npm publication,
so missing permission fails before a partial release.
6. Mark the merged release PR as `autorelease: tagged` and remove
`autorelease: pending`. Failures before this step leave it pending so Release
Please cannot silently move on from an incomplete release.

An ordinary push, a handwritten version change, and a release PR with unrelated
files are not release candidates. A scheduler-only manifest release still gets
its internal component tag after repository validation, but produces no npm or
GitHub Release operation.

If the workflow stops after a partial external write, run
`recover-npm-release` from `v2` with only the full merge commit SHA from the same
merged manifest release PR. Recovery requires that SHA to remain an ancestor of
`v2`, reconstructs the exact public and tag plans from the manifest delta,
rebuilds the same deterministic artifacts, skips exact npm versions and tags
that already exist, and completes only missing steps. It cannot be used for an
arbitrary commit. An existing npm artifact is treated as complete when its exact
`name@version` exists and `next` still points to it; recovery deliberately does
not download or compare the published bytes. Existing public versions must form
a prefix of the dependency-first release plan, so recovery rejects a published
consumer whose earlier planned dependency is still missing.

A fresh prerelease cannot skip recovery. For each component, the current `next`
version must already have the expected ancestor tag and, for a public package,
a completed non-draft GitHub prerelease before another numeric suffix may be
published.

`stable-dry-run` exercises the future `main`/stable `2.x`/`latest` Graph policy
from the current `v2` source. It runs the same local validation and a
no-credentials `pnpm publish --dry-run`; it does not create a release PR, tag,
GitHub Release, npm version, or dist-tag change.

## Adding another public package

Before cutover, every public workspace package must be present in both manifest
files and configured for numbered `next` prereleases. Give it a distinct
component tag, a changelog, and a `test:package-contract` script that supports
an exact tarball output. Use a temporary package-scoped `release-as` for its
first exact `next.0`, then remove that option after the bootstrap release PR
merges. The `node-workspace` plugin keeps `updatePeerDependencies` enabled so a
peer-only workspace consumer, such as a future React package, participates when
its changed workspace dependency is released. Release-managed packages must use
the dependency package's canonical name as every `workspace:` dependency key;
pnpm workspace aliases are rejected because Release Please cannot infer that
dependency edge.

During the pre-publication run, `PACKAGE_CONTRACT_WORKSPACE_TARBALLS` contains a
JSON map from already built public workspace package names to their exact local
tarball paths. If the package depends on another package changed in the same
release, its clean consumer fixture must install that local tarball instead of
requesting the not-yet-published version from npm. The dependency-first release
plan guarantees that every changed dependency is available before its consumer.

The manifest delta is the publication allowlist: unchanged public packages are
not packed, published, tagged, or given GitHub Releases. Changed packages are
published dependency-first. A new private package may be a manifest component
for dependency propagation, but it must remain `private: true`; it receives only
the internal tag needed by Release Please.

CI and E2E run for pull requests and for pushes to both `main` and `v2`.
Storybook previews are separate:
Expand Down Expand Up @@ -61,19 +164,27 @@ Cutover happens only when v2 is ready to replace v1. The maintainers should:
3. perform one final reviewed synchronization from `main` to `v2`;
4. verify the final v2 prerelease under `next`;
5. make the v2 line the new `main`;
6. publish stable `2.x` under `latest`.
6. change the manifest package policies and workflow target from `v2`/`next`
prereleases to `main`/`latest` stable versions, and make public GitHub
Releases stable;
7. narrow the old v1 `.github/workflows/release.yml` trigger to exactly
`release/v1`, removing both `main` and the broad `release/v*` pattern, so that
it cannot publish the new v2 line;
8. prepare, review, validate, and publish stable `2.x` under `latest`.

After cutover, `main` is the stable v2 branch and `release/v1` is used only for
necessary v1 maintenance. Release workflow configuration can be adjusted as
part of the cutover change; it is not preconfigured in this temporary setup.
necessary v1 maintenance. The stable dry-run proves the Graph artifact path in
advance, but the branch, version, dist-tag, GitHub Release, manifest policy, and
old-workflow changes above still belong to one reviewed cutover change.

## Practical limits

Do not create `release/v1` before cutover. Do not publish a v2 prerelease under
`latest`. Do not merge v2-only development back into the pre-cutover `main`
branch.

For this short transition, normal pull request review and the manual v2 release
workflow are the approval mechanism. The workflow versions and publishes the
`packages/graph` workspace package explicitly. No additional role system, SHA
Normal pull request review and the combined manifest release PR are the approval
mechanism. Do not run recovery with a different SHA, move `next` manually,
publish another locally packed artifact, or leave a bootstrap `release-as`
option in place after its release PR merges. No additional role system, SHA
ledger, ruleset framework, or scheduled synchronization process is required.
2 changes: 1 addition & 1 deletion packages/graph/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Config } from "jest";

const jestConfig: Config = {
testPathIgnorePatterns: ["/node_modules/", "/build/"],
testPathIgnorePatterns: ["/node_modules/", "/build/", "/scripts/"],
testEnvironment: "jsdom",
setupFiles: ["<rootDir>/setupJest.cjs", "jest-canvas-mock"],
transformIgnorePatterns: [],
Expand Down
3 changes: 2 additions & 1 deletion packages/graph/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@
"typecheck:published-playwright-types": "node scripts/check-playwright-consumer-types.mjs",
"lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\"",
"test:unit": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --passWithNoTests --updateSnapshot",
"test": "pnpm run test:unit",
"test:release-contract": "node --test scripts/release-contract.test.mjs scripts/resolve-release-candidate.test.mjs scripts/release-please.test.mjs",
"test": "pnpm run test:unit && pnpm run test:release-contract",
"build:docs": "node scripts/build-docs.mjs",
"build": "node scripts/build.mjs",
"prepublishOnly": "pnpm run typecheck && pnpm run test",
Expand Down
Loading
Loading