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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Release notes

See [CONTRIBUTING.md](../CONTRIBUTING.md#releases) for contributing release
notes and preparing a release.
11 changes: 11 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/config@4.0.1/schema.json",
"baseBranch": "main",
"access": "public",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"updateInternalDependencies": "patch",
"ignore": []
}
8 changes: 4 additions & 4 deletions .github/workflows/lint-workflows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: raven-actions/actionlint@205b530c5d9fa8f44ae9ed59f341a0db994aa6f8 # v2.1.2
- uses: kjanat/actionlint@08bb2c4f0d039744455b87aef1c647fb8b66d37b # v1.17.0

zizmor:
name: zizmor
Expand All @@ -37,7 +37,7 @@ jobs:
permissions:
security-events: write
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
- uses: zizmorcore/zizmor-action@53290badfa369cb0bf7ad16342cb62c2fa47ac7b # v0.8.0
222 changes: 222 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
name: Release

on:
push:
branches: [main]
workflow_dispatch:
inputs:
commit:
description:
"Optional 40-character version-bump commit SHA reachable from the
selected ref"
required: false
type: string

concurrency:
group: ${{ github.workflow }}
queue: max
cancel-in-progress: false

permissions: {}

env:
NODE_VERSION: "24.22.1"

jobs:
plan:
if: github.repository_owner == 'convex'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
outputs:
sha: ${{ steps.select.outputs.sha }}
name: ${{ steps.select.outputs.name }}
version: ${{ steps.select.outputs.version }}
channel: ${{ steps.select.outputs.channel }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: ${{ env.NODE_VERSION }}
package-manager-cache: false
- id: select
name: Select the latest version-bump commit
env:
RELEASE_COMMIT: ${{ inputs.commit }}
run: |
if [[ "$GITHUB_EVENT_NAME" == workflow_dispatch ]]; then
source="${RELEASE_COMMIT:-$GITHUB_SHA}"
[[ "$source" =~ ^[0-9a-f]{40}$ ]] || { echo 'Expected a 40-character commit SHA' >&2; exit 1; }
git merge-base --is-ancestor "$source" "$GITHUB_SHA"
else
before=$(jq -r .before "$GITHUB_EVENT_PATH")
source=''
candidates=$(git rev-list --first-parent "$before..$GITHUB_SHA" -- package.json)
while read -r candidate && [[ -n "$candidate" ]]; do
current=$(git show "$candidate:package.json" | jq -r .version)
previous=$(git show "$candidate^1:package.json" | jq -r .version)
if [[ "$current" != "$previous" ]]; then source="$candidate"; break; fi
done <<< "$candidates"
if [[ -z "$source" ]]; then echo 'No version bump'; exit 0; fi
fi
name=$(git show "$source:package.json" | jq -r .name)
VERSION=$(git show "$source:package.json" | jq -r .version)
PREVIOUS_VERSION=$(git show "$source^1:package.json" | jq -r .version)
export VERSION PREVIOUS_VERSION
node --input-type=module <<'NODE'
import { execFileSync } from 'node:child_process';
import { appendFileSync } from 'node:fs';
import { createRequire } from 'node:module';
// Use the standard SemVer library bundled with the pinned npm.
const npmRoot = execFileSync('npm', ['root', '-g'], { encoding: 'utf8' }).trim();
const semver = createRequire(`${npmRoot}/npm/package.json`)('semver');
const { VERSION: version, PREVIOUS_VERSION: previous, GITHUB_REF: ref } = process.env;
if (semver.valid(version) !== version || !semver.gt(version, previous)) throw new Error('Selected commit must increase the package version');
const pre = semver.prerelease(version);
const channel = pre ? String(pre[0]) : 'latest';
if (pre && (!/^[a-z][a-z0-9-]*$/.test(channel) || channel === 'latest' || semver.validRange(channel))) throw new Error('Invalid prerelease channel');
if (!pre && ref !== 'refs/heads/main') throw new Error('Stable releases require main');
appendFileSync(process.env.GITHUB_OUTPUT, `channel=${channel}\n`);
NODE
tag="v$VERSION"
if git show-ref --verify --quiet "refs/tags/$tag"; then
[[ "$(git rev-parse "$tag^{commit}")" == "$source" ]] || { echo "$tag points elsewhere" >&2; exit 1; }
if [[ "$GITHUB_EVENT_NAME" != workflow_dispatch ]]; then
echo "$tag was already staged; skipping automatic release"
exit 0
fi
fi
{
echo "sha=$source"
echo "name=$name"
echo "version=$VERSION"
} >> "$GITHUB_OUTPUT"

build:
needs: plan
if: needs.plan.outputs.sha != ''
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
outputs:
artifact_id: ${{ steps.upload.outputs.artifact-id }}
sha256: ${{ steps.digest.outputs.sha256 }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ needs.plan.outputs.sha }}
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: ${{ env.NODE_VERSION }}
package-manager-cache: false
- run: npm ci
- name: Validate release notes
run: |
# Compare against this checkout while reading all notes, including prerelease state.
git update-ref refs/heads/main HEAD
npx changeset status --output "$RUNNER_TEMP/changesets.json"
jq -e '.changesets | length == 0' "$RUNNER_TEMP/changesets.json"
version=$(jq -r .version package.json)
awk -v version="$version" '/^## / { active=($0 == "## " version); next } active && !/^#/ && /[[:alnum:]]/ { found=1 } END { exit !found }' CHANGELOG.md
- run: npm run build
- run: npm run check
- name: Run E2E tests and verify generated code
env:
CONVEX_AGENT_MODE: anonymous
CONVEX_DISABLE_TELEMETRY: "1"
run: |
npm run test:e2e
git diff --exit-code -- src/component/_generated example/convex/_generated
test -z "$(git ls-files --others --exclude-standard src/component/_generated example/convex/_generated)"
- name: Pack the tested package
run: |
mkdir -p "$RUNNER_TEMP/npm-release"
npm pack --ignore-scripts --pack-destination "$RUNNER_TEMP/npm-release"
- id: digest
run:
echo "sha256=$(sha256sum "$RUNNER_TEMP"/npm-release/*.tgz | cut -d ' '
-f 1)" >> "$GITHUB_OUTPUT"
- id: upload
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: npm-release-${{ needs.plan.outputs.sha }}
path: ${{ runner.temp }}/npm-release/*.tgz
if-no-files-found: error

stage:
needs: [plan, build]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
id-token: write
steps:
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: ${{ env.NODE_VERSION }}
package-manager-cache: false
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
with:
artifact-ids: ${{ needs.build.outputs.artifact_id }}
path: ${{ runner.temp }}/npm-release
merge-multiple: true
- name: Stage the tested artifact
env:
SOURCE_SHA: ${{ needs.plan.outputs.sha }}
PACKAGE_NAME: ${{ needs.plan.outputs.name }}
VERSION: ${{ needs.plan.outputs.version }}
CHANNEL: ${{ needs.plan.outputs.channel }}
EXPECTED_SHA256: ${{ needs.build.outputs.sha256 }}
run: |
set -o pipefail
set -- "$RUNNER_TEMP"/npm-release/*.tgz
test "$#" -eq 1
TARBALL="$1"
echo "$EXPECTED_SHA256 $TARBALL" | sha256sum -c -
{
echo "$PACKAGE_NAME@$VERSION — source $SOURCE_SHA — npm channel $CHANNEL"
echo "Review on npm's Staged Packages page, then approve with 2FA or npm stage approve <stage-id>."
} >> "$GITHUB_STEP_SUMMARY"
# npm reads GITHUB_SHA for provenance; checkout alone does not change it.
GITHUB_SHA="$SOURCE_SHA" npm stage publish "$TARBALL" \
--ignore-scripts --provenance --access public --tag "$CHANNEL" \
2>&1 | tee "$RUNNER_TEMP/npm-stage.log"
- name: Preserve staging output and stage ID
if: always()
run: |
if test -f "$RUNNER_TEMP/npm-stage.log"; then
cat "$RUNNER_TEMP/npm-stage.log" >> "$GITHUB_STEP_SUMMARY"
fi

tag:
needs: [plan, stage]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ needs.plan.outputs.sha }}
fetch-depth: 0
persist-credentials: false
- name: Tag the staged source
env:
GH_TOKEN: ${{ github.token }}
SOURCE_SHA: ${{ needs.plan.outputs.sha }}
VERSION: ${{ needs.plan.outputs.version }}
run: |
tag="v$VERSION"
if git show-ref --verify --quiet "refs/tags/$tag"; then
test "$(git rev-parse "$tag^{commit}")" = "$SOURCE_SHA"
else
git tag "$tag" "$SOURCE_SHA"
gh auth setup-git
git push origin "refs/tags/$tag"
fi
29 changes: 18 additions & 11 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ concurrency:
cancel-in-progress: true

on:
workflow_dispatch:
push:
branches: [main]
pull_request:
Expand All @@ -16,29 +17,35 @@ jobs:
name: Test and lint
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false

- name: Node setup
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
cache-dependency-path: package.json
node-version: "20.x"
node-version: "24.22.1"
cache: "npm"

- name: Install and build
run: |
npm i
npm ci
npm run build
- name: Publish package for testing branch
run:
npx pkg-pr-new publish || echo "Have you set up pkg-pr-new for this
repo?"
- name: Test
run: npm run check
- name: Run E2E smoke test and verify generated code
env:
CONVEX_AGENT_MODE: anonymous
CONVEX_DISABLE_TELEMETRY: "1"
run: |
npm run test
npm run typecheck
npm run lint
npm run test:e2e
git diff --exit-code -- src/component/_generated example/convex/_generated
test -z "$(git ls-files --others --exclude-standard src/component/_generated example/convex/_generated)"
- name: Publish package for testing branch
run: npx pkg-pr-new publish || echo "pkg-pr-new failed"
59 changes: 59 additions & 0 deletions .github/workflows/version.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Update release PR

concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false

on:
push:
branches: [main]
workflow_dispatch:

permissions: {}

jobs:
version:
name: Update release PR
if: github.repository_owner == 'convex' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.sha }}
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: "24.22.1"
package-manager-cache: false
- run: npm ci --ignore-scripts
- name: Check whether a version PR is needed
id: mode
uses: changesets/action/select-mode@ae32849d5ba541f9ae29e40e22a623bc13562f51 # v2.1.2
- name: Check main is still at this run's commit
if: steps.mode.outputs.mode == 'version'
id: freshness
env:
GH_TOKEN: ${{ github.token }}
run: |
main_sha="$(gh api "repos/$GITHUB_REPOSITORY/git/ref/heads/main" --jq '.object.sha')"
if [[ "$main_sha" == "$GITHUB_SHA" ]]; then
echo "current=true" >> "$GITHUB_OUTPUT"
else
echo "current=false" >> "$GITHUB_OUTPUT"
echo "Skipping release PR update: main has moved beyond this run's commit."
fi
- name: Create or update the release PR
if:
steps.mode.outputs.mode == 'version' &&
steps.freshness.outputs.current == 'true'
uses: changesets/action/version@ae32849d5ba541f9ae29e40e22a623bc13562f51 # v2.1.2
with:
github-token: ${{ github.token }}
pr-title: "Prepare next release"
commit-message: "Version packages"
pr-base-branch: main
Loading
Loading