From 82ddc8bb3069e522a32c9cf015d73f184b17fdfb Mon Sep 17 00:00:00 2001 From: zbynekstara Date: Wed, 5 Aug 2026 14:23:57 +0200 Subject: [PATCH 1/5] chore: use changesets to support an automatic release process --- .changeset/README.md | 137 ++++++++ .changeset/config.json | 54 +++ .github/pull_request_template.md | 3 +- .github/workflows/publish.yml | 125 +++++++ .github/workflows/release.yml | 147 ++++++++ .github/workflows/test-pr.yml | 11 + CONTRIBUTING.md | 41 +++ package.json | 12 +- scripts/changelog-from-changesets.mjs | 372 ++++++++++++++++++++ scripts/release-info.mjs | 59 ++++ yarn.lock | 479 +++++++++++++++++++++++++- 11 files changed, 1429 insertions(+), 11 deletions(-) create mode 100644 .changeset/README.md create mode 100644 .changeset/config.json create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/release.yml create mode 100644 scripts/changelog-from-changesets.mjs create mode 100644 scripts/release-info.mjs diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000000..551a9274c0 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,137 @@ +# Changesets + +This folder is managed by [Changesets](https://github.com/changesets/changesets). A +changeset is a small Markdown file that records, at PR time, **which packages to bump** +and **what to write in the changelog** for a change. + +## Adding a changeset + +Run: + +```bash +yarn changeset +``` + +Pick the package(s) your change affects and the bump type (`patch` / `minor` / `major`), +then write the changelog line(s). This creates a `.changeset/.md` file — commit +it with your PR. CI (`changeset status`) fails a PR that changes releasable code but adds no +changeset. Docs-, test-, and demo-only PRs don't need one (see below); if CI still asks for +one, add an empty changeset with `yarn changeset --empty`. + +### Which file changes require a changeset (`changedFilePatterns`) + +`config.json`'s `changedFilePatterns` decides whether a PR touched *releasable* code. The rule +is **allow everything, then subtract an ignore list** (`"**"` + `"!…"` negations) — a file in a +publishable package triggers a bump requirement **unless** it matches an ignore pattern. This +mirrors the previous Yarn `changesetIgnorePatterns` setup (where a file counted unless ignored), +so a brand-new kind of shipped file requires a changeset by default. JSON can't hold comments, +so the annotated intent lives here: + +A change to a publishable package requires a changeset when it touches **distributed source, +types, an entry file, or the manifest** — for reference, the paths that *do* trigger a bump: + +- `**/src/**` — source (all packages) +- `**/types/**` — `@joint/core` public type tree +- `**/*.d.ts` — hand-written type decls (e.g. `svg.d.ts`, `DirectedGraph.d.ts`) +- `**/package.json` — dependencies / exports / manifest +- `**/wrappers/**` — `@joint/core` build-time bundle wrappers +- `**/mocks/**` — `@joint/vitest-plugin-mock-svg` shipped mocks +- `**/index.js`, `**/index.mjs`, `**/joint.mjs`, `**/DirectedGraph.mjs` — package entry files +- `**/eslint.config.*.mjs` — `@joint/eslint-config` product configs (note: `eslint.config.mjs`, + the per-package lint config, is **ignored**) + +Everything else is **ignored** (never triggers a bump), grouped as: + +- **Tests / mocks / benchmarks / demos / stories** (not shipped): + `**/*.test.{js,jsx,ts,tsx,mjs}`, `**/test/**`, `**/__mocks__/**`, `**/bench/**`, `**/demo/**`, + `**/stories/**`, `**/.storybook/**` +- **Generated output & caches**: `**/dist/**`, `**/build/**`, `**/coverage/**`, `**/coverage.json`, + `**/.tscache/**` +- **Build & tooling scripts / config**: `**/scripts/**`, `**/grunt/**`, `**/Gruntfile.js`, + `**/Makefile`, `**/rollup.config.*`, `**/rollup.resources.mjs`, `**/vite.config.*`, + `**/vitest.workspace.*`, `**/jest.config.js`, `**/jest.react18.config.mjs`, `**/karma.conf.js`, + `**/tsconfig*.json`, `**/dts-generator.config.js`, `**/api-extractor.json`, `**/knip.json`, + `**/typedoc.*`, `**/.prettierrc*`, `**/.gitignore`, `**/eslint.config.mjs` +- **Documentation & metadata**: `**/*.md`, `**/LICENSE` + +> Test files are matched by **naming convention** (`*.test.{js,jsx,ts,tsx,mjs}`), which covers +> `@joint/react`'s `.ts`/`.tsx` tests under `src/**/__tests__/` without blanket-ignoring the +> directory (the lone non-`.test` file there, a `.md` benchmark, is already ignored by `**/*.md`). +> Because patterns are matched **per package** (relative to each package dir), the ignore +> negations override the broad `"**"` — e.g. a `demo/index.js` does not require a changeset even +> though it lives in a publishable package. + +## Body convention (important — our renderer depends on it) + +We render the root `CHANGELOG` ourselves (`"changelog": false` in `config.json`, +`scripts/changelog-from-changesets.mjs`), so the changeset **body** must follow our +semantic-commit convention — the same `type(scope): description` you already use in commits. +Write **one changelog row per line**: + +``` +()[!]: +``` + +- **`type`** — `feat` or `fix`. Any other line (`docs`, `chore`, `refactor`, plain prose, …) + is ignored by the renderer and never reaches the changelog. `feat` rows print as-is; + `fix` rows get the word `fix ` prepended, so phrase the text to read well after it + (e.g. `fix(util): to guard against prototype pollution` → `util - fix to guard …`). +- **`class`** — the scope, e.g. `dia.Paper`, `routers.rightAngle`, ``. It becomes + the row prefix (`dia.Paper - …`) and drives ordering (see `NAMESPACE_ORDER` / + `SUBCLASS_ORDER` in the renderer, which encode the "Section 2" class hierarchy). +- **`!`** before the `:` marks a **breaking** change; it renders as a leading `!` on the + class (`!dia.Graph - …`). + +Don't add PR or commit references by hand. The root `CHANGELOG` never carries links, and the +**GitHub Release notes (`RELEASE_NOTES.md`) link each row automatically** to the commit on +`master` that introduced its changeset (the PR's merge/squash commit). + +### Frontmatter vs. body + +- **Frontmatter** = the per-package version bumps (Changesets' native meaning). This is what + gets released. +- **Body** = the changelog rows. Rows are attributed to the package(s) named in the + frontmatter. **Keep a changeset focused on one documented package**; if a single PR needs + distinct rows for different packages, write separate changeset files. + +### Which packages appear in the CHANGELOG + +Only these four are rendered into the root `CHANGELOG` / `RELEASE_NOTES.md`, in this order: + +1. `@joint/react` +2. `@joint/core` +3. `@joint/layout-directed-graph` +4. `@joint/layout-msagl` + +Other publishable packages (`@joint/cli`, `@joint/decorators`, `@joint/shapes-general`, +`@joint/shapes-general-tools`, `@joint/vitest-plugin-mock-svg`, `@joint/eslint-config`) are +still **versioned and published** when a changeset names them (or when they're bumped as a +dependent), but they are intentionally **omitted from the human-facing CHANGELOG**. Add them +to `DOCUMENTED_PACKAGES` in the renderer if that ever needs to change. + +## Example + +```markdown +--- +"@joint/core": minor +--- +feat(dia.Paper): add `originX` and `originY` options to `getFitToContentArea()` +fix(routers.rightAngle): to allow zero-value margins and un-clamp `minPathMargin` +``` + +## Dependent bumps + +`config.json` uses `updateInternalDependents: "out-of-range"`, so a package is auto-bumped +only when a dependency's **new version falls outside its declared `workspace:` range**: + +- `@joint/react` depends on `@joint/core` via `workspace:^` → ignores core **patch and minor** + bumps (only a core **major** cascades to react). +- Everything else depends on core via `workspace:~` → ignores core **patch** bumps (a core + **minor** or **major** cascades). + +Cascaded packages are published but, having no authored rows, don't appear in the CHANGELOG. + +## Releasing + +Releases are driven by the `release.yml` / `publish.yml` GitHub Actions workflows, not by +`changeset publish`. See `CONTRIBUTING.md` → *Releasing*. diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000000..c70a4330cb --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.4/schema.json", + "changelog": false, + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "master", + "updateInternalDependencies": "patch", + "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { + "updateInternalDependents": "out-of-range" + }, + "privatePackages": { + "version": false, + "tag": false + }, + "changedFilePatterns": [ + "**", + "!**/*.test.{js,jsx,ts,tsx,mjs}", + "!**/test/**", + "!**/__mocks__/**", + "!**/bench/**", + "!**/demo/**", + "!**/stories/**", + "!**/.storybook/**", + "!**/dist/**", + "!**/build/**", + "!**/coverage/**", + "!**/coverage.json", + "!**/.tscache/**", + "!**/scripts/**", + "!**/grunt/**", + "!**/Gruntfile.js", + "!**/Makefile", + "!**/rollup.config.*", + "!**/rollup.resources.mjs", + "!**/vite.config.*", + "!**/vitest.workspace.*", + "!**/jest.config.js", + "!**/jest.react18.config.mjs", + "!**/karma.conf.js", + "!**/tsconfig*.json", + "!**/dts-generator.config.js", + "!**/api-extractor.json", + "!**/knip.json", + "!**/typedoc.*", + "!**/.prettierrc*", + "!**/.gitignore", + "!**/eslint.config.mjs", + "!**/*.md", + "!**/LICENSE" + ], + "ignore": [] +} diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d015073ee9..73df58def0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -3,10 +3,11 @@ Thank you for submitting a pull request! Please verify that: * [ ] Code is up-to-date with the `master` branch -* [ ] You've successfully run `grunt test` locally +* [ ] You've successfully run `yarn test` locally * [ ] If applicable, there are new or updated unit tests validating the change * [ ] If applicable, there are new or updated @types * [ ] If applicable, documentation has been updated +* [ ] If the change is releasable, you've added a changeset (`yarn changeset`) — see CONTRIBUTING.md --> ## Description diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000000..e21258cafc --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,125 @@ +# Phase 2 of the release pipeline: publish, tag, release, and refresh `prod`. +# +# Fires when a release commit lands on `master` (the squash-merge of the PR opened +# by release.yml, whose title starts with "Release v"). It: +# 1. publishes every changed public package to npm with Yarn (NOT changeset publish), +# 2. tags v + cuts one aggregated GitHub Release — only when core changed +# (detected by the tag not existing yet), +# 3. force-updates `prod` to the merged release commit — always. +# +# Channel: derived from the version suffix. A prerelease version (e.g. 4.4.0-beta.0, +# produced by `changeset pre enter beta`) publishes under that dist-tag as a GitHub +# prerelease; a plain version publishes under `latest`. +# +# Required repository secrets: +# NPM_TOKEN — npm automation token (org 2FA must allow automation tokens) +# RELEASE_APP_ID — GitHub App id (contents:write to push tags + prod) +# RELEASE_APP_PRIVATE_KEY — GitHub App private key +name: Release (publish) + +on: + push: + branches: + - master + +concurrency: + group: release-publish + cancel-in-progress: false + +jobs: + publish: + # Only run for release commits. + if: ${{ github.repository == 'clientIO/joint' && startsWith(github.event.head_commit.message, 'Release v') }} + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.RELEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + + - name: Checkout master + uses: actions/checkout@v4 + with: + ref: master + # Need tags to detect whether v already exists. + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} + + - name: Setup Node.js v22 + uses: actions/setup-node@v4 + with: + node-version: 22.14.0 + registry-url: 'https://registry.npmjs.org' + + - name: Cache Yarn + uses: actions/cache@v4 + with: + path: | + .yarn/cache + key: yarn-cache-${{ hashFiles('yarn.lock') }} + + - name: Install dependencies + run: yarn install --immutable + + # Derive the release facts from repository state (changesets are already + # consumed at this point, so we cannot re-run the release plan). + - name: Derive release facts + id: facts + run: | + CORE_VERSION=$(node -p "require('./packages/joint-core/package.json').version") + echo "core_version=$CORE_VERSION" >> "$GITHUB_OUTPUT" + + # Channel: prerelease suffix -> that dist-tag; otherwise `latest`. + if [[ "$CORE_VERSION" == *-* ]]; then + PRE_ID="${CORE_VERSION#*-}" # e.g. beta.0 + DIST_TAG="${PRE_ID%%.*}" # e.g. beta + IS_PRERELEASE=true + else + DIST_TAG="latest" + IS_PRERELEASE=false + fi + echo "dist_tag=$DIST_TAG" >> "$GITHUB_OUTPUT" + echo "is_prerelease=$IS_PRERELEASE" >> "$GITHUB_OUTPUT" + + # Core changed this release iff its tag does not exist yet. + if git rev-parse -q --verify "refs/tags/v$CORE_VERSION" >/dev/null; then + echo "core_changed=false" >> "$GITHUB_OUTPUT" + else + echo "core_changed=true" >> "$GITHUB_OUTPUT" + fi + + # Publish with Yarn (resolves workspace: ranges; skips prepublishOnly guards; + # topological order; --no-private excludes examples; --tolerate-republish skips + # already-published versions). + - name: Publish to npm + env: + YARN_NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + yarn release:publish --tag "${{ steps.facts.outputs.dist_tag }}" + + # Tag + one aggregated GitHub Release — only when core changed. + - name: Tag & GitHub Release + if: ${{ steps.facts.outputs.core_changed == 'true' }} + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + CORE_VERSION="${{ steps.facts.outputs.core_version }}" + git tag "v$CORE_VERSION" + git push origin "v$CORE_VERSION" + NOTES_ARG=(--notes-file RELEASE_NOTES.md) + [ -s RELEASE_NOTES.md ] || NOTES_ARG=(--generate-notes) + if [ "${{ steps.facts.outputs.is_prerelease }}" = "true" ]; then + gh release create "v$CORE_VERSION" --title "Release v$CORE_VERSION" "${NOTES_ARG[@]}" --prerelease + else + gh release create "v$CORE_VERSION" --title "Release v$CORE_VERSION" "${NOTES_ARG[@]}" --latest + fi + + # Refresh `prod` to the merged release commit — ALWAYS (even when core did + # not change) so JointJS+ trials/builds always track the latest master. + - name: Refresh prod branch + run: git push --force origin master:prod diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..a61bd47f0e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,147 @@ +# Phase 1 of the release pipeline: prepare the release and open the release PR. +# +# The maintainer's "release button": dispatch this workflow. It renders the root +# CHANGELOG + RELEASE_NOTES.md from the pending changesets, applies the version +# bumps (`changeset version`), commits the result to a `release/next` branch, and +# opens an auto-merging PR to `master`. Merging that PR triggers Phase 2 +# (`publish.yml`), which publishes to npm, tags, cuts the GitHub Release, and +# refreshes `prod` — all automatically. +# +# Pre-releases (beta/next): run `yarn changeset pre enter ` on master +# first so the versions carry a `-.N` suffix; Phase 2 detects the suffix and +# publishes under that dist-tag as a prerelease. `yarn changeset pre exit` returns +# to stable `latest` releases. +# +# Required repository secrets: +# RELEASE_APP_ID — GitHub App id (App must have contents:write + pull-requests:write) +# RELEASE_APP_PRIVATE_KEY — GitHub App private key +# (Using an App token, not GITHUB_TOKEN, so the opened PR triggers test-pr.yml.) +name: Release (prepare) + +on: + workflow_dispatch: + inputs: + dist_tag: + description: 'Release channel. For next/beta, first run `yarn changeset pre enter ` on master.' + type: choice + options: + - latest + - next + - beta + default: latest + dry_run: + description: 'Render + version + build, but do not push the branch or open the PR.' + type: boolean + default: false + +# Prevent overlapping release preparations. +concurrency: + group: release-prepare + cancel-in-progress: false + +jobs: + prepare: + if: ${{ github.repository == 'clientIO/joint' }} + runs-on: ubuntu-latest + steps: + # A GitHub App token (not GITHUB_TOKEN) so the release PR triggers CI. + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.RELEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + + # Full history: the changelog renderer resolves each row's `master` commit + # (for the GitHub Release notes) from when its changeset was added. + - name: Checkout master + uses: actions/checkout@v4 + with: + ref: master + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} + + - name: Setup Node.js v22 + uses: actions/setup-node@v4 + with: + node-version: 22.14.0 + + - name: Cache Yarn + uses: actions/cache@v4 + with: + path: | + .yarn/cache + key: yarn-cache-${{ hashFiles('yarn.lock') }} + + - name: Install dependencies + run: yarn install --immutable + + # Nothing to release if there are no changesets (README.md excluded). + - name: Guard — changesets present + run: | + shopt -s nullglob + count=0 + for f in .changeset/*.md; do + [ "$(basename "$f")" = "README.md" ] || count=$((count+1)) + done + if [ "$count" -eq 0 ]; then + echo "::error::No changesets found — nothing to release." + exit 1 + fi + echo "Found $count changeset(s)." + + # Compute the commit/PR title from the release plan (before changesets are consumed). + - name: Compute release info + id: info + run: node scripts/release-info.mjs + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # Render CHANGELOG + RELEASE_NOTES.md (while changesets exist), then apply + # bumps + workspace-range updates + dependent bumps and delete the changesets. + - name: Version packages + run: | + yarn release:version + yarn install --mode update-lockfile + + # Make sure the bumped repo still builds. + - name: Sanity build + run: yarn dist + + - name: Push branch & open release PR + if: ${{ inputs.dry_run == false }} + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + RELEASE_TITLE: ${{ steps.info.outputs.release_title }} + run: | + BRANCH="release/next" + git checkout -B "$BRANCH" + git add -A + git commit -m "$RELEASE_TITLE" + git push --force origin "$BRANCH" + # Open (or update) the PR and enable squash auto-merge. + if gh pr view "$BRANCH" --json number >/dev/null 2>&1; then + echo "PR already exists for $BRANCH; branch updated." + else + gh pr create --base master --head "$BRANCH" \ + --title "$RELEASE_TITLE" \ + --body-file RELEASE_NOTES.md \ + --label release + fi + gh pr merge "$BRANCH" --auto --squash + + - name: Dry-run summary + if: ${{ inputs.dry_run == true }} + run: | + echo "## Dry run — release NOT pushed" >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + echo "channel: ${{ inputs.dist_tag }}" >> "$GITHUB_STEP_SUMMARY" + echo "${{ steps.info.outputs.release_title }}" >> "$GITHUB_STEP_SUMMARY" + echo "released: ${{ steps.info.outputs.released }}" >> "$GITHUB_STEP_SUMMARY" + echo '' >> "$GITHUB_STEP_SUMMARY" + echo "--- CHANGELOG (new top) ---" >> "$GITHUB_STEP_SUMMARY" + head -n 40 CHANGELOG >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/test-pr.yml b/.github/workflows/test-pr.yml index 162955f372..2172754b3b 100644 --- a/.github/workflows/test-pr.yml +++ b/.github/workflows/test-pr.yml @@ -62,6 +62,17 @@ jobs: id: install-dependencies run: yarn install --immutable + # Enforce that a PR touching releasable code (see `changedFilePatterns` + # in .changeset/config.json) also adds a changeset. Docs/test/demo-only + # PRs are exempt automatically. The release PR (branch `release/*`) is + # exempt because it *consumes* changesets rather than adding them. + - name: Enforce changeset + id: changeset-status + if: ${{ github.event_name == 'pull_request' && !startsWith(github.head_ref, 'release/') }} + run: | + git fetch origin master:refs/remotes/origin/master || true + yarn changeset status --since=origin/master + # Build the joint project - name: Prepare joint id: prepare-joint diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 439707dfc8..543a56e2a2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,6 +72,7 @@ Before submitting a PR, please verify: - [ ] If applicable, there are new or updated unit tests validating the change - [ ] If applicable, there are new or updated @types - [ ] If applicable, documentation has been updated +- [ ] You've added a changeset (`yarn changeset`) if the change is releasable ### Commit Message Format @@ -84,6 +85,46 @@ Examples: Types: `feat`, `fix`, `style`, `refactor`, `test`, `chore`, `example` +### Changesets + +Versioning and the changelog are driven by [Changesets](https://github.com/changesets/changesets). +If your PR changes releasable code, add a changeset: + +```bash +yarn changeset +``` + +Pick the affected package(s) and bump type, then write the changelog line(s) in the +**same `type(scope): description` form as commits** — one row per line: + +```markdown +--- +"@joint/core": minor +--- +feat(dia.Paper): add `originX` and `originY` options to `getFitToContentArea()` +fix(routers.rightAngle): to allow zero-value margins and un-clamp `minPathMargin` +``` + +Commit the generated `.changeset/*.md` with your PR. CI (`changeset status`) fails a PR that +changes releasable code without a changeset; docs-, test-, and demo-only PRs are exempt. Don't +add PR/commit links by hand — the GitHub Release notes link each row to its `master` commit +automatically. See [`.changeset/README.md`](.changeset/README.md) for the full body convention +(feat/fix filtering, `!` for breaking changes, which packages appear in the CHANGELOG). + +### Releasing (maintainers) + +Releases are automated in two phases; no manual npm/tag/GitHub-Release steps. + +1. **Prepare** — dispatch the **Release (prepare)** workflow (`release.yml`). It renders the root + `CHANGELOG` + `RELEASE_NOTES.md` from the pending changesets, applies the bumps + (`changeset version`), and opens an auto-merging `release/next` PR to `master`. +2. **Publish** — merging that PR triggers **Release (publish)** (`publish.yml`), which publishes the + changed packages to npm (via Yarn), tags `vX.Y.Z` + cuts one GitHub Release when `@joint/core` + changed, and force-updates `prod` to the merged commit. + +For a `beta`/`next` release, run `yarn changeset pre enter ` on `master` before step 1 (and +`yarn changeset pre exit` to return to stable). + ## Code Style - TypeScript strict mode is enabled diff --git a/package.json b/package.json index 886bfca87c..981d6b6f79 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,12 @@ "test-e2e-all": "yarn workspaces foreach --all -tvv run test-e2e-all", "lint": "yarn workspaces foreach --all -tvv run lint", "lint-fix": "yarn workspaces foreach --all -tvv run lint-fix", - "pack-all": "yarn workspaces foreach --all -tvv --include \"@joint/core\" --include \"@joint/layout-directed-graph\" --include \"@joint/layout-msagl\" pack --out %s-%v.tgz" + "pack-all": "yarn workspaces foreach --all -tvv --include \"@joint/core\" --include \"@joint/layout-directed-graph\" --include \"@joint/layout-msagl\" pack --out %s-%v.tgz", + "changeset": "changeset", + "release:status": "changeset status --since=origin/master", + "release:changelog": "node scripts/changelog-from-changesets.mjs", + "release:version": "yarn release:changelog && changeset version", + "release:publish": "yarn workspaces foreach --all --no-private -t npm publish --access public --tolerate-republish" }, "workspaces": [ "./packages/*", @@ -38,5 +43,8 @@ "npm": "11.2.0", "yarn": "4.7.0" }, - "packageManager": "yarn@4.7.0" + "packageManager": "yarn@4.7.0", + "devDependencies": { + "@changesets/cli": "2.31.1" + } } diff --git a/scripts/changelog-from-changesets.mjs b/scripts/changelog-from-changesets.mjs new file mode 100644 index 0000000000..c58b1dfa79 --- /dev/null +++ b/scripts/changelog-from-changesets.mjs @@ -0,0 +1,372 @@ +#!/usr/bin/env node +// Render the root `CHANGELOG` (and `RELEASE_NOTES.md`) from the pending +// `.changeset/*.md` files, in JointJS's bespoke format. +// +// Why this exists: Changesets owns *versioning* (bumps + `workspace:` ranges + +// rule-based dependent bumps), but it writes per-package `CHANGELOG.md` files in +// its own format. JointJS wants a single root `CHANGELOG` grouped by version with +// a specific class-hierarchy ordering, so we disable Changesets' changelog +// (`"changelog": false` in .changeset/config.json) and render it ourselves. +// +// Run this BEFORE `changeset version` (the changesets must still exist). It reads +// the changesets, computes the resulting versions via Changesets' own release-plan +// API (so the numbers match exactly what `changeset version` will write, including +// the `updateInternalDependents: "out-of-range"` cascade), and prepends the entry +// to `CHANGELOG` / writes `RELEASE_NOTES.md`. It does NOT bump anything or delete +// changesets — that is `changeset version`'s job, run right after. +// +// Usage: +// node scripts/changelog-from-changesets.mjs [--dry-run] [--date DD-MM-YYYY] +// [--changelog CHANGELOG] [--release-notes RELEASE_NOTES.md] [--repo owner/name] +// +// Changeset body convention (see .changeset/README.md), one row per line: +// ()[!]: +// e.g. +// feat(dia.Paper): add `originX` and `originY` options to `getFitToContentArea()` +// fix(routers.rightAngle): to allow zero-value margins and un-clamp `minPathMargin` +// feat(dia.Graph)!: drop the deprecated `cellNamespace` setter +// feat: packaging changes <- no class -> sorts first +// Non feat/fix lines are ignored (they never reach the CHANGELOG). +// +// The GitHub Release notes (RELEASE_NOTES.md) automatically link each row to the +// commit on the base branch that introduced its changeset (the PR's merge/squash +// commit). The root CHANGELOG never carries links. Requires git history — run in a +// non-shallow checkout; links are silently omitted when a commit can't be resolved +// (e.g. an uncommitted changeset in a local dry run). + +import { readFileSync, writeFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { execFileSync } from 'node:child_process'; +import { getPackages } from '@manypkg/get-packages'; +import readChangesetsMod from '@changesets/read'; +import assembleReleasePlanMod from '@changesets/assemble-release-plan'; +import { read as readConfig } from '@changesets/config'; + +const readChangesets = readChangesetsMod.default ?? readChangesetsMod; +const assembleReleasePlan = assembleReleasePlanMod.default ?? assembleReleasePlanMod; + +// --------------------------------------------------------------------------- +// Tunables — the JointJS "Section 2" hierarchy. Edit here to change ordering. +// --------------------------------------------------------------------------- + +// Packages rendered into the root CHANGELOG / RELEASE_NOTES, in display order. +// Other publishable packages (cli, decorators, shapes-*, vitest-plugin, eslint-config) +// are still versioned & published by the pipeline, but are intentionally NOT +// listed in the human-facing CHANGELOG (matches historical convention). +const DOCUMENTED_PACKAGES = [ + '@joint/react', + '@joint/core', + '@joint/layout-directed-graph', + '@joint/layout-msagl', +]; + +// Class (scope) namespace priority within a package section — the JointJS +// "Section 2" hierarchy, most-abstract to most-foundational. A row's namespace is +// the first dotted segment of its class (e.g. `dia.Paper` -> `dia`). Rows sort by: +// 1. rows with NO class listed (e.g. `feat: packaging changes`) come first, +// 2. then this namespace index, +// 3. within the namespace: the explicit SUBCLASS_ORDER if any, else alphabetical +// by full class (so the bare namespace change comes first), +// 4. feat before fix within the exact same class, +// 5. authored order. +// Unknown namespaces (e.g. @joint/react's ``, `useCells`) sort last, +// alphabetically among themselves. +const NAMESPACE_ORDER = [ + 'shapes', + 'dia', + 'elementTools', + 'linkTools', + 'anchors', + 'linkAnchors', + 'highlighters', + 'layout', + 'connectionPoints', + 'connectionStrategies', + 'connectors', + 'routers', + 'mvc', + 'util', + 'config', + 'Vectorizer', + 'Geometry', +]; + +// Namespaces whose subclasses have a fixed (non-alphabetical) order. The bare +// namespace (index 0) comes first; classes not listed here sort after the listed +// ones, alphabetically. +const SUBCLASS_ORDER = { + dia: [ + 'dia', + 'dia.Paper', + 'dia.PaperLayer', + 'dia.ElementView', + 'dia.LinkView', + 'dia.CellView', + 'dia.Graph', + 'dia.Element', + 'dia.Link', + 'dia.Cell', + 'dia.attributes', + 'dia.ports', + 'dia.HighlighterView', + 'dia.ToolsView', + 'dia.ToolView', + ], + mvc: ['mvc', 'mvc.View', 'mvc.Listener'], +}; + +// Scope aliases -> canonical namespace (short commit-style scopes for the same class). +const NAMESPACE_ALIASES = { + V: 'Vectorizer', + g: 'Geometry', + geometry: 'Geometry', +}; + +// --------------------------------------------------------------------------- + +function parseArgs(argv) { + const args = { dryRun: false, date: null, changelog: 'CHANGELOG', releaseNotes: 'RELEASE_NOTES.md', repo: 'clientIO/joint' }; + for (let i = 0; i < argv.length; i++) { + const a = argv[i]; + if (a === '--dry-run') args.dryRun = true; + else if (a === '--date') args.date = argv[++i]; + else if (a === '--changelog') args.changelog = argv[++i]; + else if (a === '--release-notes') args.releaseNotes = argv[++i]; + else if (a === '--repo') args.repo = argv[++i]; + else throw new Error(`Unknown argument: ${a}`); + } + return args; +} + +function todayDDMMYYYY() { + const d = new Date(); + const pad = (n) => String(n).padStart(2, '0'); + return `${pad(d.getDate())}-${pad(d.getMonth() + 1)}-${d.getFullYear()}`; +} + +// Parse a single changeset body line into a row, or null if it is not a +// feat/fix line (those are dropped from the changelog). +function parseRow(line, authoredIndex) { + const m = line.match(/^\s*(feat|fix)\s*(?:\(([^)]*)\))?\s*(!)?\s*:\s*(.+?)\s*$/); + if (!m) return null; + const [, type, rawClass, bang, text] = m; + return { + type, + className: (rawClass || '').trim(), + breaking: Boolean(bang), + text, + commit: null, // resolved per changeset (see resolveCommitSha) + authoredIndex, + }; +} + +// Resolve the commit on the base branch that introduced a changeset file (the +// merge/squash commit of the PR that added it), for linking rows in the GitHub +// Release notes. Returns null when it can't be determined — e.g. the changeset is +// still uncommitted in the working tree (local dry run) or history is unavailable. +function resolveCommitSha(changesetId, cwd) { + try { + const out = execFileSync( + 'git', + ['log', '--diff-filter=A', '-1', '--format=%H', '--', `.changeset/${changesetId}.md`], + { cwd, stdio: ['ignore', 'pipe', 'ignore'] } + ) + .toString() + .trim(); + return out || null; + } catch { + return null; + } +} + +function namespaceKey(className) { + const first = className.split('.')[0]; + return NAMESPACE_ALIASES[first] ?? first; +} + +function namespaceIndex(className) { + if (!className) return -1; // rows with no class listed sort first + const idx = NAMESPACE_ORDER.indexOf(namespaceKey(className)); + return idx === -1 ? NAMESPACE_ORDER.length : idx; +} + +// Order two rows already known to share a namespace: explicit SUBCLASS_ORDER +// position if any (listed classes first, in order), otherwise alphabetical by +// full class (so the bare namespace comes first). +function compareWithinNamespace(a, b) { + const order = SUBCLASS_ORDER[namespaceKey(a.className)]; + if (order) { + const ra = order.indexOf(a.className); + const rb = order.indexOf(b.className); + const na = ra === -1 ? Number.POSITIVE_INFINITY : ra; + const nb = rb === -1 ? Number.POSITIVE_INFINITY : rb; + if (na !== nb) return na - nb; + // Both unlisted (or same rank): fall back to alphabetical. + } + if (a.className !== b.className) return a.className < b.className ? -1 : 1; + return 0; +} + +function compareRows(a, b) { + const na = namespaceIndex(a.className); + const nb = namespaceIndex(b.className); + if (na !== nb) return na - nb; + const withinNs = compareWithinNamespace(a, b); + if (withinNs !== 0) return withinNs; + const ta = a.type === 'feat' ? 0 : 1; // feat before fix within the same class + const tb = b.type === 'feat' ? 0 : 1; + if (ta !== tb) return ta - tb; + return a.authoredIndex - b.authoredIndex; +} + +// Render one CHANGELOG row body (without the leading " * "). +function renderRowText(row) { + const cls = row.className ? `${row.breaking ? '!' : ''}${row.className} - ` : ''; + const verb = row.type === 'fix' ? 'fix ' : ''; + return `${cls}${verb}${row.text}`; +} + +async function main() { + const args = parseArgs(process.argv.slice(2)); + const date = args.date || todayDDMMYYYY(); + const cwd = process.cwd(); + + const packages = await getPackages(cwd); + const config = await readConfig(cwd, packages); + const changesets = await readChangesets(cwd); + + if (changesets.length === 0) { + console.error('No changesets found — nothing to render.'); + process.exitCode = 1; + return; + } + + const plan = assembleReleasePlan(changesets, packages, config, undefined); + const newVersionByPkg = new Map(); + for (const rel of plan.releases) { + if (rel.type !== 'none') newVersionByPkg.set(rel.name, rel.newVersion); + } + + // Collect rows per documented package. + const documented = new Set(DOCUMENTED_PACKAGES); + const rowsByPackage = new Map(); // pkg -> Row[] + let authoredCounter = 0; + for (const cs of changesets) { + const targets = cs.releases.map((r) => r.name).filter((n) => documented.has(n)); + if (targets.length === 0) continue; + const rows = cs.summary + .split(/\r?\n/) + .map((line) => line.trim()) + .filter(Boolean) + .map((line) => parseRow(line, authoredCounter++)) + .filter(Boolean); + if (rows.length === 0) continue; + // All rows from this changeset link to the commit that added the changeset. + const commit = resolveCommitSha(cs.id, cwd); + for (const r of rows) r.commit = commit; + for (const pkg of targets) { + if (!rowsByPackage.has(pkg)) rowsByPackage.set(pkg, []); + rowsByPackage.get(pkg).push(...rows.map((r) => ({ ...r }))); + } + } + + if (rowsByPackage.size === 0) { + console.error('No feat/fix rows for documented packages — CHANGELOG left unchanged.'); + // Still emit an empty RELEASE_NOTES so downstream steps have a file. + if (!args.dryRun) writeFileSync(resolve(cwd, args.releaseNotes), ''); + return; + } + + // Group documented packages by their new version -> version blocks. + const blocksByVersion = new Map(); // version -> [{pkg, rows}] + for (const pkg of DOCUMENTED_PACKAGES) { + if (!rowsByPackage.has(pkg)) continue; + const version = newVersionByPkg.get(pkg); + if (!version) { + console.error(`WARNING: ${pkg} has changelog rows but no version bump in the plan — skipping.`); + continue; + } + const rows = rowsByPackage.get(pkg).sort(compareRows); + if (!blocksByVersion.has(version)) blocksByVersion.set(version, []); + blocksByVersion.get(version).push({ pkg, rows }); + } + + // Order blocks by version, highest first. + const versions = [...blocksByVersion.keys()].sort(compareSemverDesc); + + // ---- Root CHANGELOG entry ------------------------------------------------ + const blockStrings = versions.map((version) => { + const header = `${date} (v${version})`; + const subsections = blocksByVersion + .get(version) + // package subsections in documented order + .sort((a, b) => DOCUMENTED_PACKAGES.indexOf(a.pkg) - DOCUMENTED_PACKAGES.indexOf(b.pkg)) + .map(({ pkg, rows }) => { + const lines = rows.map((r) => ` * ${renderRowText(r)}`); + return ` ${pkg}\n${lines.join('\n')}`; + }); + return `${header}\n\n${subsections.join('\n\n')}`; + }); + const changelogEntry = blockStrings.join('\n\n'); + + // ---- RELEASE_NOTES.md (GitHub Release body), grouped by package ---------- + const releaseNotes = DOCUMENTED_PACKAGES.filter((pkg) => rowsByPackage.has(pkg)) + .map((pkg) => { + const version = newVersionByPkg.get(pkg); + const rows = rowsByPackage.get(pkg).sort(compareRows); + const lines = rows.map((r) => { + const cls = r.className ? `**${r.breaking ? '!' : ''}${r.className}** — ` : ''; + const verb = r.type === 'fix' ? 'fix ' : ''; + const link = r.commit + ? ` ([\`${r.commit.slice(0, 7)}\`](https://github.com/${args.repo}/commit/${r.commit}))` + : ''; + return `- ${cls}${verb}${r.text}${link}`; + }); + return `## ${pkg}@${version}\n\n${lines.join('\n')}`; + }) + .join('\n\n'); + + if (args.dryRun) { + console.log('===== CHANGELOG entry =====\n'); + console.log(changelogEntry); + console.log('\n===== RELEASE_NOTES.md =====\n'); + console.log(releaseNotes); + console.log('\n===== released versions ====='); + for (const [name, v] of newVersionByPkg) console.log(` ${name} -> ${v}`); + return; + } + + // Prepend to CHANGELOG. + const changelogPath = resolve(cwd, args.changelog); + const existing = readFileSync(changelogPath, 'utf8'); + writeFileSync(changelogPath, `${changelogEntry}\n\n${existing}`); + + // Write RELEASE_NOTES.md (overwrite). + writeFileSync(resolve(cwd, args.releaseNotes), `${releaseNotes}\n`); + + console.error(`Rendered ${versions.length} version block(s) into ${args.changelog} and wrote ${args.releaseNotes}.`); +} + +// Descending semver compare good enough for X.Y.Z[-tag.N]. +function compareSemverDesc(a, b) { + const parse = (v) => { + const [core, pre] = v.split('-'); + const nums = core.split('.').map(Number); + return { nums, pre: pre || null }; + }; + const pa = parse(a); + const pb = parse(b); + for (let i = 0; i < 3; i++) { + if ((pa.nums[i] || 0) !== (pb.nums[i] || 0)) return (pb.nums[i] || 0) - (pa.nums[i] || 0); + } + // A version WITH a prerelease tag is lower than one without. + if (pa.pre && !pb.pre) return 1; + if (!pa.pre && pb.pre) return -1; + if (pa.pre && pb.pre) return pa.pre < pb.pre ? 1 : pa.pre > pb.pre ? -1 : 0; + return 0; +} + +main().catch((err) => { + console.error(err); + process.exitCode = 1; +}); diff --git a/scripts/release-info.mjs b/scripts/release-info.mjs new file mode 100644 index 0000000000..7d26ea0649 --- /dev/null +++ b/scripts/release-info.mjs @@ -0,0 +1,59 @@ +#!/usr/bin/env node +// Print release metadata derived from the pending `.changeset/*.md` files, for +// the release workflow to build the commit/PR title. Run BEFORE `changeset version`. +// +// Emits `KEY=VALUE` lines to stdout, and also appends them to $GITHUB_OUTPUT when set: +// core_version= +// core_changed= (is @joint/core in the release plan?) +// release_title=Release v (), ... (documented packages only) +// released= + +import { appendFileSync } from 'node:fs'; +import { getPackages } from '@manypkg/get-packages'; +import readChangesetsMod from '@changesets/read'; +import assembleReleasePlanMod from '@changesets/assemble-release-plan'; +import { read as readConfig } from '@changesets/config'; + +const readChangesets = readChangesetsMod.default ?? readChangesetsMod; +const assembleReleasePlan = assembleReleasePlanMod.default ?? assembleReleasePlanMod; + +// Keep in sync with DOCUMENTED_PACKAGES in changelog-from-changesets.mjs. +const DOCUMENTED_PACKAGES = [ + '@joint/react', + '@joint/core', + '@joint/layout-directed-graph', + '@joint/layout-msagl', +]; + +const cwd = process.cwd(); +const packages = await getPackages(cwd); +const config = await readConfig(cwd, packages); +const changesets = await readChangesets(cwd); +const plan = assembleReleasePlan(changesets, packages, config, undefined); + +const released = plan.releases.filter((r) => r.type !== 'none'); +const versionByPkg = new Map(released.map((r) => [r.name, r.newVersion])); + +const core = versionByPkg.get('@joint/core'); +const coreVersion = core ?? packages.packages.find((p) => p.packageJson.name === '@joint/core').packageJson.version; +const coreChanged = versionByPkg.has('@joint/core'); + +const titleParts = DOCUMENTED_PACKAGES.filter((p) => versionByPkg.has(p)).map( + (p) => `v${versionByPkg.get(p)} (${p})` +); +// Fall back to any released package so the title is never empty (still starts with "Release v"). +if (titleParts.length === 0) { + for (const r of released) titleParts.push(`v${r.newVersion} (${r.name})`); +} +const releaseTitle = `Release ${titleParts.join(', ')}`; +const releasedList = released.map((r) => `${r.name}@${r.newVersion}`).join(','); + +const out = [ + `core_version=${coreVersion}`, + `core_changed=${coreChanged}`, + `release_title=${releaseTitle}`, + `released=${releasedList}`, +]; + +for (const line of out) console.log(line); +if (process.env.GITHUB_OUTPUT) appendFileSync(process.env.GITHUB_OUTPUT, out.join('\n') + '\n'); diff --git a/yarn.lock b/yarn.lock index 3aeb50bded..547ec1314a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3056,6 +3056,13 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.5.5": + version: 7.29.7 + resolution: "@babel/runtime@npm:7.29.7" + checksum: 10/9883b4951787779fd382b121f22f92966d85f19434841f65fb00b2dfec232107e139683f47c6f252891826ad8ee18317b46c3a0e4819116a9885f47b46d7126a + languageName: node + linkType: hard + "@babel/template@npm:^7.26.9, @babel/template@npm:^7.27.2, @babel/template@npm:^7.28.6": version: 7.28.6 resolution: "@babel/template@npm:7.28.6" @@ -3135,6 +3142,239 @@ __metadata: languageName: node linkType: hard +"@changesets/apply-release-plan@npm:^7.1.1": + version: 7.1.1 + resolution: "@changesets/apply-release-plan@npm:7.1.1" + dependencies: + "@changesets/config": "npm:^3.1.4" + "@changesets/get-version-range-type": "npm:^0.4.0" + "@changesets/git": "npm:^3.0.4" + "@changesets/should-skip-package": "npm:^0.1.2" + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + detect-indent: "npm:^6.0.0" + fs-extra: "npm:^7.0.1" + lodash.startcase: "npm:^4.4.0" + outdent: "npm:^0.5.0" + prettier: "npm:^2.7.1" + resolve-from: "npm:^5.0.0" + semver: "npm:^7.5.3" + checksum: 10/6810c645c08f5f54a7d40025e41b79fe0d52cf83830b1ce1562d34ba6cbc26d9ce2c7482580623ebf3ea5fa1b01f32e9f61f6b7e1b4296d8ac45d8ba7a6bcae0 + languageName: node + linkType: hard + +"@changesets/assemble-release-plan@npm:^6.0.10": + version: 6.0.10 + resolution: "@changesets/assemble-release-plan@npm:6.0.10" + dependencies: + "@changesets/errors": "npm:^0.2.0" + "@changesets/get-dependents-graph": "npm:^2.1.4" + "@changesets/should-skip-package": "npm:^0.1.2" + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + semver: "npm:^7.5.3" + checksum: 10/75abf5d008d7aed4f29cdb8c46a7d3bcb16531a317be7d2cdbd51b6b6a80cffb350ac5dafe658954402440de1836cd0096f1b69c0768c2bec8939f4ff26cc34e + languageName: node + linkType: hard + +"@changesets/changelog-git@npm:^0.2.1": + version: 0.2.1 + resolution: "@changesets/changelog-git@npm:0.2.1" + dependencies: + "@changesets/types": "npm:^6.1.0" + checksum: 10/c22f3c0baf50c102a6890046351ee42f65ff6d58747ba4f75e5e40da1ed5fbcfd0dc2d11cdfb86acbb3262e58acb93f096c798827cac570c1e22e8f32f58a30f + languageName: node + linkType: hard + +"@changesets/cli@npm:2.31.1": + version: 2.31.1 + resolution: "@changesets/cli@npm:2.31.1" + dependencies: + "@changesets/apply-release-plan": "npm:^7.1.1" + "@changesets/assemble-release-plan": "npm:^6.0.10" + "@changesets/changelog-git": "npm:^0.2.1" + "@changesets/config": "npm:^3.1.4" + "@changesets/errors": "npm:^0.2.0" + "@changesets/get-dependents-graph": "npm:^2.1.4" + "@changesets/get-release-plan": "npm:^4.0.16" + "@changesets/git": "npm:^3.0.4" + "@changesets/logger": "npm:^0.1.1" + "@changesets/pre": "npm:^2.0.2" + "@changesets/read": "npm:^0.6.7" + "@changesets/should-skip-package": "npm:^0.1.2" + "@changesets/types": "npm:^6.1.0" + "@changesets/write": "npm:^0.4.0" + "@inquirer/external-editor": "npm:^1.0.2" + "@manypkg/get-packages": "npm:^1.1.3" + ansi-colors: "npm:^4.1.3" + enquirer: "npm:^2.4.1" + fs-extra: "npm:^7.0.1" + mri: "npm:^1.2.0" + package-manager-detector: "npm:^0.2.0" + picocolors: "npm:^1.1.0" + resolve-from: "npm:^5.0.0" + semver: "npm:^7.5.3" + spawndamnit: "npm:^3.0.1" + term-size: "npm:^2.1.0" + bin: + changeset: bin.js + checksum: 10/cdd707e821cd1b589eb2301b9c2f4219c118a6f4bdf20ec9f0a0ad43e99a8a4ef5883f90ab926723cee904217f7aabe14ee43346eda873495254f40e28672585 + languageName: node + linkType: hard + +"@changesets/config@npm:^3.1.4": + version: 3.1.4 + resolution: "@changesets/config@npm:3.1.4" + dependencies: + "@changesets/errors": "npm:^0.2.0" + "@changesets/get-dependents-graph": "npm:^2.1.4" + "@changesets/logger": "npm:^0.1.1" + "@changesets/should-skip-package": "npm:^0.1.2" + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + fs-extra: "npm:^7.0.1" + micromatch: "npm:^4.0.8" + checksum: 10/d563b0613c3fa387d517bd137c64755bd8246f74dc070c6a9cd5d2d05b2cd7b95cc042f8064fc01f4c18b04d5becc28e1c35f72e386e7c89ec5f0de781d30bf6 + languageName: node + linkType: hard + +"@changesets/errors@npm:^0.2.0": + version: 0.2.0 + resolution: "@changesets/errors@npm:0.2.0" + dependencies: + extendable-error: "npm:^0.1.5" + checksum: 10/4b79373f92287af4f723e8dbbccaf0299aa8735fc043243d0ad587f04a7614615ea50180be575d4438b9f00aa82d1cf85e902b77a55bdd3e0a8dd97e77b18c60 + languageName: node + linkType: hard + +"@changesets/get-dependents-graph@npm:^2.1.4": + version: 2.1.4 + resolution: "@changesets/get-dependents-graph@npm:2.1.4" + dependencies: + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + picocolors: "npm:^1.1.0" + semver: "npm:^7.5.3" + checksum: 10/4b4895a69a47315e286b365d68af00216e29cba0b1033374cc4b7db0ec75625dd829e9e54aecf2ca7131ba7cc50f1ff1f51beb79a901b6da845c310f85d94464 + languageName: node + linkType: hard + +"@changesets/get-release-plan@npm:^4.0.16": + version: 4.0.16 + resolution: "@changesets/get-release-plan@npm:4.0.16" + dependencies: + "@changesets/assemble-release-plan": "npm:^6.0.10" + "@changesets/config": "npm:^3.1.4" + "@changesets/pre": "npm:^2.0.2" + "@changesets/read": "npm:^0.6.7" + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + checksum: 10/bb19b1ed535071d0d706731c91f19f6e87f18f78e583fa1e4fb7ab944c166ce1be5aa5f70b31cc5178aee6155b214522e49e0fd44a74eab416f8524411e07be6 + languageName: node + linkType: hard + +"@changesets/get-version-range-type@npm:^0.4.0": + version: 0.4.0 + resolution: "@changesets/get-version-range-type@npm:0.4.0" + checksum: 10/9868e99b31af652d3fa08fc33d55b9636f2feed1f4efdb318a6dbb4bb061281868de089b93041ce7f2775ab9cf454b92b1199767d0f4f228d8bbc483e61d2fd8 + languageName: node + linkType: hard + +"@changesets/git@npm:^3.0.4": + version: 3.0.4 + resolution: "@changesets/git@npm:3.0.4" + dependencies: + "@changesets/errors": "npm:^0.2.0" + "@manypkg/get-packages": "npm:^1.1.3" + is-subdir: "npm:^1.1.1" + micromatch: "npm:^4.0.8" + spawndamnit: "npm:^3.0.1" + checksum: 10/4f5a1f3354ec39d530df78b198eaaf2a8ef6cca873dd18efb8706aae09cab04e0d985abd236288644fac5d10cc5cb6ba2538c3e0be023c4d80790ff841f39fa6 + languageName: node + linkType: hard + +"@changesets/logger@npm:^0.1.1": + version: 0.1.1 + resolution: "@changesets/logger@npm:0.1.1" + dependencies: + picocolors: "npm:^1.1.0" + checksum: 10/bbfc050ddd0afdaa95bb790e81894b7548a2def059deeaed1685e22c10ede245ec2264df42bb2200cc0c8bd040e427bcd68a7afcca2633dc263a28e923d7c175 + languageName: node + linkType: hard + +"@changesets/parse@npm:^0.4.3": + version: 0.4.3 + resolution: "@changesets/parse@npm:0.4.3" + dependencies: + "@changesets/types": "npm:^6.1.0" + js-yaml: "npm:^4.1.1" + checksum: 10/f5742266a4ecb90a8283868f2bec740ce7be94745dee7df0b2f47882775d700bdfa3b660c2ec8d06b64153cf9b02cb3d46064f391c62933c9c60a9570763f928 + languageName: node + linkType: hard + +"@changesets/pre@npm:^2.0.2": + version: 2.0.2 + resolution: "@changesets/pre@npm:2.0.2" + dependencies: + "@changesets/errors": "npm:^0.2.0" + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + fs-extra: "npm:^7.0.1" + checksum: 10/daaedd2747492ced61f107d38f90e535607bcb073b10ffac3d9e3bcad1a4cc082370884224fc6785af2d92d37f6b0a3bf853f9759b8fda294878d00d24344415 + languageName: node + linkType: hard + +"@changesets/read@npm:^0.6.7": + version: 0.6.7 + resolution: "@changesets/read@npm:0.6.7" + dependencies: + "@changesets/git": "npm:^3.0.4" + "@changesets/logger": "npm:^0.1.1" + "@changesets/parse": "npm:^0.4.3" + "@changesets/types": "npm:^6.1.0" + fs-extra: "npm:^7.0.1" + p-filter: "npm:^2.1.0" + picocolors: "npm:^1.1.0" + checksum: 10/6bf3fd4b0c743d2ef53cb39c4e52731622949a695f031412ff6ea9f30860620a1d9deb1cfd1af0c92d2e2d275b6d949c0cc1e83c192f2094e0071d690c48a42e + languageName: node + linkType: hard + +"@changesets/should-skip-package@npm:^0.1.2": + version: 0.1.2 + resolution: "@changesets/should-skip-package@npm:0.1.2" + dependencies: + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + checksum: 10/d09fcf1200ee201f0dd5b8049d90e8b5e0cfd34cc94f5c661c4cdab182a8263628733f9bc5886550a92f6f7857339d79fc77f12ffd53559b029a2bf9a2fa7ace + languageName: node + linkType: hard + +"@changesets/types@npm:^4.0.1": + version: 4.1.0 + resolution: "@changesets/types@npm:4.1.0" + checksum: 10/4d7c65a447400ac474b2dc2d79bc1a5341c305fbce4a648ef59d9939bc1bbbbd6852684c417a9a4ef0226468b9cb522b9ac2b5393f21fa5f20f1b12bee94eab5 + languageName: node + linkType: hard + +"@changesets/types@npm:^6.1.0": + version: 6.1.0 + resolution: "@changesets/types@npm:6.1.0" + checksum: 10/2dcd00712cb85d0c53afdd8d0e856b4bf9c0ce8dc36c838c918d44799aacd9ba8659b9ff610ff92b94fc03c8fd2b52c5b05418fcf8a1bd138cd9182414ede373 + languageName: node + linkType: hard + +"@changesets/write@npm:^0.4.0": + version: 0.4.0 + resolution: "@changesets/write@npm:0.4.0" + dependencies: + "@changesets/types": "npm:^6.1.0" + fs-extra: "npm:^7.0.1" + human-id: "npm:^4.1.1" + prettier: "npm:^2.7.1" + checksum: 10/bcea8431a09e282bdf66adbd8411d5d3cc19b4a2df519a42586c912b23a7b3ef18d1d0765e2d1a27ff175e2dfc9ef4c2df95cfa920dd4dd2972aaaf662afc6b9 + languageName: node + linkType: hard + "@clack/core@npm:0.3.5, @clack/core@npm:^0.3.5": version: 0.3.5 resolution: "@clack/core@npm:0.3.5" @@ -4974,7 +5214,7 @@ __metadata: languageName: node linkType: hard -"@inquirer/external-editor@npm:^1.0.3": +"@inquirer/external-editor@npm:^1.0.2, @inquirer/external-editor@npm:^1.0.3": version: 1.0.3 resolution: "@inquirer/external-editor@npm:1.0.3" dependencies: @@ -7000,6 +7240,32 @@ __metadata: languageName: node linkType: hard +"@manypkg/find-root@npm:^1.1.0": + version: 1.1.0 + resolution: "@manypkg/find-root@npm:1.1.0" + dependencies: + "@babel/runtime": "npm:^7.5.5" + "@types/node": "npm:^12.7.1" + find-up: "npm:^4.1.0" + fs-extra: "npm:^8.1.0" + checksum: 10/31e7dde82612a0e37ebb07876d76b1bf2aedc5b285b5e50d94cdf63edbf1fa3970349b84a5837a3c687e5b643e9a4f4588ae1f4b4ae9d412516d57bf977a08db + languageName: node + linkType: hard + +"@manypkg/get-packages@npm:^1.1.3": + version: 1.1.3 + resolution: "@manypkg/get-packages@npm:1.1.3" + dependencies: + "@babel/runtime": "npm:^7.5.5" + "@changesets/types": "npm:^4.0.1" + "@manypkg/find-root": "npm:^1.1.0" + fs-extra: "npm:^8.1.0" + globby: "npm:^11.0.0" + read-yaml-file: "npm:^1.1.0" + checksum: 10/4912e002199ff3974ec48586376a04c5f1815a4faa5f4d36b0698838eec143c9d4e3d42c41e0de009f48a1e2251802ed63c1311ab44de225b50102f85919a248 + languageName: node + linkType: hard + "@mdx-js/react@npm:^3.0.0": version: 3.1.1 resolution: "@mdx-js/react@npm:3.1.1" @@ -10566,6 +10832,13 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^12.7.1": + version: 12.20.55 + resolution: "@types/node@npm:12.20.55" + checksum: 10/1f916a06fff02faadb09a16ed6e31820ce170798b202ef0b14fc244bfbd721938c54a3a99836e185e4414ca461fe96c5bb5c67c3d248f153555b7e6347f061dd + languageName: node + linkType: hard + "@types/node@npm:^20.1.0, @types/node@npm:^20.11.30": version: 20.19.31 resolution: "@types/node@npm:20.19.31" @@ -12175,7 +12448,7 @@ __metadata: languageName: node linkType: hard -"ansi-colors@npm:4.1.3, ansi-colors@npm:^4.1.3": +"ansi-colors@npm:4.1.3, ansi-colors@npm:^4.1.1, ansi-colors@npm:^4.1.3": version: 4.1.3 resolution: "ansi-colors@npm:4.1.3" checksum: 10/43d6e2fc7b1c6e4dc373de708ee76311ec2e0433e7e8bd3194e7ff123ea6a747428fc61afdcf5969da5be3a5f0fd054602bec56fc0ebe249ce2fcde6e649e3c2 @@ -13146,6 +13419,15 @@ __metadata: languageName: node linkType: hard +"better-path-resolve@npm:1.0.0": + version: 1.0.0 + resolution: "better-path-resolve@npm:1.0.0" + dependencies: + is-windows: "npm:^1.0.0" + checksum: 10/5392dbe04e7fe68b944eb37961d9dfa147aaac3ee9ee3f6e13d42e2c9fbe949e68d16e896c14ee9016fa5f8e6e53ec7fd8b5f01b50a32067a7d94ac9cfb9a050 + languageName: node + linkType: hard + "bezier-js@npm:^6.1.4": version: 6.1.4 resolution: "bezier-js@npm:6.1.4" @@ -14881,7 +15163,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.5, cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: @@ -15557,6 +15839,13 @@ __metadata: languageName: node linkType: hard +"detect-indent@npm:^6.0.0": + version: 6.1.0 + resolution: "detect-indent@npm:6.1.0" + checksum: 10/ab953a73c72dbd4e8fc68e4ed4bfd92c97eb6c43734af3900add963fd3a9316f3bc0578b018b24198d4c31a358571eff5f0656e81a1f3b9ad5c547d58b2d093d + languageName: node + linkType: hard + "detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.1, detect-libc@npm:^2.0.3": version: 2.1.2 resolution: "detect-libc@npm:2.1.2" @@ -15646,6 +15935,15 @@ __metadata: languageName: node linkType: hard +"dir-glob@npm:^3.0.1": + version: 3.0.1 + resolution: "dir-glob@npm:3.0.1" + dependencies: + path-type: "npm:^4.0.0" + checksum: 10/fa05e18324510d7283f55862f3161c6759a3f2f8dbce491a2fc14c8324c498286c54282c1f0e933cb930da8419b30679389499b919122952a4f8592362ef4615 + languageName: node + linkType: hard + "dns-packet@npm:^5.2.2": version: 5.6.1 resolution: "dns-packet@npm:5.6.1" @@ -16019,6 +16317,16 @@ __metadata: languageName: node linkType: hard +"enquirer@npm:^2.4.1": + version: 2.4.1 + resolution: "enquirer@npm:2.4.1" + dependencies: + ansi-colors: "npm:^4.1.1" + strip-ansi: "npm:^6.0.1" + checksum: 10/b3726486cd98f0d458a851a03326a2a5dd4d84f37ff94ff2a2960c915e0fc865865da3b78f0877dc36ac5c1189069eca603e82ec63d5bc6b0dd9985bf6426d7a + languageName: node + linkType: hard + "ent@npm:~2.2.0": version: 2.2.2 resolution: "ent@npm:2.2.2" @@ -17819,6 +18127,13 @@ __metadata: languageName: node linkType: hard +"extendable-error@npm:^0.1.5": + version: 0.1.7 + resolution: "extendable-error@npm:0.1.7" + checksum: 10/80478be7429a1675d2085f701239796bab3230ed6f2fb1b138fbabec24bea6516b7c5ceb6e9c209efcc9c089948d93715703845653535f8e8a49655066a9255e + languageName: node + linkType: hard + "extglob@npm:^2.0.4": version: 2.0.4 resolution: "extglob@npm:2.0.4" @@ -17883,7 +18198,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:3.3.3, fast-glob@npm:^3.2.11, fast-glob@npm:^3.3.2, fast-glob@npm:^3.3.3": +"fast-glob@npm:3.3.3, fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.2, fast-glob@npm:^3.3.3": version: 3.3.3 resolution: "fast-glob@npm:3.3.3" dependencies: @@ -18430,6 +18745,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^7.0.1": + version: 7.0.1 + resolution: "fs-extra@npm:7.0.1" + dependencies: + graceful-fs: "npm:^4.1.2" + jsonfile: "npm:^4.0.0" + universalify: "npm:^0.1.0" + checksum: 10/3fc6e56ba2f07c00d452163f27f21a7076b72ef7da8a50fef004336d59ef4c34deda11d10ecd73fd8fbcf20e4f575f52857293090b3c9f8741d4e0598be30fea + languageName: node + linkType: hard + "fs-extra@npm:^8.1.0": version: 8.1.0 resolution: "fs-extra@npm:8.1.0" @@ -18951,6 +19277,20 @@ __metadata: languageName: node linkType: hard +"globby@npm:^11.0.0": + version: 11.1.0 + resolution: "globby@npm:11.1.0" + dependencies: + array-union: "npm:^2.1.0" + dir-glob: "npm:^3.0.1" + fast-glob: "npm:^3.2.9" + ignore: "npm:^5.2.0" + merge2: "npm:^1.4.1" + slash: "npm:^3.0.0" + checksum: 10/288e95e310227bbe037076ea81b7c2598ccbc3122d87abc6dab39e1eec309aa14f0e366a98cdc45237ffcfcbad3db597778c0068217dcb1950fef6249104e1b1 + languageName: node + linkType: hard + "globby@npm:^14.0.0": version: 14.1.0 resolution: "globby@npm:14.1.0" @@ -19023,7 +19363,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": +"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.5, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 @@ -19787,6 +20127,15 @@ __metadata: languageName: node linkType: hard +"human-id@npm:^4.1.1": + version: 4.2.0 + resolution: "human-id@npm:4.2.0" + bin: + human-id: dist/cli.js + checksum: 10/879f603f199642a67995b76a41510e142140f67852fe886c7b4cd1e90f82f4fb1452a6b2a8b75b1c313073e663d0bbcbc5dbb1dd17a648991d4d1a6da709f087 + languageName: node + linkType: hard + "human-signals@npm:^1.1.1": version: 1.1.1 resolution: "human-signals@npm:1.1.1" @@ -20626,6 +20975,15 @@ __metadata: languageName: node linkType: hard +"is-subdir@npm:^1.1.1": + version: 1.2.0 + resolution: "is-subdir@npm:1.2.0" + dependencies: + better-path-resolve: "npm:1.0.0" + checksum: 10/31029a383972bff4cc4f1bd1463fd04dde017e0a04ae3a6f6e08124a90c6c4656312d593101b0f38805fa3f3c8f6bc4583524bbf72c50784fa5ca0d3e5a76279 + languageName: node + linkType: hard + "is-symbol@npm:^1.0.4, is-symbol@npm:^1.1.1": version: 1.1.1 resolution: "is-symbol@npm:1.1.1" @@ -20702,7 +21060,7 @@ __metadata: languageName: node linkType: hard -"is-windows@npm:^1.0.1, is-windows@npm:^1.0.2": +"is-windows@npm:^1.0.0, is-windows@npm:^1.0.1, is-windows@npm:^1.0.2": version: 1.0.2 resolution: "is-windows@npm:1.0.2" checksum: 10/438b7e52656fe3b9b293b180defb4e448088e7023a523ec21a91a80b9ff8cdb3377ddb5b6e60f7c7de4fa8b63ab56e121b6705fe081b3cf1b828b0a380009ad7 @@ -21516,6 +21874,8 @@ __metadata: "joint@workspace:.": version: 0.0.0-use.local resolution: "joint@workspace:." + dependencies: + "@changesets/cli": "npm:2.31.1" languageName: unknown linkType: soft @@ -21617,6 +21977,18 @@ __metadata: languageName: node linkType: hard +"js-yaml@npm:^3.6.1": + version: 3.15.1 + resolution: "js-yaml@npm:3.15.1" + dependencies: + argparse: "npm:^1.0.7" + esprima: "npm:^4.0.0" + bin: + js-yaml: bin/js-yaml.js + checksum: 10/905842ce08c18b154ff2c187ff81bb41294cc5dda214331b5d9b8bcf395894e48b00fa37b9d4dd4c3ffec672b7496a94558e3876d3a8ad4d12b06248112e6d92 + languageName: node + linkType: hard + "js-yaml@npm:^4.1.0, js-yaml@npm:^4.1.1": version: 4.1.1 resolution: "js-yaml@npm:4.1.1" @@ -22504,6 +22876,13 @@ __metadata: languageName: node linkType: hard +"lodash.startcase@npm:^4.4.0": + version: 4.4.0 + resolution: "lodash.startcase@npm:4.4.0" + checksum: 10/3091048a54a2f92bcf2c6441d2bd9a706fb133d5f461ae7c310d6dca1530338a06c91e9e42a5b14b12e875ddae1814d448050dc02afe2cec09b3995d8e836837 + languageName: node + linkType: hard + "lodash.zip@npm:^4.2.0": version: 4.2.0 resolution: "lodash.zip@npm:4.2.0" @@ -22936,7 +23315,7 @@ __metadata: languageName: node linkType: hard -"merge2@npm:^1.3.0": +"merge2@npm:^1.3.0, merge2@npm:^1.4.1": version: 1.4.1 resolution: "merge2@npm:1.4.1" checksum: 10/7268db63ed5169466540b6fb947aec313200bcf6d40c5ab722c22e242f651994619bcd85601602972d3c85bd2cc45a358a4c61937e9f11a061919a1da569b0c2 @@ -24389,6 +24768,13 @@ __metadata: languageName: node linkType: hard +"outdent@npm:^0.5.0": + version: 0.5.0 + resolution: "outdent@npm:0.5.0" + checksum: 10/7d94a7d93883afa32c99d84f33248b221f4eeeedbb571921fe0e5cf0bee32e64746c587e9606d98ec22762870c782d21dd4bc3a0edf442d347cb54aa107b198d + languageName: node + linkType: hard + "outpipe@npm:^1.1.0": version: 1.1.1 resolution: "outpipe@npm:1.1.1" @@ -24695,6 +25081,15 @@ __metadata: languageName: node linkType: hard +"p-filter@npm:^2.1.0": + version: 2.1.0 + resolution: "p-filter@npm:2.1.0" + dependencies: + p-map: "npm:^2.0.0" + checksum: 10/76e552ca624ce2233448d68b19eec9de42b695208121998f7e011edce71d1079a83096ee6a2078fb2a59cfa8a5c999f046edf00ebf16a8e780022010b4693234 + languageName: node + linkType: hard + "p-finally@npm:^2.0.0": version: 2.0.1 resolution: "p-finally@npm:2.0.1" @@ -24765,6 +25160,13 @@ __metadata: languageName: node linkType: hard +"p-map@npm:^2.0.0": + version: 2.1.0 + resolution: "p-map@npm:2.1.0" + checksum: 10/9e3ad3c9f6d75a5b5661bcad78c91f3a63849189737cd75e4f1225bf9ac205194e5c44aac2ef6f09562b1facdb9bd1425584d7ac375bfaa17b3f1a142dab936d + languageName: node + linkType: hard + "p-map@npm:^7.0.2": version: 7.0.4 resolution: "p-map@npm:7.0.4" @@ -24833,6 +25235,15 @@ __metadata: languageName: node linkType: hard +"package-manager-detector@npm:^0.2.0": + version: 0.2.11 + resolution: "package-manager-detector@npm:0.2.11" + dependencies: + quansync: "npm:^0.2.7" + checksum: 10/2c1a8da0e5895f0be06a8e1f4b4336fb78a19167ca3932dbaeca7260f948e67cf53b32585a13f8108341e7a468b38b4f2a8afc7b11691cb2d856ecd759d570fb + languageName: node + linkType: hard + "pacote@npm:20.0.0": version: 20.0.0 resolution: "pacote@npm:20.0.0" @@ -25172,6 +25583,13 @@ __metadata: languageName: node linkType: hard +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 10/5b1e2daa247062061325b8fdbfd1fb56dde0a448fb1455453276ea18c60685bdad23a445dc148cf87bc216be1573357509b7d4060494a6fd768c7efad833ee45 + languageName: node + linkType: hard + "path-type@npm:^6.0.0": version: 6.0.0 resolution: "path-type@npm:6.0.0" @@ -25247,7 +25665,7 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:1.1.1, picocolors@npm:^1.0.0, picocolors@npm:^1.0.1, picocolors@npm:^1.1.1": +"picocolors@npm:1.1.1, picocolors@npm:^1.0.0, picocolors@npm:^1.0.1, picocolors@npm:^1.1.0, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" checksum: 10/e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 @@ -25657,6 +26075,15 @@ __metadata: languageName: node linkType: hard +"prettier@npm:^2.7.1": + version: 2.8.8 + resolution: "prettier@npm:2.8.8" + bin: + prettier: bin-prettier.js + checksum: 10/00cdb6ab0281f98306cd1847425c24cbaaa48a5ff03633945ab4c701901b8e96ad558eb0777364ffc312f437af9b5a07d0f45346266e8245beaf6247b9c62b24 + languageName: node + linkType: hard + "pretty-bytes@npm:^3.0.0": version: 3.0.1 resolution: "pretty-bytes@npm:3.0.1" @@ -25982,6 +26409,13 @@ __metadata: languageName: node linkType: hard +"quansync@npm:^0.2.7": + version: 0.2.11 + resolution: "quansync@npm:0.2.11" + checksum: 10/d4f0cc21a25052a8a6183f17752a6221829c4795b40641de67c06945b356841ff00296d3700d0332dfe8e86100fdcc02f4be7559f3f1774a753b05adb7800d01 + languageName: node + linkType: hard + "query-selector-shadow-dom@npm:^1.0.1": version: 1.0.1 resolution: "query-selector-shadow-dom@npm:1.0.1" @@ -26343,6 +26777,18 @@ __metadata: languageName: node linkType: hard +"read-yaml-file@npm:^1.1.0": + version: 1.1.0 + resolution: "read-yaml-file@npm:1.1.0" + dependencies: + graceful-fs: "npm:^4.1.5" + js-yaml: "npm:^3.6.1" + pify: "npm:^4.0.1" + strip-bom: "npm:^3.0.0" + checksum: 10/41ee5f075507ef0403328dd54e225a61c3149f915675ce7fd0fd791ddcce2e6c30a9fe0f76ffa7a465c1c157b9b4ad8ded1dcf47dc3b396103eeb013490bbc2e + languageName: node + linkType: hard + "readable-stream@npm:1 || 2, readable-stream@npm:^2.0.0, readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:^2.1.5, readable-stream@npm:^2.2.2, readable-stream@npm:^2.3.6, readable-stream@npm:^2.3.8, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" @@ -28756,6 +29202,16 @@ __metadata: languageName: node linkType: hard +"spawndamnit@npm:^3.0.1": + version: 3.0.1 + resolution: "spawndamnit@npm:3.0.1" + dependencies: + cross-spawn: "npm:^7.0.5" + signal-exit: "npm:^4.0.1" + checksum: 10/47d88a7f1e5691e13e435eddc3d34123c2f7746e2853e91bfac5ea7c6e3bb4b1d1995223b25f7a8745871510d92f63ecd3c9fa02aa2896ac0c79fb618eb08bbe + languageName: node + linkType: hard + "spdx-correct@npm:^3.0.0": version: 3.2.0 resolution: "spdx-correct@npm:3.2.0" @@ -29614,6 +30070,13 @@ __metadata: languageName: node linkType: hard +"term-size@npm:^2.1.0": + version: 2.2.1 + resolution: "term-size@npm:2.2.1" + checksum: 10/f96aca2d4139c91e3359f5949ffb86f0a58f8c254ab7fe4a64b65126974939c782db6aaa91bf51a56d0344e505e22f9a0186f2f689e23ac9382b54606603c537 + languageName: node + linkType: hard + "terser-webpack-plugin@npm:^5.3.11, terser-webpack-plugin@npm:^5.3.16": version: 5.3.16 resolution: "terser-webpack-plugin@npm:5.3.16" From 2278b61cfa0686cf1a2e4a29e21f7e973f6d891d Mon Sep 17 00:00:00 2001 From: zbynekstara Date: Fri, 7 Aug 2026 17:01:48 +0200 Subject: [PATCH 2/5] fixes --- .github/workflows/publish.yml | 24 +++++++++---------- .github/workflows/release.yml | 20 +++++++++------- CONTRIBUTING.md | 17 +++++++------ package.json | 6 ++++- packages/joint-decorators/package.json | 2 +- packages/joint-eslint-config/package.json | 2 +- .../joint-layout-directed-graph/package.json | 2 +- packages/joint-layout-msagl/package.json | 2 +- .../joint-shapes-general-tools/package.json | 1 + packages/joint-shapes-general/package.json | 1 + yarn.lock | 12 ++++++---- 11 files changed, 53 insertions(+), 36 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e21258cafc..c7c1875c4f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -7,9 +7,9 @@ # (detected by the tag not existing yet), # 3. force-updates `prod` to the merged release commit — always. # -# Channel: derived from the version suffix. A prerelease version (e.g. 4.4.0-beta.0, -# produced by `changeset pre enter beta`) publishes under that dist-tag as a GitHub -# prerelease; a plain version publishes under `latest`. +# Channel: chosen in Phase 1 (release.yml `dist_tag` input) and carried here via the +# committed RELEASE_CHANNEL file. `latest` publishes stable (npm `--tag latest`, GitHub +# Release `--latest`); `alpha`/`beta` publish under that dist-tag as a `--prerelease`. # # Required repository secrets: # NPM_TOKEN — npm automation token (org 2FA must allow automation tokens) @@ -73,17 +73,17 @@ jobs: CORE_VERSION=$(node -p "require('./packages/joint-core/package.json').version") echo "core_version=$CORE_VERSION" >> "$GITHUB_OUTPUT" - # Channel: prerelease suffix -> that dist-tag; otherwise `latest`. - if [[ "$CORE_VERSION" == *-* ]]; then - PRE_ID="${CORE_VERSION#*-}" # e.g. beta.0 - DIST_TAG="${PRE_ID%%.*}" # e.g. beta - IS_PRERELEASE=true + # Channel is chosen in Phase 1 (dist_tag input) and carried in RELEASE_CHANNEL. + # `latest` = stable; anything else = prerelease. Default to latest if absent. + DIST_TAG="$(cat RELEASE_CHANNEL 2>/dev/null || echo latest)" + DIST_TAG="${DIST_TAG//[[:space:]]/}" + [ -n "$DIST_TAG" ] || DIST_TAG=latest + echo "dist_tag=$DIST_TAG" >> "$GITHUB_OUTPUT" + if [ "$DIST_TAG" = "latest" ]; then + echo "is_prerelease=false" >> "$GITHUB_OUTPUT" else - DIST_TAG="latest" - IS_PRERELEASE=false + echo "is_prerelease=true" >> "$GITHUB_OUTPUT" fi - echo "dist_tag=$DIST_TAG" >> "$GITHUB_OUTPUT" - echo "is_prerelease=$IS_PRERELEASE" >> "$GITHUB_OUTPUT" # Core changed this release iff its tag does not exist yet. if git rev-parse -q --verify "refs/tags/v$CORE_VERSION" >/dev/null; then diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a61bd47f0e..a9e3527c20 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,15 +2,17 @@ # # The maintainer's "release button": dispatch this workflow. It renders the root # CHANGELOG + RELEASE_NOTES.md from the pending changesets, applies the version -# bumps (`changeset version`), commits the result to a `release/next` branch, and +# bumps (`changeset version`), commits the result to a `release/pending` branch, and # opens an auto-merging PR to `master`. Merging that PR triggers Phase 2 # (`publish.yml`), which publishes to npm, tags, cuts the GitHub Release, and # refreshes `prod` — all automatically. # -# Pre-releases (beta/next): run `yarn changeset pre enter ` on master -# first so the versions carry a `-.N` suffix; Phase 2 detects the suffix and -# publishes under that dist-tag as a prerelease. `yarn changeset pre exit` returns -# to stable `latest` releases. +# Release channel: pick the `dist_tag` input (latest | alpha | beta). It is the single +# source of truth — carried to Phase 2 via the committed RELEASE_CHANNEL file, which +# publishes under that npm dist-tag: `latest` as a stable GitHub Release, `alpha`/`beta` +# as a prerelease. For prerelease *version numbers* (x.y.z-beta.N), additionally run +# `yarn changeset pre enter ` on master before dispatching (and `pre exit` to +# leave pre mode) — that only affects the numbering, not the channel. # # Required repository secrets: # RELEASE_APP_ID — GitHub App id (App must have contents:write + pull-requests:write) @@ -22,11 +24,11 @@ on: workflow_dispatch: inputs: dist_tag: - description: 'Release channel. For next/beta, first run `yarn changeset pre enter ` on master.' + description: 'npm dist-tag / release channel. latest = stable Release; alpha/beta = prerelease.' type: choice options: - latest - - next + - alpha - beta default: latest dry_run: @@ -117,7 +119,9 @@ jobs: GH_TOKEN: ${{ steps.app-token.outputs.token }} RELEASE_TITLE: ${{ steps.info.outputs.release_title }} run: | - BRANCH="release/next" + BRANCH="release/pending" + # Carry the chosen release channel to Phase 2 (publish.yml). + echo "${{ inputs.dist_tag }}" > RELEASE_CHANNEL git checkout -B "$BRANCH" git add -A git commit -m "$RELEASE_TITLE" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 543a56e2a2..3fc19f0a43 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -115,15 +115,18 @@ automatically. See [`.changeset/README.md`](.changeset/README.md) for the full b Releases are automated in two phases; no manual npm/tag/GitHub-Release steps. -1. **Prepare** — dispatch the **Release (prepare)** workflow (`release.yml`). It renders the root - `CHANGELOG` + `RELEASE_NOTES.md` from the pending changesets, applies the bumps - (`changeset version`), and opens an auto-merging `release/next` PR to `master`. +1. **Prepare** — dispatch the **Release (prepare)** workflow (`release.yml`), choosing the `dist_tag` + (`latest` | `alpha` | `beta`). It renders the root `CHANGELOG` + `RELEASE_NOTES.md` from the pending + changesets, applies the bumps (`changeset version`), and opens an auto-merging `release/pending` PR to + `master`. 2. **Publish** — merging that PR triggers **Release (publish)** (`publish.yml`), which publishes the - changed packages to npm (via Yarn), tags `vX.Y.Z` + cuts one GitHub Release when `@joint/core` - changed, and force-updates `prod` to the merged commit. + changed packages to npm under the chosen `dist_tag` (via Yarn), tags `vX.Y.Z` + cuts one GitHub + Release when `@joint/core` changed, and force-updates `prod` to the merged commit. -For a `beta`/`next` release, run `yarn changeset pre enter ` on `master` before step 1 (and -`yarn changeset pre exit` to return to stable). +The `dist_tag` input is the release channel: `latest` publishes a stable Release; `alpha`/`beta` +publish a prerelease under that npm tag. To also get prerelease *version numbers* (`x.y.z-beta.N`), +run `yarn changeset pre enter ` on `master` before step 1 (and `yarn changeset pre exit` to +return to stable) — that affects the numbering only, not the channel. ## Code Style diff --git a/package.json b/package.json index 981d6b6f79..e8c508d05f 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,10 @@ }, "packageManager": "yarn@4.7.0", "devDependencies": { - "@changesets/cli": "2.31.1" + "@changesets/assemble-release-plan": "6.0.10", + "@changesets/cli": "2.31.1", + "@changesets/config": "3.1.4", + "@changesets/read": "0.6.7", + "@manypkg/get-packages": "1.1.3" } } diff --git a/packages/joint-decorators/package.json b/packages/joint-decorators/package.json index 80161793c7..60390d11ee 100644 --- a/packages/joint-decorators/package.json +++ b/packages/joint-decorators/package.json @@ -1,7 +1,7 @@ { "name": "@joint/decorators", "title": "JointJS Decorators", - "version": "4.3.1", + "version": "0.4.0", "description": "Decorators module for JointJS", "main": "build/index.js", "types": "build/index.d.ts", diff --git a/packages/joint-eslint-config/package.json b/packages/joint-eslint-config/package.json index 23210cca4c..8a33bd1bf8 100644 --- a/packages/joint-eslint-config/package.json +++ b/packages/joint-eslint-config/package.json @@ -1,7 +1,7 @@ { "name": "@joint/eslint-config", "title": "JointJS ESLintConfig", - "version": "4.3.1", + "version": "4.3.0", "description": "ESLintConfig module for JointJS", "sideEffects": false, "type": "module", diff --git a/packages/joint-layout-directed-graph/package.json b/packages/joint-layout-directed-graph/package.json index d3b33e0a5d..3bc215bfba 100644 --- a/packages/joint-layout-directed-graph/package.json +++ b/packages/joint-layout-directed-graph/package.json @@ -1,7 +1,7 @@ { "name": "@joint/layout-directed-graph", "title": "JointJS LayoutDirectedGraph", - "version": "4.3.1", + "version": "4.3.0", "description": "LayoutDirectedGraph module for JointJS", "main": "dist/DirectedGraph.js", "module": "./DirectedGraph.mjs", diff --git a/packages/joint-layout-msagl/package.json b/packages/joint-layout-msagl/package.json index d21840c074..cd0cb7ec31 100644 --- a/packages/joint-layout-msagl/package.json +++ b/packages/joint-layout-msagl/package.json @@ -1,7 +1,7 @@ { "name": "@joint/layout-msagl", "title": "JointJS LayoutMSAGL", - "version": "4.3.1", + "version": "4.3.0", "description": "LayoutMSAGL module for JointJS", "sideEffects": false, "main": "./dist/esm/index.mjs", diff --git a/packages/joint-shapes-general-tools/package.json b/packages/joint-shapes-general-tools/package.json index ecda29a270..28eaa95719 100644 --- a/packages/joint-shapes-general-tools/package.json +++ b/packages/joint-shapes-general-tools/package.json @@ -1,5 +1,6 @@ { "name": "@joint/shapes-general-tools", + "private": true, "title": "JointJS General Shapes Tools", "version": "4.3.1", "description": "General Shapes Tools module for JointJS", diff --git a/packages/joint-shapes-general/package.json b/packages/joint-shapes-general/package.json index f37c113126..1e75ec8355 100644 --- a/packages/joint-shapes-general/package.json +++ b/packages/joint-shapes-general/package.json @@ -1,5 +1,6 @@ { "name": "@joint/shapes-general", + "private": true, "title": "JointJS General Shapes", "version": "4.3.1", "description": "General Shapes module for JointJS", diff --git a/yarn.lock b/yarn.lock index 547ec1314a..ad8fd4619f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3163,7 +3163,7 @@ __metadata: languageName: node linkType: hard -"@changesets/assemble-release-plan@npm:^6.0.10": +"@changesets/assemble-release-plan@npm:6.0.10, @changesets/assemble-release-plan@npm:^6.0.10": version: 6.0.10 resolution: "@changesets/assemble-release-plan@npm:6.0.10" dependencies: @@ -3222,7 +3222,7 @@ __metadata: languageName: node linkType: hard -"@changesets/config@npm:^3.1.4": +"@changesets/config@npm:3.1.4, @changesets/config@npm:^3.1.4": version: 3.1.4 resolution: "@changesets/config@npm:3.1.4" dependencies: @@ -3324,7 +3324,7 @@ __metadata: languageName: node linkType: hard -"@changesets/read@npm:^0.6.7": +"@changesets/read@npm:0.6.7, @changesets/read@npm:^0.6.7": version: 0.6.7 resolution: "@changesets/read@npm:0.6.7" dependencies: @@ -7252,7 +7252,7 @@ __metadata: languageName: node linkType: hard -"@manypkg/get-packages@npm:^1.1.3": +"@manypkg/get-packages@npm:1.1.3, @manypkg/get-packages@npm:^1.1.3": version: 1.1.3 resolution: "@manypkg/get-packages@npm:1.1.3" dependencies: @@ -21875,7 +21875,11 @@ __metadata: version: 0.0.0-use.local resolution: "joint@workspace:." dependencies: + "@changesets/assemble-release-plan": "npm:6.0.10" "@changesets/cli": "npm:2.31.1" + "@changesets/config": "npm:3.1.4" + "@changesets/read": "npm:0.6.7" + "@manypkg/get-packages": "npm:1.1.3" languageName: unknown linkType: soft From 555a9987f3f8d1aced1a571cc92d89878876c21e Mon Sep 17 00:00:00 2001 From: zbynekstara Date: Fri, 7 Aug 2026 17:06:02 +0200 Subject: [PATCH 3/5] add empty changeset --- .changeset/light-horses-learn.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changeset/light-horses-learn.md diff --git a/.changeset/light-horses-learn.md b/.changeset/light-horses-learn.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/light-horses-learn.md @@ -0,0 +1,2 @@ +--- +--- From bd9195eb43663f5ec96e36af153a77fe9ebf0322 Mon Sep 17 00:00:00 2001 From: zbynekstara Date: Mon, 10 Aug 2026 12:37:40 +0200 Subject: [PATCH 4/5] fixes --- .changeset/README.md | 137 -------------------------- .changeset/config.json | 4 - .github/workflows/publish.yml | 28 ++++-- .github/workflows/release.yml | 38 +++---- .github/workflows/test-pr.yml | 9 +- CONTRIBUTING.md | 10 +- package.json | 8 +- scripts/changelog-from-changesets.mjs | 14 ++- scripts/release-info.mjs | 6 +- 9 files changed, 65 insertions(+), 189 deletions(-) delete mode 100644 .changeset/README.md diff --git a/.changeset/README.md b/.changeset/README.md deleted file mode 100644 index 551a9274c0..0000000000 --- a/.changeset/README.md +++ /dev/null @@ -1,137 +0,0 @@ -# Changesets - -This folder is managed by [Changesets](https://github.com/changesets/changesets). A -changeset is a small Markdown file that records, at PR time, **which packages to bump** -and **what to write in the changelog** for a change. - -## Adding a changeset - -Run: - -```bash -yarn changeset -``` - -Pick the package(s) your change affects and the bump type (`patch` / `minor` / `major`), -then write the changelog line(s). This creates a `.changeset/.md` file — commit -it with your PR. CI (`changeset status`) fails a PR that changes releasable code but adds no -changeset. Docs-, test-, and demo-only PRs don't need one (see below); if CI still asks for -one, add an empty changeset with `yarn changeset --empty`. - -### Which file changes require a changeset (`changedFilePatterns`) - -`config.json`'s `changedFilePatterns` decides whether a PR touched *releasable* code. The rule -is **allow everything, then subtract an ignore list** (`"**"` + `"!…"` negations) — a file in a -publishable package triggers a bump requirement **unless** it matches an ignore pattern. This -mirrors the previous Yarn `changesetIgnorePatterns` setup (where a file counted unless ignored), -so a brand-new kind of shipped file requires a changeset by default. JSON can't hold comments, -so the annotated intent lives here: - -A change to a publishable package requires a changeset when it touches **distributed source, -types, an entry file, or the manifest** — for reference, the paths that *do* trigger a bump: - -- `**/src/**` — source (all packages) -- `**/types/**` — `@joint/core` public type tree -- `**/*.d.ts` — hand-written type decls (e.g. `svg.d.ts`, `DirectedGraph.d.ts`) -- `**/package.json` — dependencies / exports / manifest -- `**/wrappers/**` — `@joint/core` build-time bundle wrappers -- `**/mocks/**` — `@joint/vitest-plugin-mock-svg` shipped mocks -- `**/index.js`, `**/index.mjs`, `**/joint.mjs`, `**/DirectedGraph.mjs` — package entry files -- `**/eslint.config.*.mjs` — `@joint/eslint-config` product configs (note: `eslint.config.mjs`, - the per-package lint config, is **ignored**) - -Everything else is **ignored** (never triggers a bump), grouped as: - -- **Tests / mocks / benchmarks / demos / stories** (not shipped): - `**/*.test.{js,jsx,ts,tsx,mjs}`, `**/test/**`, `**/__mocks__/**`, `**/bench/**`, `**/demo/**`, - `**/stories/**`, `**/.storybook/**` -- **Generated output & caches**: `**/dist/**`, `**/build/**`, `**/coverage/**`, `**/coverage.json`, - `**/.tscache/**` -- **Build & tooling scripts / config**: `**/scripts/**`, `**/grunt/**`, `**/Gruntfile.js`, - `**/Makefile`, `**/rollup.config.*`, `**/rollup.resources.mjs`, `**/vite.config.*`, - `**/vitest.workspace.*`, `**/jest.config.js`, `**/jest.react18.config.mjs`, `**/karma.conf.js`, - `**/tsconfig*.json`, `**/dts-generator.config.js`, `**/api-extractor.json`, `**/knip.json`, - `**/typedoc.*`, `**/.prettierrc*`, `**/.gitignore`, `**/eslint.config.mjs` -- **Documentation & metadata**: `**/*.md`, `**/LICENSE` - -> Test files are matched by **naming convention** (`*.test.{js,jsx,ts,tsx,mjs}`), which covers -> `@joint/react`'s `.ts`/`.tsx` tests under `src/**/__tests__/` without blanket-ignoring the -> directory (the lone non-`.test` file there, a `.md` benchmark, is already ignored by `**/*.md`). -> Because patterns are matched **per package** (relative to each package dir), the ignore -> negations override the broad `"**"` — e.g. a `demo/index.js` does not require a changeset even -> though it lives in a publishable package. - -## Body convention (important — our renderer depends on it) - -We render the root `CHANGELOG` ourselves (`"changelog": false` in `config.json`, -`scripts/changelog-from-changesets.mjs`), so the changeset **body** must follow our -semantic-commit convention — the same `type(scope): description` you already use in commits. -Write **one changelog row per line**: - -``` -()[!]: -``` - -- **`type`** — `feat` or `fix`. Any other line (`docs`, `chore`, `refactor`, plain prose, …) - is ignored by the renderer and never reaches the changelog. `feat` rows print as-is; - `fix` rows get the word `fix ` prepended, so phrase the text to read well after it - (e.g. `fix(util): to guard against prototype pollution` → `util - fix to guard …`). -- **`class`** — the scope, e.g. `dia.Paper`, `routers.rightAngle`, ``. It becomes - the row prefix (`dia.Paper - …`) and drives ordering (see `NAMESPACE_ORDER` / - `SUBCLASS_ORDER` in the renderer, which encode the "Section 2" class hierarchy). -- **`!`** before the `:` marks a **breaking** change; it renders as a leading `!` on the - class (`!dia.Graph - …`). - -Don't add PR or commit references by hand. The root `CHANGELOG` never carries links, and the -**GitHub Release notes (`RELEASE_NOTES.md`) link each row automatically** to the commit on -`master` that introduced its changeset (the PR's merge/squash commit). - -### Frontmatter vs. body - -- **Frontmatter** = the per-package version bumps (Changesets' native meaning). This is what - gets released. -- **Body** = the changelog rows. Rows are attributed to the package(s) named in the - frontmatter. **Keep a changeset focused on one documented package**; if a single PR needs - distinct rows for different packages, write separate changeset files. - -### Which packages appear in the CHANGELOG - -Only these four are rendered into the root `CHANGELOG` / `RELEASE_NOTES.md`, in this order: - -1. `@joint/react` -2. `@joint/core` -3. `@joint/layout-directed-graph` -4. `@joint/layout-msagl` - -Other publishable packages (`@joint/cli`, `@joint/decorators`, `@joint/shapes-general`, -`@joint/shapes-general-tools`, `@joint/vitest-plugin-mock-svg`, `@joint/eslint-config`) are -still **versioned and published** when a changeset names them (or when they're bumped as a -dependent), but they are intentionally **omitted from the human-facing CHANGELOG**. Add them -to `DOCUMENTED_PACKAGES` in the renderer if that ever needs to change. - -## Example - -```markdown ---- -"@joint/core": minor ---- -feat(dia.Paper): add `originX` and `originY` options to `getFitToContentArea()` -fix(routers.rightAngle): to allow zero-value margins and un-clamp `minPathMargin` -``` - -## Dependent bumps - -`config.json` uses `updateInternalDependents: "out-of-range"`, so a package is auto-bumped -only when a dependency's **new version falls outside its declared `workspace:` range**: - -- `@joint/react` depends on `@joint/core` via `workspace:^` → ignores core **patch and minor** - bumps (only a core **major** cascades to react). -- Everything else depends on core via `workspace:~` → ignores core **patch** bumps (a core - **minor** or **major** cascades). - -Cascaded packages are published but, having no authored rows, don't appear in the CHANGELOG. - -## Releasing - -Releases are driven by the `release.yml` / `publish.yml` GitHub Actions workflows, not by -`changeset publish`. See `CONTRIBUTING.md` → *Releasing*. diff --git a/.changeset/config.json b/.changeset/config.json index c70a4330cb..a750a2413a 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,5 +1,4 @@ { - "$schema": "https://unpkg.com/@changesets/config@3.1.4/schema.json", "changelog": false, "commit": false, "fixed": [], @@ -7,9 +6,6 @@ "access": "public", "baseBranch": "master", "updateInternalDependencies": "patch", - "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { - "updateInternalDependents": "out-of-range" - }, "privatePackages": { "version": false, "tag": false diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c7c1875c4f..54b672c5bf 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,7 +1,9 @@ # Phase 2 of the release pipeline: publish, tag, release, and refresh `prod`. # -# Fires when a release commit lands on `master` (the squash-merge of the PR opened -# by release.yml, whose title starts with "Release v"). It: +# Fires when the release PR opened by release.yml (source branch `release/pending`) is +# merged into `master`. It keys off the merged pull_request event + that branch name, +# NOT the commit message, so legacy/manual "Release v…" commits (or releases on other +# branches like `dev`) never trigger it. It: # 1. publishes every changed public package to npm with Yarn (NOT changeset publish), # 2. tags v + cuts one aggregated GitHub Release — only when core changed # (detected by the tag not existing yet), @@ -18,7 +20,9 @@ name: Release (publish) on: - push: + pull_request: + types: + - closed branches: - master @@ -28,8 +32,9 @@ concurrency: jobs: publish: - # Only run for release commits. - if: ${{ github.repository == 'clientIO/joint' && startsWith(github.event.head_commit.message, 'Release v') }} + # Only when our release PR (source branch release/pending, opened by release.yml) + # is actually merged into master (not just closed) + if: ${{ github.repository == 'clientIO/joint' && github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'release/pending' }} runs-on: ubuntu-latest permissions: contents: write @@ -95,14 +100,14 @@ jobs: # Publish with Yarn (resolves workspace: ranges; skips prepublishOnly guards; # topological order; --no-private excludes examples; --tolerate-republish skips # already-published versions). - - name: Publish to npm + - name: Publish to NPM via Yarn env: YARN_NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | - yarn release:publish --tag "${{ steps.facts.outputs.dist_tag }}" + yarn release-publish --tag "${{ steps.facts.outputs.dist_tag }}" - # Tag + one aggregated GitHub Release — only when core changed. + # Create tag + GitHub Release - only when @joint/core changed - name: Tag & GitHub Release if: ${{ steps.facts.outputs.core_changed == 'true' }} env: @@ -119,7 +124,10 @@ jobs: gh release create "v$CORE_VERSION" --title "Release v$CORE_VERSION" "${NOTES_ARG[@]}" --latest fi - # Refresh `prod` to the merged release commit — ALWAYS (even when core did - # not change) so JointJS+ trials/builds always track the latest master. + # Refresh `prod` branch to the merged release commit - name: Refresh prod branch run: git push --force origin master:prod + + # Delete `release/pending` branch + - name: Delete release branch + run: git push origin --delete release/pending || echo "release/pending already deleted" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a9e3527c20..9f9401723e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -92,22 +92,16 @@ jobs: fi echo "Found $count changeset(s)." - # Compute the commit/PR title from the release plan (before changesets are consumed). - - name: Compute release info - id: info - run: node scripts/release-info.mjs - - name: Configure git run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - # Render CHANGELOG + RELEASE_NOTES.md (while changesets exist), then apply - # bumps + workspace-range updates + dependent bumps and delete the changesets. + # Writes info values to $GITHUB_OUTPUT: + # steps.info.outputs.release_title / core_version / core_changed / released. - name: Version packages - run: | - yarn release:version - yarn install --mode update-lockfile + id: info + run: yarn release-version # Make sure the bumped repo still builds. - name: Sanity build @@ -126,16 +120,24 @@ jobs: git add -A git commit -m "$RELEASE_TITLE" git push --force origin "$BRANCH" - # Open (or update) the PR and enable squash auto-merge. - if gh pr view "$BRANCH" --json number >/dev/null 2>&1; then - echo "PR already exists for $BRANCH; branch updated." + # Reuse an OPEN PR for this branch if one exists (refreshing its title/body, + # since the force-push just replaced the contents); otherwise open a new one. + # Restricting to `--state open` avoids matching a previous, already-merged + # release PR that still carries this head-ref name in history. + PR_NUMBER="$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number // empty')" + if [ -n "$PR_NUMBER" ]; then + echo "Reusing open PR #$PR_NUMBER for $BRANCH; refreshing title/body." + gh pr edit "$PR_NUMBER" --title "$RELEASE_TITLE" --body-file RELEASE_NOTES.md else - gh pr create --base master --head "$BRANCH" \ - --title "$RELEASE_TITLE" \ - --body-file RELEASE_NOTES.md \ - --label release + PR_NUMBER="$(basename "$(gh pr create --base master --head "$BRANCH" \ + --title "$RELEASE_TITLE" --body-file RELEASE_NOTES.md)")" + echo "Opened PR #$PR_NUMBER for $BRANCH." fi - gh pr merge "$BRANCH" --auto --squash + # Cosmetic label for humans only (NOT the publish trigger — publish.yml keys + # off the release/pending branch). Best-effort: skip if the label is absent. + gh pr edit "$PR_NUMBER" --add-label release 2>/dev/null \ + || echo "note: 'release' label not applied (optional; create it in repo labels if wanted)" + gh pr merge "$PR_NUMBER" --auto --squash - name: Dry-run summary if: ${{ inputs.dry_run == true }} diff --git a/.github/workflows/test-pr.yml b/.github/workflows/test-pr.yml index 2172754b3b..e799403102 100644 --- a/.github/workflows/test-pr.yml +++ b/.github/workflows/test-pr.yml @@ -62,16 +62,15 @@ jobs: id: install-dependencies run: yarn install --immutable - # Enforce that a PR touching releasable code (see `changedFilePatterns` - # in .changeset/config.json) also adds a changeset. Docs/test/demo-only - # PRs are exempt automatically. The release PR (branch `release/*`) is - # exempt because it *consumes* changesets rather than adding them. + # A PR touching releasable must add a changeset + # (See `.changeset/config.json`) + # The release PR (branch `release/*`) is exempt - it consumes changesets - name: Enforce changeset id: changeset-status if: ${{ github.event_name == 'pull_request' && !startsWith(github.head_ref, 'release/') }} run: | git fetch origin master:refs/remotes/origin/master || true - yarn changeset status --since=origin/master + yarn release-status --since=origin/master # Build the joint project - name: Prepare joint diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3fc19f0a43..0d050a48c7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -102,14 +102,18 @@ Pick the affected package(s) and bump type, then write the changelog line(s) in "@joint/core": minor --- feat(dia.Paper): add `originX` and `originY` options to `getFitToContentArea()` -fix(routers.rightAngle): to allow zero-value margins and un-clamp `minPathMargin` +fix(routers.rightAngle): allow zero-value margins and un-clamp `minPathMargin` ``` Commit the generated `.changeset/*.md` with your PR. CI (`changeset status`) fails a PR that changes releasable code without a changeset; docs-, test-, and demo-only PRs are exempt. Don't add PR/commit links by hand — the GitHub Release notes link each row to its `master` commit -automatically. See [`.changeset/README.md`](.changeset/README.md) for the full body convention -(feat/fix filtering, `!` for breaking changes, which packages appear in the CHANGELOG). +automatically. `feat` rows render verbatim; `fix` rows are prefixed with `fix to ` (so +`fix(routers.rightAngle): allow zero-value margins` becomes `routers.rightAngle - fix to allow +zero-value margins`). Non-`feat`/`fix` lines are ignored by the changelog renderer, and `!` +before the `:` marks a breaking change. Only `@joint/react`, `@joint/core`, `@joint/layout-directed-graph`, +and `@joint/layout-msagl` appear in the root `CHANGELOG`; other packages are still versioned and +published, just not listed there. ### Releasing (maintainers) diff --git a/package.json b/package.json index e8c508d05f..092f5f2f56 100644 --- a/package.json +++ b/package.json @@ -25,11 +25,9 @@ "lint": "yarn workspaces foreach --all -tvv run lint", "lint-fix": "yarn workspaces foreach --all -tvv run lint-fix", "pack-all": "yarn workspaces foreach --all -tvv --include \"@joint/core\" --include \"@joint/layout-directed-graph\" --include \"@joint/layout-msagl\" pack --out %s-%v.tgz", - "changeset": "changeset", - "release:status": "changeset status --since=origin/master", - "release:changelog": "node scripts/changelog-from-changesets.mjs", - "release:version": "yarn release:changelog && changeset version", - "release:publish": "yarn workspaces foreach --all --no-private -t npm publish --access public --tolerate-republish" + "release-status": "changeset status", + "release-version": "node scripts/release-info.mjs && node scripts/changelog-from-changesets.mjs && changeset version", + "release-publish": "yarn workspaces foreach --all --no-private -t npm publish --access public --tolerate-republish" }, "workspaces": [ "./packages/*", diff --git a/scripts/changelog-from-changesets.mjs b/scripts/changelog-from-changesets.mjs index c58b1dfa79..32013a923c 100644 --- a/scripts/changelog-from-changesets.mjs +++ b/scripts/changelog-from-changesets.mjs @@ -11,7 +11,7 @@ // Run this BEFORE `changeset version` (the changesets must still exist). It reads // the changesets, computes the resulting versions via Changesets' own release-plan // API (so the numbers match exactly what `changeset version` will write, including -// the `updateInternalDependents: "out-of-range"` cascade), and prepends the entry +// the out-of-range dependent-bump cascade — Changesets' default), and prepends the entry // to `CHANGELOG` / writes `RELEASE_NOTES.md`. It does NOT bump anything or delete // changesets — that is `changeset version`'s job, run right after. // @@ -19,13 +19,15 @@ // node scripts/changelog-from-changesets.mjs [--dry-run] [--date DD-MM-YYYY] // [--changelog CHANGELOG] [--release-notes RELEASE_NOTES.md] [--repo owner/name] // -// Changeset body convention (see .changeset/README.md), one row per line: +// Changeset body convention (see CONTRIBUTING.md -> Changesets), one row per line: // ()[!]: // e.g. // feat(dia.Paper): add `originX` and `originY` options to `getFitToContentArea()` -// fix(routers.rightAngle): to allow zero-value margins and un-clamp `minPathMargin` +// fix(routers.rightAngle): allow zero-value margins and un-clamp `minPathMargin` // feat(dia.Graph)!: drop the deprecated `cellNamespace` setter // feat: packaging changes <- no class -> sorts first +// `feat` rows render verbatim; `fix` rows are prefixed with `fix to ` (so the row +// above renders as `routers.rightAngle - fix to allow zero-value margins …`). // Non feat/fix lines are ignored (they never reach the CHANGELOG). // // The GitHub Release notes (RELEASE_NOTES.md) automatically link each row to the @@ -220,9 +222,11 @@ function compareRows(a, b) { } // Render one CHANGELOG row body (without the leading " * "). +// `fix` rows are prefixed with `fix to ` so the author writes the natural commit +// form (`fix(scope): allow …`) and it reads `scope - fix to allow …`. function renderRowText(row) { const cls = row.className ? `${row.breaking ? '!' : ''}${row.className} - ` : ''; - const verb = row.type === 'fix' ? 'fix ' : ''; + const verb = row.type === 'fix' ? 'fix to ' : ''; return `${cls}${verb}${row.text}`; } @@ -316,7 +320,7 @@ async function main() { const rows = rowsByPackage.get(pkg).sort(compareRows); const lines = rows.map((r) => { const cls = r.className ? `**${r.breaking ? '!' : ''}${r.className}** — ` : ''; - const verb = r.type === 'fix' ? 'fix ' : ''; + const verb = r.type === 'fix' ? 'fix to ' : ''; const link = r.commit ? ` ([\`${r.commit.slice(0, 7)}\`](https://github.com/${args.repo}/commit/${r.commit}))` : ''; diff --git a/scripts/release-info.mjs b/scripts/release-info.mjs index 7d26ea0649..a956160cc5 100644 --- a/scripts/release-info.mjs +++ b/scripts/release-info.mjs @@ -1,6 +1,8 @@ #!/usr/bin/env node -// Print release metadata derived from the pending `.changeset/*.md` files, for -// the release workflow to build the commit/PR title. Run BEFORE `changeset version`. +// Print release metadata derived from the pending `.changeset/*.md` files, for the +// release workflow to build the commit/PR title. Invoked first by the `release-version` +// script (`node scripts/release-info.mjs && node scripts/changelog-from-changesets.mjs && changeset version`), +// so it always runs BEFORE `changeset version` consumes the changesets. // // Emits `KEY=VALUE` lines to stdout, and also appends them to $GITHUB_OUTPUT when set: // core_version= From 950f17f0cdcc6d45843b1e84b7537b2bdbc08113 Mon Sep 17 00:00:00 2001 From: zbynekstara Date: Mon, 10 Aug 2026 13:09:30 +0200 Subject: [PATCH 5/5] fix --- .github/workflows/test-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-pr.yml b/.github/workflows/test-pr.yml index e799403102..c0dd23549c 100644 --- a/.github/workflows/test-pr.yml +++ b/.github/workflows/test-pr.yml @@ -62,7 +62,7 @@ jobs: id: install-dependencies run: yarn install --immutable - # A PR touching releasable must add a changeset + # A PR touching releasable files must add a changeset # (See `.changeset/config.json`) # The release PR (branch `release/*`) is exempt - it consumes changesets - name: Enforce changeset