snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
This commit is contained in:
73
.github/workflows/bump-versions.yml
vendored
Normal file
73
.github/workflows/bump-versions.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
name: Bump versions to latest tldraw version
|
||||
# This bumps all package versions to match the latest tldraw version from npm
|
||||
|
||||
# Prevent more than one version bumping job from running at the same time
|
||||
concurrency:
|
||||
group: version-bump
|
||||
|
||||
# Only run on main branch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
bump_versions:
|
||||
name: 'Bump package versions to latest tldraw version'
|
||||
environment: npm deploy
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-16-cores-arm-open
|
||||
|
||||
steps:
|
||||
- name: Generate GH token
|
||||
id: generate_token
|
||||
uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92
|
||||
with:
|
||||
app_id: ${{ secrets.HUPPY_APP_ID }}
|
||||
private_key: ${{ secrets.HUPPY_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Check out code
|
||||
id: checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
|
||||
- name: Configure git credentials
|
||||
run: git config --global url."https://x-access-token:${{ steps.generate_token.outputs.token }}@github.com/".insteadOf "https://github.com/"
|
||||
|
||||
- name: Check branch
|
||||
run: |
|
||||
if [[ "$(git rev-parse --abbrev-ref HEAD)" != "main" ]]; then
|
||||
echo "ERROR This workflow only runs on the main branch."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Run our setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Bump versions
|
||||
run: |
|
||||
git config --global user.name 'huppy-bot[bot]'
|
||||
git config --global user.email '128400622+huppy-bot[bot]@users.noreply.github.com'
|
||||
yarn tsx ./internal/scripts/bump-versions.ts
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
|
||||
# Preserve the setup action for post-job cleanup when checkout already succeeded.
|
||||
- name: Check out Discord notify action
|
||||
if: failure() && steps.checkout.outcome != 'success'
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
sparse-checkout: .github/actions/discord-fail-notify
|
||||
|
||||
- name: Notify Discord on failure
|
||||
if: failure()
|
||||
uses: ./.github/actions/discord-fail-notify
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_DEPLOY_WEBHOOK_URL }}
|
||||
112
.github/workflows/checks.yml
vendored
Normal file
112
.github/workflows/checks.yml
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
name: Checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
CI: 1
|
||||
PRINT_GITHUB_ANNOTATIONS: 1
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
# update-pr-template reads the repo's label list (octo.issues.listLabelsForRepo).
|
||||
issues: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: 'Tests & checks'
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest-16-cores-arm-open # TODO: this should probably run on multiple OSes
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
|
||||
- name: Check for duplicate dependencies
|
||||
run: yarn dedupe --check
|
||||
|
||||
- name: Check packages
|
||||
run: yarn check-packages
|
||||
|
||||
- name: Check circular dependencies
|
||||
run: yarn check-circular-deps
|
||||
|
||||
- name: Restore build tool caches
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
.lazy
|
||||
**/.lazy
|
||||
**/.tsbuild
|
||||
**/*.tsbuildinfo
|
||||
key: test-tools-${{ runner.os }}-${{ hashFiles('yarn.lock', '.oxlintrc.json', '.oxfmtrc.json', 'lazy.config.ts') }}
|
||||
|
||||
- name: Typecheck
|
||||
run: yarn build-types
|
||||
|
||||
- name: Check bundle sizes
|
||||
run: yarn lazy check-bundle-size
|
||||
|
||||
- name: Check PR template
|
||||
run: yarn update-pr-template --check
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
- name: Check API declarations and docs work as intended
|
||||
run: yarn api-check
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
|
||||
- name: Check dotcom localizations
|
||||
run: |
|
||||
yarn build-i18n
|
||||
git diff --exit-code --quiet ./apps/dotcom/client/public/tla/locales/ ./apps/dotcom/client/public/tla/locales-compiled/ || (echo "Please run yarn build-i18n and commit the changes" && exit 1)
|
||||
|
||||
# vitest's fsModuleCache (~54MB gzipped). Entries are content-hashed, so a stale
|
||||
# restore is safe - unmatched modules are just re-transformed. Keyed on yarn.lock so
|
||||
# one entry is reused across commits instead of saving a new copy on every run.
|
||||
- name: Restore vitest module cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: node_modules/.vitest-cache
|
||||
key: vitest-cache-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: vitest-cache-${{ runner.os }}-
|
||||
|
||||
- name: Test
|
||||
run: yarn test-ci
|
||||
|
||||
build:
|
||||
name: 'Build all projects'
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest-16-cores-arm-open
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
|
||||
- name: Restore build tool caches
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
.lazy
|
||||
**/.lazy
|
||||
**/.tsbuild
|
||||
**/*.tsbuildinfo
|
||||
key: build-tools-${{ runner.os }}-${{ hashFiles('yarn.lock', 'lazy.config.ts') }}
|
||||
|
||||
- name: Build packages
|
||||
# the sed pipe makes sure that github annotations come through without
|
||||
# turbo's prefix
|
||||
run: "yarn build-package | sed -E 's/^.*? ::/::/'"
|
||||
52
.github/workflows/claude.yml
vendored
Normal file
52
.github/workflows/claude.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: Claude Code
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
issues:
|
||||
types: [opened, assigned]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
claude:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
||||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
id-token: write
|
||||
actions: read # Required for Claude to read CI results on PRs
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
|
||||
# This is an optional setting that allows Claude to read CI results on PRs
|
||||
additional_permissions: |
|
||||
actions: read
|
||||
|
||||
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
|
||||
# prompt: 'Update the pull request description to include a summary of changes.'
|
||||
|
||||
# Optional: Add claude_args to customize behavior and configuration
|
||||
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
||||
# or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options
|
||||
# claude_args: '--model claude-opus-4-1-20250805 --allowed-tools Bash(gh pr:*)'
|
||||
37
.github/workflows/close-stale-issues.yml
vendored
Normal file
37
.github/workflows/close-stale-issues.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
name: Close inactive issues
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 1 * *'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
with:
|
||||
days-before-issue-stale: 150
|
||||
days-before-issue-close: 30
|
||||
stale-issue-label: 'stale'
|
||||
stale-issue-message: >
|
||||
This issue has been marked as stale because it has been open for 150 days with no activity.
|
||||
If there's no further discussion, it'll be closed in 30 days time.
|
||||
If you would like to ensure it stays open, you can comment on the issue and revive the discussion.
|
||||
Or, you can add the 'keep' label to prevent it from being closed.
|
||||
close-issue-message: 'This issue was closed because it has been inactive for 30 days since being marked as stale.'
|
||||
close-issue-reason: 'not_planned'
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
operations-per-run: 1000
|
||||
exempt-issue-labels: keep
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
41
.github/workflows/dependabot-dedupe.yml
vendored
Normal file
41
.github/workflows/dependabot-dedupe.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Dedupe Dependabot PRs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['dependabot/**']
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
repository-projects: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
dedupe:
|
||||
name: Dedupe Dependabot PRs
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config user.name 'github-actions[bot]'
|
||||
git config user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
|
||||
- name: Dedupe dependencies
|
||||
run: yarn dedupe
|
||||
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git add .
|
||||
git commit -m 'Dedupe' || true
|
||||
|
||||
- name: Push changes
|
||||
run: git push
|
||||
55
.github/workflows/deploy-analytics.yml
vendored
Normal file
55
.github/workflows/deploy-analytics.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
name: Deploy analytics worker
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'apps/analytics-worker/**'
|
||||
- 'internal/scripts/deploy-analytics.ts'
|
||||
- 'internal/scripts/lib/deploy.ts'
|
||||
- '.github/workflows/deploy-analytics.yml'
|
||||
- '.github/actions/setup/**'
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- production
|
||||
|
||||
env:
|
||||
CI: 1
|
||||
PRINT_GITHUB_ANNOTATIONS: 1
|
||||
TLDRAW_ENV: ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }}
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ github.ref == 'refs/heads/production' && 'deploy-production' || 'deploy-staging' }}
|
||||
concurrency: analytics-worker-${{ github.ref == 'refs/heads/production' && 'deploy-production' || github.ref }}
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
|
||||
- name: Build types
|
||||
run: yarn build-types
|
||||
|
||||
- name: Deploy
|
||||
run: yarn tsx internal/scripts/deploy-analytics.ts
|
||||
env:
|
||||
RELEASE_COMMIT_HASH: ${{ github.sha }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
DISCORD_DEPLOY_WEBHOOK_URL: ${{ secrets.DISCORD_DEPLOY_WEBHOOK_URL }}
|
||||
61
.github/workflows/deploy-bemo.yml
vendored
Normal file
61
.github/workflows/deploy-bemo.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Deploy bemo
|
||||
|
||||
# TODO: add some sort of production trigger
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- bemo-production
|
||||
|
||||
env:
|
||||
CI: 1
|
||||
PRINT_GITHUB_ANNOTATIONS: 1
|
||||
TLDRAW_ENV: ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
# createGithubDeployment (internal/scripts/lib/deploy.ts) registers the deployment that
|
||||
# renders the 'View Deployment' button on the PR.
|
||||
deployments: write
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }}
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ github.ref == 'refs/heads/bemo-production' && 'bemo-production' || 'bemo-canary' }}
|
||||
concurrency: bemo-${{ github.ref == 'refs/heads/bemo-production' && 'bemo-production' || github.ref }}
|
||||
|
||||
steps:
|
||||
# - name: Notify initial start
|
||||
# uses: MineBartekSA/discord-webhook@v2
|
||||
# if: github.ref == 'refs/heads/bemo-production'
|
||||
# with:
|
||||
# webhook: ${{ secrets.DISCORD_DEPLOY_WEBHOOK_URL }}
|
||||
# content: 'Preparing ${{ env.TLDRAW_ENV }} bemo deploy: ${{ github.event.head_commit.message }} by ${{ github.event.head_commit.author.name }}'
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
|
||||
- name: Build types
|
||||
run: yarn build-types
|
||||
|
||||
- name: Deploy
|
||||
run: yarn tsx internal/scripts/deploy-bemo.ts
|
||||
env:
|
||||
RELEASE_COMMIT_HASH: ${{ github.sha }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
DISCORD_DEPLOY_WEBHOOK_URL: ${{ secrets.DISCORD_DEPLOY_WEBHOOK_URL }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_BEMO_WORKER_DSN: ${{ secrets.SENTRY_BEMO_WORKER_DSN }}
|
||||
215
.github/workflows/deploy-dotcom.yml
vendored
Normal file
215
.github/workflows/deploy-dotcom.yml
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
name: Deploy .com
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, labeled, unlabeled]
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- production
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
# createGithubDeployment (internal/scripts/lib/deploy.ts) registers the deployment that
|
||||
# renders the 'View Deployment' button on the PR.
|
||||
deployments: write
|
||||
|
||||
env:
|
||||
CI: 1
|
||||
PRINT_GITHUB_ANNOTATIONS: 1
|
||||
TLDRAW_ENV: ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }}
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest-16-cores-arm-open
|
||||
environment: ${{ github.ref == 'refs/heads/production' && 'deploy-production' || 'deploy-staging' }}
|
||||
concurrency: dotcom-${{ github.ref == 'refs/heads/production' && 'deploy-production' || github.ref }}
|
||||
if: |
|
||||
github.event_name == 'push' ||
|
||||
(github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'dotcom-preview-please'))
|
||||
|
||||
steps:
|
||||
# - name: Notify initial start
|
||||
# uses: MineBartekSA/discord-webhook@v2
|
||||
# if: github.ref == 'refs/heads/production'
|
||||
# with:
|
||||
# webhook: ${{ secrets.DISCORD_DEPLOY_WEBHOOK_URL }}
|
||||
# content: 'Preparing ${{ env.TLDRAW_ENV }} dotcom deploy: ${{ github.event.head_commit.message }} by ${{ github.event.head_commit.author.name }}'
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
env:
|
||||
# Abort a stalled fetch so checkout's retry kicks in instead of hanging until the job timeout
|
||||
GIT_HTTP_LOW_SPEED_LIMIT: 1000
|
||||
GIT_HTTP_LOW_SPEED_TIME: 60
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
|
||||
- name: Determine zero deploy target
|
||||
id: deploy_target
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
if [[ "${GITHUB_REF}" == "refs/heads/production" ]]; then
|
||||
echo "DEPLOY_ZERO=flyio-multinode" >> $GITHUB_ENV
|
||||
elif [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
|
||||
echo "DEPLOY_ZERO=flyio-multinode" >> $GITHUB_ENV
|
||||
else
|
||||
echo "DEPLOY_ZERO=flyio" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Install Supabase CLI
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: supabase/setup-cli@ab058987d8d6c725971f6cf9d0b5c98467e30bd1 # v1
|
||||
with:
|
||||
# Pinned: v2.112.0 rejects the Management API's own timestamps, which
|
||||
# breaks `branches get` and `branches delete`. See supabase/cli#6115.
|
||||
version: 2.111.0
|
||||
|
||||
- name: Delete Supabase branch
|
||||
if: contains(github.event.pull_request.labels.*.name, 'reset-preview-db')
|
||||
env:
|
||||
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
|
||||
run: |
|
||||
supabase branches delete "pr-${{ github.event.number }}" \
|
||||
--project-ref "${{ vars.SUPABASE_PREVIEW_PROJECT_ID }}" || echo "::warning::Failed to delete Supabase branch (may not exist yet)"
|
||||
|
||||
- name: Create Supabase preview branch
|
||||
if: github.event_name == 'pull_request'
|
||||
env:
|
||||
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
|
||||
run: |
|
||||
BRANCH_NAME="pr-${{ github.event.number }}"
|
||||
PROJECT_REF="${{ vars.SUPABASE_PREVIEW_PROJECT_ID }}"
|
||||
|
||||
# Create branch if it doesn't already exist.
|
||||
if supabase branches get "$BRANCH_NAME" \
|
||||
--project-ref "$PROJECT_REF" > /dev/null 2>&1; then
|
||||
echo "Branch $BRANCH_NAME already exists."
|
||||
else
|
||||
supabase branches create "$BRANCH_NAME" --project-ref "$PROJECT_REF" > /dev/null
|
||||
echo "Branch $BRANCH_NAME created."
|
||||
fi
|
||||
|
||||
# Wait for branch to be ready, then extract connection strings.
|
||||
# Write to a temp file to avoid secrets leaking into CI step output.
|
||||
BRANCH_JSON=$(mktemp)
|
||||
for i in $(seq 1 24); do
|
||||
# While the branch is still provisioning, `branches get` may exit
|
||||
# non-zero and/or write a non-JSON line to stdout (the newer CLI
|
||||
# renders an error there). Tolerate both: `|| true` keeps `set -e`
|
||||
# from aborting on a non-zero get, and the guarded jq below treats
|
||||
# any non-JSON / missing field as "not ready yet" so the loop retries.
|
||||
supabase branches get "$BRANCH_NAME" \
|
||||
--project-ref "$PROJECT_REF" \
|
||||
-o json > "$BRANCH_JSON" 2>/dev/null || true
|
||||
POSTGRES_URL_TRANSACTION=$(jq -r '.POSTGRES_URL // empty' "$BRANCH_JSON" 2>/dev/null || true)
|
||||
if [ -n "$POSTGRES_URL_TRANSACTION" ]; then
|
||||
echo "Branch $BRANCH_NAME is ready."
|
||||
break
|
||||
fi
|
||||
echo "Waiting for branch to be ready ($i/24)..."
|
||||
sleep 5
|
||||
done
|
||||
POSTGRES_URL_NON_POOLING=$(jq -r '.POSTGRES_URL_NON_POOLING // empty' "$BRANCH_JSON" 2>/dev/null || true)
|
||||
rm "$BRANCH_JSON"
|
||||
|
||||
if [ -z "$POSTGRES_URL_TRANSACTION" ]; then
|
||||
echo "::error::Branch not ready after 2 minutes — POSTGRES_URL still empty"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$POSTGRES_URL_NON_POOLING" ] || [ "$POSTGRES_URL_NON_POOLING" = "null" ]; then
|
||||
echo "::error::Failed to get POSTGRES_URL_NON_POOLING from branch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Rewrite transaction pooler (port 6543, IPv6-only) to session pooler
|
||||
# (port 5432, IPv4-compatible) since GitHub Actions runners are IPv4-only.
|
||||
POSTGRES_URL="${POSTGRES_URL_TRANSACTION/:6543/:5432}"
|
||||
|
||||
echo "::add-mask::$POSTGRES_URL"
|
||||
echo "::add-mask::$POSTGRES_URL_TRANSACTION"
|
||||
echo "::add-mask::$POSTGRES_URL_NON_POOLING"
|
||||
echo "POSTGRES_URL=$POSTGRES_URL" >> "$GITHUB_ENV"
|
||||
echo "POSTGRES_URL_NON_POOLING=$POSTGRES_URL_NON_POOLING" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build types
|
||||
run: yarn build-types
|
||||
|
||||
- name: Setup Fly cli
|
||||
if: env.DEPLOY_ZERO == 'flyio' || env.DEPLOY_ZERO == 'flyio-multinode'
|
||||
uses: superfly/flyctl-actions/setup-flyctl@ed8efb33836e8b2096c7fd3ba1c8afe303ebbff1 # 1.6
|
||||
|
||||
- name: Deploy
|
||||
run: yarn tsx internal/scripts/deploy-dotcom.ts
|
||||
env:
|
||||
RELEASE_COMMIT_HASH: ${{ github.sha }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
APP_ORIGIN: ${{ vars.APP_ORIGIN }}
|
||||
ASSET_UPLOAD: ${{ vars.ASSET_UPLOAD }}
|
||||
IMAGE_WORKER: ${{ vars.IMAGE_WORKER }}
|
||||
MULTIPLAYER_SERVER: ${{ vars.MULTIPLAYER_SERVER }}
|
||||
USER_CONTENT_SENTRY_DSN: ${{ secrets.USER_CONTENT_SENTRY_DSN }}
|
||||
USER_CONTENT_URL: ${{ vars.USER_CONTENT_URL }}
|
||||
NEXT_PUBLIC_GOOGLE_CLOUD_PROJECT_NUMBER: ${{ vars.NEXT_PUBLIC_GOOGLE_CLOUD_PROJECT_NUMBER }}
|
||||
SUPABASE_LITE_URL: ${{ vars.SUPABASE_LITE_URL }}
|
||||
VERCEL_ORG_ID: ${{ vars.VERCEL_ORG_ID }}
|
||||
VERCEL_PROJECT_ID: ${{ vars.VERCEL_DOTCOM_PROJECT_ID }}
|
||||
|
||||
ANALYTICS_API_URL: ${{ secrets.ANALYTICS_API_URL }}
|
||||
ANALYTICS_API_TOKEN: ${{ secrets.ANALYTICS_API_TOKEN }}
|
||||
ASSET_UPLOAD_SENTRY_DSN: ${{ secrets.ASSET_UPLOAD_SENTRY_DSN }}
|
||||
BOTCOM_POSTGRES_CONNECTION_STRING: ${{ env.POSTGRES_URL_NON_POOLING || secrets.BOTCOM_POSTGRES_CONNECTION_STRING }}
|
||||
BOTCOM_POSTGRES_POOLED_CONNECTION_STRING: ${{ env.POSTGRES_URL || secrets.BOTCOM_POSTGRES_POOLED_CONNECTION_STRING }}
|
||||
CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
MCP_SCREENSHOT_TOKEN_SECRET: ${{ secrets.MCP_SCREENSHOT_TOKEN_SECRET }}
|
||||
DISCORD_DEPLOY_WEBHOOK_URL: ${{ secrets.DISCORD_DEPLOY_WEBHOOK_URL }}
|
||||
DISCORD_FEEDBACK_WEBHOOK_URL: ${{ secrets.DISCORD_FEEDBACK_WEBHOOK_URL }}
|
||||
PLAIN_API_KEY: ${{ secrets.PLAIN_API_KEY }}
|
||||
PLAIN_LABEL_TYPE_ID: ${{ secrets.PLAIN_LABEL_TYPE_ID }}
|
||||
PLAIN_WORKSPACE_ID: ${{ secrets.PLAIN_WORKSPACE_ID }}
|
||||
DISCORD_HEALTH_WEBHOOK_URL: ${{ secrets.DISCORD_HEALTH_WEBHOOK_URL }}
|
||||
GC_MAPS_API_KEY: ${{ secrets.GC_MAPS_API_KEY }}
|
||||
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
||||
HEALTH_CHECK_BEARER_TOKEN: ${{ secrets.HEALTH_CHECK_BEARER_TOKEN }}
|
||||
HEALTH_WORKER_UPDOWN_WEBHOOK_PATH: ${{ secrets.HEALTH_WORKER_UPDOWN_WEBHOOK_PATH }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
||||
R2_ACCESS_KEY_SECRET: ${{ secrets.R2_ACCESS_KEY_SECRET }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_CSP_REPORT_URI: ${{ secrets.SENTRY_CSP_REPORT_URI }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
SUPABASE_LITE_ANON_KEY: ${{ secrets.SUPABASE_LITE_ANON_KEY }}
|
||||
TLDRAW_LICENSE: ${{ secrets.TLDRAW_LICENSE }}
|
||||
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
|
||||
VITE_CLERK_PUBLISHABLE_KEY: ${{ secrets.VITE_CLERK_PUBLISHABLE_KEY }}
|
||||
VITE_POSTHOG_KEY: ${{ secrets.VITE_POSTHOG_KEY }}
|
||||
VITE_GA4_MEASUREMENT_ID: ${{ secrets.VITE_GA4_MEASUREMENT_ID }}
|
||||
WORKER_SENTRY_DSN: ${{ secrets.WORKER_SENTRY_DSN }}
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||
ZERO_ADMIN_PASSWORD: ${{ secrets.ZERO_ADMIN_PASSWORD }}
|
||||
ZERO_R2_ENDPOINT: ${{ secrets.ZERO_R2_ENDPOINT }}
|
||||
ZERO_R2_BUCKET_NAME: ${{ secrets.ZERO_R2_BUCKET_NAME }}
|
||||
ZERO_R2_ACCESS_KEY_ID: ${{ secrets.ZERO_R2_ACCESS_KEY_ID }}
|
||||
ZERO_R2_SECRET_ACCESS_KEY: ${{ secrets.ZERO_R2_SECRET_ACCESS_KEY }}
|
||||
ZERO_OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.ZERO_OTEL_EXPORTER_OTLP_ENDPOINT }}
|
||||
ZERO_OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.ZERO_OTEL_EXPORTER_OTLP_HEADERS }}
|
||||
|
||||
# The deploy script posts its own "Deploy failed" message, but a job timeout
|
||||
# kills it without running that handler, so cover the cancelled case here.
|
||||
- name: Notify Discord on cancelled deploy
|
||||
if: cancelled() && github.event_name == 'push'
|
||||
uses: ./.github/actions/discord-fail-notify
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_DEPLOY_WEBHOOK_URL }}
|
||||
status: was cancelled (job timeout?)
|
||||
115
.github/workflows/deploy-grafana.yml
vendored
Normal file
115
.github/workflows/deploy-grafana.yml
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
name: Deploy grafana resources
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'internal/grafana/**'
|
||||
- '.github/workflows/deploy-grafana.yml'
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'internal/grafana/**'
|
||||
- '.github/workflows/deploy-grafana.yml'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
GCX_VERSION: 1.0.0
|
||||
GCX_SHA256: c88c65958d19d83dc3a192a2a5037e08307a9c776019f24249b89f369c9c4d41
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }}
|
||||
# Validate and push both need the Grafana secrets, which fork PRs don't get.
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-latest
|
||||
concurrency: grafana-deploy-${{ github.event_name == 'push' && 'main' || github.ref }}
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install gcx
|
||||
run: |
|
||||
curl -fsSL -o gcx.tar.gz "https://github.com/grafana/gcx/releases/download/v${GCX_VERSION}/gcx_${GCX_VERSION}_linux_amd64.tar.gz"
|
||||
echo "${GCX_SHA256} gcx.tar.gz" | sha256sum -c -
|
||||
tar xzf gcx.tar.gz gcx
|
||||
sudo mv gcx /usr/local/bin/gcx
|
||||
gcx --version
|
||||
|
||||
- name: Validate resources
|
||||
run: gcx resources validate -p internal/grafana/resources
|
||||
env:
|
||||
GRAFANA_SERVER: ${{ secrets.GRAFANA_SERVER }}
|
||||
GRAFANA_TOKEN: ${{ secrets.GRAFANA_TOKEN }}
|
||||
|
||||
- name: Dry run
|
||||
if: github.event_name == 'pull_request'
|
||||
run: gcx resources push -p internal/grafana/resources --dry-run --on-error abort
|
||||
env:
|
||||
GRAFANA_SERVER: ${{ secrets.GRAFANA_SERVER }}
|
||||
GRAFANA_TOKEN: ${{ secrets.GRAFANA_TOKEN }}
|
||||
|
||||
# Everything except alert rules, folders first. Two orderings matter and neither is
|
||||
# the alphabetical one this glob would otherwise give: folders have to exist before
|
||||
# anything annotated into them, and alert rules go last (see the step below for why).
|
||||
# A commit adding a folder and a dashboard inside it lands both in one push, so the
|
||||
# folder cannot be assumed to already be there.
|
||||
- name: Push resources
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
for dir in internal/grafana/resources/*folders*/; do
|
||||
gcx resources push -p "$dir" --on-error abort
|
||||
done
|
||||
for dir in internal/grafana/resources/*/; do
|
||||
case "$dir" in *folders*|*alertrules*) continue ;; esac
|
||||
gcx resources push -p "$dir" --on-error abort
|
||||
done
|
||||
env:
|
||||
GRAFANA_SERVER: ${{ secrets.GRAFANA_SERVER }}
|
||||
GRAFANA_TOKEN: ${{ secrets.GRAFANA_TOKEN }}
|
||||
|
||||
# Alert rules are the one resource type that can fail here having passed the PR
|
||||
# dry run, because alerting has no server-side dry-run — the PR reports them as
|
||||
# "skipped". A newly created rule carrying group labels is rejected outright
|
||||
# ("cannot set group when creating a new rule"), so the first time any new rule
|
||||
# lands, this step is where it surfaces. Kept last and separate so that failure
|
||||
# costs the rule, not every dashboard in the tree. Still fails the job.
|
||||
- name: Push alert rules
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
for dir in internal/grafana/resources/*alertrules*/; do
|
||||
gcx resources push -p "$dir" --on-error abort
|
||||
done
|
||||
env:
|
||||
GRAFANA_SERVER: ${{ secrets.GRAFANA_SERVER }}
|
||||
GRAFANA_TOKEN: ${{ secrets.GRAFANA_TOKEN }}
|
||||
|
||||
# Notification templates can't go through `gcx resources push` — gcx's discovery
|
||||
# registry ignores the notifications.alerting.grafana.app API group entirely — so
|
||||
# template files live in templates/ (not resources/) and go through the classic
|
||||
# provisioning API. The PUT is idempotent and sets `api` provenance, which locks
|
||||
# UI edits the same way provisioned resources are locked.
|
||||
- name: Push notification templates
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
for f in internal/grafana/templates/*.gotmpl; do
|
||||
name=$(basename "$f" .gotmpl)
|
||||
jq -Rs '{template: .}' "$f" | curl -fsS -X PUT \
|
||||
"$GRAFANA_SERVER/api/v1/provisioning/templates/$name" \
|
||||
-H "Authorization: Bearer $GRAFANA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @- > /dev/null
|
||||
done
|
||||
env:
|
||||
GRAFANA_SERVER: ${{ secrets.GRAFANA_SERVER }}
|
||||
GRAFANA_TOKEN: ${{ secrets.GRAFANA_TOKEN }}
|
||||
63
.github/workflows/deploy-mcp-app.yml
vendored
Normal file
63
.github/workflows/deploy-mcp-app.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
name: Deploy MCP app
|
||||
|
||||
on:
|
||||
# Auto-deploy whenever production is updated, keeping the public MCP worker on
|
||||
# the same commit and SDK build as production dotcom. Only production triggers a
|
||||
# deploy: there is no staging worker, so a push to main must not overwrite the
|
||||
# shared `tldraw-mcp-app` worker.
|
||||
push:
|
||||
branches:
|
||||
- production
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
target:
|
||||
description: 'Target ref to deploy'
|
||||
required: true
|
||||
default: 'production'
|
||||
|
||||
env:
|
||||
CI: 1
|
||||
PRINT_GITHUB_ANNOTATIONS: 1
|
||||
TLDRAW_ENV: production
|
||||
TARGET: ${{ github.event.inputs.target || github.ref_name }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy MCP app to production
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-latest
|
||||
environment: deploy-production
|
||||
concurrency: mcp-app-deploy-production
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ env.TARGET }}
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
|
||||
- name: Build types
|
||||
run: yarn build-types
|
||||
|
||||
- name: Deploy
|
||||
run: yarn workspace mcp-app deploy
|
||||
env:
|
||||
VITE_TLDRAW_LICENSE_KEY: ${{ secrets.MCP_APP_TLDRAW_LICENSE_KEY }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
|
||||
- name: Notify Discord on failure
|
||||
if: failure()
|
||||
uses: ./.github/actions/discord-fail-notify
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_DEPLOY_WEBHOOK_URL }}
|
||||
57
.github/workflows/deploy-templates.yml
vendored
Normal file
57
.github/workflows/deploy-templates.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: Deploy templates
|
||||
|
||||
# Deploys the cloudflare-hosted starter kit demos at *.templates.tldraw.dev, which are the live
|
||||
# embeds on the docs pages. Two things should reach them: a change to a template (so edits go live
|
||||
# without waiting for a release), and a release (so the demos pick up the new SDK — an SDK bump
|
||||
# never touches templates/, so the path filter alone would never fire for one). This workflow
|
||||
# covers the first; publish.yml covers the second with a job of its own, using the same
|
||||
# .github/actions/deploy-templates steps.
|
||||
#
|
||||
# The vercel-hosted demos (chat, shader, workflow) deploy through vercel's own git integration and
|
||||
# aren't handled here.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'templates/**'
|
||||
workflow_dispatch:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy templates
|
||||
# CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID are secrets on this environment rather than on
|
||||
# the repository, so the job has to select it to see them at all.
|
||||
environment: deploy-production
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest-16-cores-arm-open
|
||||
# a release deploy and a template-change deploy can otherwise race each other onto the same
|
||||
# custom domains, and the loser silently wins.
|
||||
concurrency:
|
||||
group: deploy-templates
|
||||
cancel-in-progress: false
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: ./.github/actions/deploy-templates
|
||||
with:
|
||||
license-key: ${{ secrets.TEMPLATES_TLDRAW_LICENSE_KEY }}
|
||||
cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
|
||||
- name: Notify Discord on failure
|
||||
if: failure()
|
||||
uses: ./.github/actions/discord-fail-notify
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_DEPLOY_WEBHOOK_URL }}
|
||||
183
.github/workflows/e2e-device.yml
vendored
Normal file
183
.github/workflows/e2e-device.yml
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
name: End to end tests (device)
|
||||
|
||||
# Run-on-request device-realistic e2e tests.
|
||||
#
|
||||
# These run real Mobile Safari (iOS Simulator) and real Chrome (Android emulator)
|
||||
# through the OS input pipeline via Appium + WebdriverIO. Unlike the Playwright
|
||||
# suite, this exercises the browser's own gesture recogniser and platform
|
||||
# behaviour, so it can catch regressions that synthetic DOM events (CDP) cannot.
|
||||
# It currently covers multi-touch gestures (pinch), but the harness is meant to
|
||||
# grow to other device-specific behaviour (exports, clipboard, shortcuts, etc.)
|
||||
# and other platforms.
|
||||
#
|
||||
# It is slower and a bit flaky by nature, so it is NOT part of the per-PR checks.
|
||||
# Trigger it on demand:
|
||||
#
|
||||
# - Manually from the Actions tab (workflow_dispatch), choosing the platform.
|
||||
# - By adding the `e2e-device-tests` label to a PR that touches device behaviour.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
platform:
|
||||
description: Which platform(s) to run
|
||||
type: choice
|
||||
default: both
|
||||
options: [both, ios, android]
|
||||
ios_device:
|
||||
description: iOS simulator device name (optional; auto-picks newest iPhone if empty)
|
||||
type: string
|
||||
default: ''
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
|
||||
env:
|
||||
CI: 1
|
||||
EXAMPLES_PORT: '5420'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
# A new push to a PR (or a re-dispatch) cancels the previous in-flight run.
|
||||
concurrency:
|
||||
group: e2e-device-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
ios:
|
||||
name: iOS Safari
|
||||
# Run when dispatched with ios/both, or when a PR is labelled `e2e-device-tests`.
|
||||
if: >
|
||||
(github.event_name == 'workflow_dispatch' && inputs.platform != 'android') ||
|
||||
(github.event_name == 'pull_request' && github.event.label.name == 'e2e-device-tests')
|
||||
runs-on: macos-26 # pinned: image version determines Xcode + iOS sim runtimes
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run our setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Build examples
|
||||
run: yarn run -T lazy refresh-assets && yarn run -T lazy run prebuild && cd apps/examples && yarn build
|
||||
|
||||
- name: Install Appium and the XCUITest driver
|
||||
run: |
|
||||
npm i -g appium@3
|
||||
appium driver install xcuitest
|
||||
|
||||
- name: Resolve Xcode version (WDA cache key)
|
||||
id: xcode
|
||||
run: echo "version=$(xcodebuild -version | head -1 | awk '{print $2}')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache WebDriverAgent build
|
||||
# WDA is rebuilt from source on a cold simulator (~3.5 min). Cache its
|
||||
# DerivedData keyed on the Xcode version so iterating on test fixes reuses
|
||||
# the prebuilt WDA instead of rebuilding. Bump the trailing -vN to bust.
|
||||
id: wda-cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ runner.temp }}/wda-derived
|
||||
key: wda-${{ runner.os }}-xcode-${{ steps.xcode.outputs.version }}-v1
|
||||
|
||||
- name: Boot iOS simulator
|
||||
id: sim
|
||||
# A fresh iOS simulator's first boot runs slow one-time migration plugins
|
||||
# that exceed XCUITest's boot-wait. So boot ONE simulator here, wait for it
|
||||
# to finish (`bootstatus -b`), and hand its UDID to Appium so it attaches to
|
||||
# the ready device instead of booting its own. The available iPhone models
|
||||
# depend on the Xcode the runner image ships, so auto-pick rather than
|
||||
# hardcode a name (an explicit `ios_device` input wins if it matches).
|
||||
env:
|
||||
DEVICE_NAME: ${{ inputs.ios_device }}
|
||||
run: |
|
||||
xcrun simctl list devices available
|
||||
SEL=$(xcrun simctl list devices available --json | node -e '
|
||||
const data = JSON.parse(require("fs").readFileSync(0, "utf8"));
|
||||
const want = process.env.DEVICE_NAME || "";
|
||||
const runtimes = Object.keys(data.devices).filter(r => r.includes("iOS")).sort().reverse();
|
||||
const phones = [];
|
||||
for (const r of runtimes) for (const d of (data.devices[r] || [])) {
|
||||
if (d.isAvailable !== false && d.name.startsWith("iPhone")) phones.push(d);
|
||||
}
|
||||
const pick = (want && phones.find(d => d.name === want)) || phones[0];
|
||||
if (!pick) process.exit(2);
|
||||
process.stdout.write(pick.udid + "|" + pick.name);
|
||||
') || { echo "No available iPhone simulator on this runner"; exit 1; }
|
||||
UDID="${SEL%%|*}"
|
||||
NAME="${SEL#*|}"
|
||||
echo "Using $NAME ($UDID)"
|
||||
xcrun simctl boot "$UDID" || true
|
||||
xcrun simctl bootstatus "$UDID" -b
|
||||
echo "udid=$UDID" >> "$GITHUB_OUTPUT"
|
||||
echo "name=$NAME" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Serve built examples
|
||||
run: yarn preview --port "$EXAMPLES_PORT" &
|
||||
working-directory: apps/examples
|
||||
|
||||
- name: Wait for examples server
|
||||
run: npx --yes wait-on "http://localhost:$EXAMPLES_PORT/end-to-end" -t 120000
|
||||
|
||||
- name: Run device tests (iOS)
|
||||
run: yarn e2e-device-ios
|
||||
working-directory: apps/examples
|
||||
env:
|
||||
APPIUM_DEVICE_NAME: ${{ steps.sim.outputs.name }}
|
||||
APPIUM_UDID: ${{ steps.sim.outputs.udid }}
|
||||
# Build WDA into the cached dir; reuse the prebuilt one on a cache hit.
|
||||
APPIUM_DERIVED_DATA_PATH: ${{ runner.temp }}/wda-derived
|
||||
APPIUM_USE_PREBUILT_WDA: ${{ steps.wda-cache.outputs.cache-hit }}
|
||||
|
||||
android:
|
||||
name: Android Chrome
|
||||
if: >
|
||||
(github.event_name == 'workflow_dispatch' && inputs.platform != 'ios') ||
|
||||
(github.event_name == 'pull_request' && github.event.label.name == 'e2e-device-tests')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run our setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Enable KVM (hardware acceleration for the emulator)
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
|
||||
| sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: Build examples
|
||||
run: yarn run -T lazy refresh-assets && yarn run -T lazy run prebuild && cd apps/examples && yarn build
|
||||
|
||||
- name: Install Appium and the UiAutomator2 driver
|
||||
run: |
|
||||
npm i -g appium@3
|
||||
appium driver install uiautomator2
|
||||
|
||||
- name: Serve built examples
|
||||
run: yarn preview --port "$EXAMPLES_PORT" &
|
||||
working-directory: apps/examples
|
||||
|
||||
- name: Wait for examples server
|
||||
run: npx --yes wait-on "http://localhost:$EXAMPLES_PORT/end-to-end" -t 120000
|
||||
|
||||
- name: Run device tests on emulator
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: 34
|
||||
arch: x86_64
|
||||
target: google_apis
|
||||
# The emulator's `localhost` is the emulator itself, so forward the host
|
||||
# dev server into it before running the tests.
|
||||
script: |
|
||||
adb reverse tcp:${{ env.EXAMPLES_PORT }} tcp:${{ env.EXAMPLES_PORT }}
|
||||
cd apps/examples && yarn e2e-device-android
|
||||
35
.github/workflows/get-changelog.yml
vendored
Normal file
35
.github/workflows/get-changelog.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Get draft changelog
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: 'Get draft changlog'
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run our setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Extract changelog
|
||||
run: yarn extract-draft-changelog
|
||||
|
||||
- name: Upload changelog.md to artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: changelog-output
|
||||
path: changelog.md
|
||||
73
.github/workflows/i18n-download-strings.yml
vendored
Normal file
73
.github/workflows/i18n-download-strings.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
name: i18n download strings
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 10 * * 1'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
download:
|
||||
name: 'Download latest strings'
|
||||
runs-on: ubuntu-latest
|
||||
environment: 'deploy-production'
|
||||
|
||||
permissions:
|
||||
# Give the default GITHUB_TOKEN write permission to commit and push the
|
||||
# added or changed files to the repository.
|
||||
contents: write
|
||||
pull-requests: write
|
||||
repository-projects: write
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
|
||||
- name: Download i18n strings
|
||||
run: 'yarn i18n-download-strings'
|
||||
env:
|
||||
LOKALISE_API_TOKEN: ${{ secrets.LOKALISE_API_TOKEN }}
|
||||
LOKALISE_PROJECT_ID: ${{ secrets.LOKALISE_PROJECT_ID }}
|
||||
LOKALISE_TLDRAW_PROJECT_ID: ${{ secrets.LOKALISE_TLDRAW_PROJECT_ID }}
|
||||
|
||||
- name: Compile i18n strings
|
||||
run: 'yarn build-i18n'
|
||||
|
||||
- name: Validate the strings
|
||||
run: 'yarn i18n-check -f react-intl --locales ./apps/dotcom/client/public/tla/locales/ --source en --only invalidKeys'
|
||||
|
||||
- name: Commit and push changes
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
git config --global user.name 'huppy-bot[bot]'
|
||||
git config --global user.email '128400622+huppy-bot[bot]@users.noreply.github.com'
|
||||
|
||||
# Check if there are any changes to JSON files
|
||||
if ! git diff --quiet '*.json' || ! git diff --cached --quiet '*.json'; then
|
||||
echo "Changes detected, creating PR..."
|
||||
else
|
||||
echo "No changes to commit"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Delete the branch if it already exists
|
||||
git push origin --delete update-i18n-strings 2>/dev/null || true
|
||||
|
||||
git checkout -b update-i18n-strings
|
||||
git add "*.json"
|
||||
git commit --no-verify -m '[automated] update i18n strings'
|
||||
git push --set-upstream origin update-i18n-strings
|
||||
gh pr create --title "[automated] update i18n strings" --body "This PR updates the i18n strings.
|
||||
|
||||
### Change type
|
||||
- [x] \`other\`" --base main --head update-i18n-strings
|
||||
35
.github/workflows/i18n-upload-strings.yml
vendored
Normal file
35
.github/workflows/i18n-upload-strings.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: i18n upload strings
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
name: 'Upload latest strings'
|
||||
runs-on: ubuntu-latest
|
||||
environment: 'deploy-production'
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
|
||||
- name: Upload i18n strings
|
||||
run: 'yarn i18n-upload-strings'
|
||||
env:
|
||||
LOKALISE_API_TOKEN: ${{ secrets.LOKALISE_API_TOKEN }}
|
||||
LOKALISE_PROJECT_ID: ${{ secrets.LOKALISE_PROJECT_ID }}
|
||||
LOKALISE_TLDRAW_PROJECT_ID: ${{ secrets.LOKALISE_TLDRAW_PROJECT_ID }}
|
||||
LOKALISE_TEAM_ID: ${{ secrets.LOKALISE_TEAM_ID }}
|
||||
LOKALISE_CARD_ID: ${{ secrets.LOKALISE_CARD_ID }}
|
||||
122
.github/workflows/issue-triage.yml
vendored
Normal file
122
.github/workflows/issue-triage.yml
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
name: Issue Triage
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
issue_number:
|
||||
description: 'Issue number to triage (for testing)'
|
||||
required: true
|
||||
type: number
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
id-token: write
|
||||
env:
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number || inputs.issue_number }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Get issue details
|
||||
id: issue
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh issue view ${{ env.ISSUE_NUMBER }} --json title,body,labels,state > /tmp/issue.json
|
||||
# Fetch issue type via API (not yet supported in gh CLI)
|
||||
ISSUE_TYPE=$(gh api repos/${{ github.repository }}/issues/${{ env.ISSUE_NUMBER }} --jq '.type.name // "none"')
|
||||
# Merge the type into the JSON
|
||||
jq --arg type "$ISSUE_TYPE" '. + {issueType: $type}' /tmp/issue.json > /tmp/issue_with_type.json
|
||||
echo "details<<EOF" >> $GITHUB_OUTPUT
|
||||
cat /tmp/issue_with_type.json >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Triage Issue with Claude
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
prompt: |
|
||||
You are an issue triage bot for the tldraw repository. Tidy up issue #${{ env.ISSUE_NUMBER }}.
|
||||
|
||||
## Issue details
|
||||
```json
|
||||
${{ steps.issue.outputs.details }}
|
||||
```
|
||||
|
||||
## Tasks
|
||||
|
||||
### 1. Improve the title (if needed)
|
||||
- Make sure the title is clear and descriptive
|
||||
- Use sentence case (capitalize only first word and proper nouns)
|
||||
- For bugs: describe the symptom (e.g., "Arrow bindings break with rotated shapes")
|
||||
- For features: use imperative mood (e.g., "Add padding option to zoomToFit")
|
||||
- If the title is already good, leave it as-is
|
||||
|
||||
### 2. Improve the body (if needed)
|
||||
- If the issue is written in a language other than English, add an English translation at the top of the body (keep the original text below)
|
||||
- Make sure the body has enough context to understand the issue
|
||||
- If the body is already good, leave it as-is
|
||||
|
||||
To update the title or body:
|
||||
```bash
|
||||
gh issue edit ${{ env.ISSUE_NUMBER }} --title "New title" --body "New body"
|
||||
```
|
||||
|
||||
### 3. Set the issue type
|
||||
Determine if this is a:
|
||||
- **Bug**: Something is broken or not working as designed (crashes, rendering errors, data loss)
|
||||
- **Feature**: A request for new functionality or enhancements (including UX preferences like "I wish X worked like Y")
|
||||
- **Task**: Internal team task
|
||||
- **Example**: Request for a new SDK example
|
||||
|
||||
Important: Distinguish between TRUE bugs and UX preferences disguised as bugs:
|
||||
- TRUE BUG: "Clicking save crashes the app" or "Text doesn't render correctly"
|
||||
- NOT A BUG (use Feature): "I don't like the color of the button" or "The menu should be on the left instead of right"
|
||||
|
||||
To set or change the issue type:
|
||||
```bash
|
||||
gh api repos/${{ github.repository }}/issues/${{ env.ISSUE_NUMBER }} -X PATCH -f type="Bug"
|
||||
```
|
||||
Replace "Bug" with the appropriate type (Bug, Feature, Task, or Example).
|
||||
|
||||
### 4. Apply appropriate labels
|
||||
Use these labels as appropriate:
|
||||
- `sdk` - Affects the tldraw SDK (@tldraw/tldraw package)
|
||||
- `dotcom` - Related to tldraw.com
|
||||
- `docs` - Changes only affect documentation
|
||||
- `examples` - Request for a new SDK example
|
||||
- `performance` - Performance-related issues
|
||||
- `a11y` - Accessibility-related issues
|
||||
- `bugfix` - For confirmed bug fixes
|
||||
- `feature` - For new features
|
||||
- `improvement` - For product improvements
|
||||
- `More Info Needed` - If the issue lacks critical information needed to understand or reproduce
|
||||
|
||||
### 5. Assign a milestone (if appropriate)
|
||||
If the issue clearly fits one of these milestones, assign it. Otherwise, leave the milestone empty.
|
||||
|
||||
Available milestones:
|
||||
- **Improve developer resources**: For examples, documentation, improved code comments, starter kits, and `npm create tldraw` improvements
|
||||
- **Improve automations**: For GitHub Actions, review bots, CI/CD, and other automation improvements
|
||||
|
||||
To assign a milestone:
|
||||
```bash
|
||||
gh issue edit ${{ env.ISSUE_NUMBER }} --milestone "Milestone Name"
|
||||
```
|
||||
|
||||
Only assign a milestone if there's a clear fit. Most issues won't need a milestone.
|
||||
|
||||
## Output format
|
||||
Use the GitHub CLI (`gh`) to make any changes. Do NOT add comments to the issue.
|
||||
claude_args: '--allowedTools "Bash(gh issue:*),Bash(gh api:*)"'
|
||||
52
.github/workflows/license-report.yml
vendored
Normal file
52
.github/workflows/license-report.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: Update license report
|
||||
|
||||
# Skipped on PRs from forks: the push below needs write access to the head branch.
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '**/package.json'
|
||||
- 'yarn.lock'
|
||||
- 'internal/scripts/license-report.ts'
|
||||
- '.github/workflows/license-report.yml'
|
||||
|
||||
env:
|
||||
CI: 1
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
update:
|
||||
name: 'Update license report'
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# `pull_request` checks out a detached merge commit, which we can't push back to.
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config user.name 'github-actions[bot]'
|
||||
git config user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
|
||||
- name: Generate license report
|
||||
run: yarn license-report --prod
|
||||
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git add apps/docs/content/community/dependencies.mdx
|
||||
git diff --cached --quiet || git commit -m 'Update license report'
|
||||
git push
|
||||
129
.github/workflows/playwright-dotcom.yml
vendored
Normal file
129
.github/workflows/playwright-dotcom.yml
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
name: End to end tests (dotcom)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
CI: 1
|
||||
PRINT_GITHUB_ANNOTATIONS: 1
|
||||
TLDRAW_ENV: 'development'
|
||||
# process-compose (the dev-app orchestrator) has no TTY in CI — run it headless.
|
||||
PC_DISABLE_TUI: '1'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: 'Check for relevant changes'
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.pull_request.head.repo.fork == false }}
|
||||
outputs:
|
||||
relevant: ${{ steps.includes.outputs.match == 'true' && steps.nondocs.outputs.match == 'true' }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Check for changes to relevant paths
|
||||
uses: dorny/paths-filter@v3
|
||||
id: includes
|
||||
with:
|
||||
filters: |
|
||||
match:
|
||||
- 'packages/**'
|
||||
- 'apps/dotcom/**'
|
||||
- '.github/workflows/playwright-dotcom.yml'
|
||||
- '.github/actions/**'
|
||||
- 'yarn.lock'
|
||||
|
||||
- name: Check for any non-doc changes
|
||||
uses: dorny/paths-filter@v3
|
||||
id: nondocs
|
||||
with:
|
||||
predicate-quantifier: 'every'
|
||||
filters: |
|
||||
match:
|
||||
- '**'
|
||||
- '!**/*.md'
|
||||
- '!**/*.mdx'
|
||||
|
||||
build:
|
||||
name: 'End to end tests (dotcom)'
|
||||
needs: check
|
||||
if: needs.check.outputs.relevant == 'true'
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest-8-cores-open
|
||||
environment: 'e2e-dotcom'
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run our setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
# No process-compose install step: `yarn dev-app` (the e2e webServer) calls the `process-compose`
|
||||
# bin of @tldraw/scripts, which fetches the pinned binary on first use.
|
||||
|
||||
- name: Set up Playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
|
||||
- name: Build dotcom client
|
||||
run: yarn run -T lazy refresh-assets && yarn run -T lazy run prebuild && yarn run -T lazy run build-i18n && cd apps/dotcom/client && yarn build
|
||||
env:
|
||||
VITE_CLERK_PUBLISHABLE_KEY: ${{ secrets.VITE_CLERK_PUBLISHABLE_KEY }}
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: |
|
||||
echo "CLERK_PUBLISHABLE_KEY=${{ secrets.VITE_CLERK_PUBLISHABLE_KEY }}" > apps/dotcom/sync-worker/.dev.vars
|
||||
echo "CLERK_SECRET_KEY=${{ secrets.CLERK_SECRET_KEY }}" >> apps/dotcom/sync-worker/.dev.vars
|
||||
yarn e2e-dotcom
|
||||
env:
|
||||
VITE_CLERK_PUBLISHABLE_KEY: ${{ secrets.VITE_CLERK_PUBLISHABLE_KEY }}
|
||||
CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }}
|
||||
if: ${{!contains(github.event.pull_request.labels.*.name, 'e2e-x10') }}
|
||||
|
||||
- name: Run Playwright tests (x10)
|
||||
run: |
|
||||
echo "CLERK_PUBLISHABLE_KEY=${{ secrets.VITE_CLERK_PUBLISHABLE_KEY }}" > apps/dotcom/sync-worker/.dev.vars
|
||||
echo "CLERK_SECRET_KEY=${{ secrets.CLERK_SECRET_KEY }}" >> apps/dotcom/sync-worker/.dev.vars
|
||||
yarn e2e-dotcom-x10
|
||||
env:
|
||||
VITE_CLERK_PUBLISHABLE_KEY: ${{ secrets.VITE_CLERK_PUBLISHABLE_KEY }}
|
||||
CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }}
|
||||
if: contains(github.event.pull_request.labels.*.name, 'e2e-x10')
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: apps/dotcom/client/playwright-report
|
||||
retention-days: 30
|
||||
|
||||
- uses: shallwefootball/s3-upload-action@0d261a6f15b3b2e209dfebdecace4b100c04f95b # master
|
||||
# only upload if we have AWS credentials:
|
||||
env:
|
||||
HAS_AWS_KEY: ${{ secrets.AWS_S3_SECRET_KEY && '1' || '' }}
|
||||
if: always() && env.HAS_AWS_KEY == '1'
|
||||
name: Upload S3
|
||||
id: s3
|
||||
with:
|
||||
aws_key_id: ${{ secrets.AWS_S3_KEY_ID }}
|
||||
aws_secret_access_key: ${{ secrets.AWS_S3_SECRET_KEY}}
|
||||
aws_bucket: playwright.tldraw.xyz
|
||||
source_dir: apps/dotcom/client/playwright-report
|
||||
|
||||
- name: Log report to summary
|
||||
if: always()
|
||||
run: |
|
||||
report_url="https://playwright.tldraw.xyz/${{ steps.s3.outputs.object_key }}/index.html"
|
||||
echo "Report: $report_url"
|
||||
echo "## Playwright report" >> $GITHUB_STEP_SUMMARY
|
||||
echo "* $report_url" >> $GITHUB_STEP_SUMMARY
|
||||
106
.github/workflows/playwright-examples.yml
vendored
Normal file
106
.github/workflows/playwright-examples.yml
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
name: End to end tests (examples)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
CI: 1
|
||||
PRINT_GITHUB_ANNOTATIONS: 1
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: 'Check for relevant changes'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
relevant: ${{ steps.includes.outputs.match == 'true' && steps.nondocs.outputs.match == 'true' }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Check for changes to relevant paths
|
||||
uses: dorny/paths-filter@v3
|
||||
id: includes
|
||||
with:
|
||||
filters: |
|
||||
match:
|
||||
- 'packages/**'
|
||||
- 'apps/examples/**'
|
||||
- '.github/workflows/playwright-examples.yml'
|
||||
- '.github/actions/**'
|
||||
- 'yarn.lock'
|
||||
|
||||
- name: Check for any non-doc changes
|
||||
uses: dorny/paths-filter@v3
|
||||
id: nondocs
|
||||
with:
|
||||
predicate-quantifier: 'every'
|
||||
filters: |
|
||||
match:
|
||||
- '**'
|
||||
- '!**/*.md'
|
||||
- '!**/*.mdx'
|
||||
|
||||
build:
|
||||
name: 'End to end tests (examples)'
|
||||
needs: check
|
||||
if: needs.check.outputs.relevant == 'true'
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest-32-cores-open
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run our setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Set up Playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
|
||||
- name: Build examples
|
||||
run: yarn run -T lazy refresh-assets && yarn run -T lazy run prebuild && cd apps/examples && yarn build
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: 'yarn e2e'
|
||||
working-directory: apps/examples
|
||||
env:
|
||||
GITHUB_REF_NAME: ${{ github.ref_name }}
|
||||
GIT_COMMIT: ${{ github.sha }}
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: apps/examples/playwright-report
|
||||
retention-days: 30
|
||||
|
||||
- uses: shallwefootball/s3-upload-action@0d261a6f15b3b2e209dfebdecace4b100c04f95b # master
|
||||
# only upload if we have AWS credentials:
|
||||
env:
|
||||
HAS_AWS_KEY: ${{ secrets.AWS_S3_SECRET_KEY && '1' || '' }}
|
||||
if: always() && env.HAS_AWS_KEY == '1'
|
||||
name: Upload S3
|
||||
id: s3
|
||||
with:
|
||||
aws_key_id: ${{ secrets.AWS_S3_KEY_ID }}
|
||||
aws_secret_access_key: ${{ secrets.AWS_S3_SECRET_KEY}}
|
||||
aws_bucket: playwright.tldraw.xyz
|
||||
source_dir: apps/examples/playwright-report
|
||||
|
||||
- name: Log report to summary
|
||||
if: always()
|
||||
run: |
|
||||
report_url="https://playwright.tldraw.xyz/${{ steps.s3.outputs.object_key }}/index.html"
|
||||
echo "Report: $report_url"
|
||||
echo "## Playwright report" >> $GITHUB_STEP_SUMMARY
|
||||
echo "* $report_url" >> $GITHUB_STEP_SUMMARY
|
||||
71
.github/workflows/playwright-mermaid.yml
vendored
Normal file
71
.github/workflows/playwright-mermaid.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
name: End to end tests (mermaid readability)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
CI: 1
|
||||
PRINT_GITHUB_ANNOTATIONS: 1
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: 'Check for relevant changes'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
relevant: ${{ steps.includes.outputs.match == 'true' }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# Only mermaid conversion changes can affect these checks, unlike the rest of the e2e suites.
|
||||
- name: Check for changes to relevant paths
|
||||
uses: dorny/paths-filter@v3
|
||||
id: includes
|
||||
with:
|
||||
filters: |
|
||||
match:
|
||||
- 'packages/mermaid/**'
|
||||
- 'apps/examples/src/examples/use-cases/hundred-mermaids/**'
|
||||
- 'apps/examples/src/misc/mermaidReadability.ts'
|
||||
- 'apps/examples/e2e/mermaid-readability/**'
|
||||
- '.github/workflows/playwright-mermaid.yml'
|
||||
- '.github/actions/**'
|
||||
- 'yarn.lock'
|
||||
|
||||
build:
|
||||
name: 'End to end tests (mermaid readability)'
|
||||
needs: check
|
||||
if: needs.check.outputs.relevant == 'true'
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest-16-cores-open
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run our setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Set up Playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
|
||||
- name: Build examples
|
||||
run: yarn run -T lazy refresh-assets && yarn run -T lazy run prebuild && cd apps/examples && yarn build
|
||||
|
||||
- name: Run Playwright tests (mermaid readability)
|
||||
run: 'yarn e2e-mermaid-readability'
|
||||
working-directory: apps/examples
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: apps/examples/playwright-report
|
||||
retention-days: 30
|
||||
107
.github/workflows/playwright-perf.yml
vendored
Normal file
107
.github/workflows/playwright-perf.yml
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
name: End to end tests (perf)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
CI: 1
|
||||
PRINT_GITHUB_ANNOTATIONS: 1
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: 'Check for relevant changes'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
relevant: ${{ steps.includes.outputs.match == 'true' && steps.nondocs.outputs.match == 'true' }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Check for changes to relevant paths
|
||||
uses: dorny/paths-filter@v3
|
||||
id: includes
|
||||
with:
|
||||
filters: |
|
||||
match:
|
||||
- 'packages/**'
|
||||
- 'apps/examples/**'
|
||||
- '.github/workflows/playwright-perf.yml'
|
||||
- '.github/actions/**'
|
||||
- 'yarn.lock'
|
||||
|
||||
- name: Check for any non-doc changes
|
||||
uses: dorny/paths-filter@v3
|
||||
id: nondocs
|
||||
with:
|
||||
predicate-quantifier: 'every'
|
||||
filters: |
|
||||
match:
|
||||
- '**'
|
||||
- '!**/*.md'
|
||||
- '!**/*.mdx'
|
||||
|
||||
build:
|
||||
name: 'End to end tests (perf)'
|
||||
needs: check
|
||||
if: needs.check.outputs.relevant == 'true'
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest-16-cores-open
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run our setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Set up Playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
|
||||
- name: Build examples
|
||||
run: yarn run -T lazy refresh-assets && yarn run -T lazy run prebuild && cd apps/examples && yarn build
|
||||
|
||||
- name: Run Playwright tests (perf)
|
||||
run: 'yarn e2e-perf'
|
||||
working-directory: apps/examples
|
||||
env:
|
||||
PERFORMANCE_ANALYTICS_ENABLED: true
|
||||
GITHUB_REF_NAME: ${{ github.ref_name }}
|
||||
GIT_COMMIT: ${{ github.sha }}
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: apps/examples/playwright-report
|
||||
retention-days: 30
|
||||
|
||||
- uses: shallwefootball/s3-upload-action@0d261a6f15b3b2e209dfebdecace4b100c04f95b # master
|
||||
# only upload if we have AWS credentials:
|
||||
env:
|
||||
HAS_AWS_KEY: ${{ secrets.AWS_S3_SECRET_KEY && '1' || '' }}
|
||||
if: always() && env.HAS_AWS_KEY == '1'
|
||||
name: Upload S3
|
||||
id: s3
|
||||
with:
|
||||
aws_key_id: ${{ secrets.AWS_S3_KEY_ID }}
|
||||
aws_secret_access_key: ${{ secrets.AWS_S3_SECRET_KEY}}
|
||||
aws_bucket: playwright.tldraw.xyz
|
||||
source_dir: apps/examples/playwright-report
|
||||
|
||||
- name: Log report to summary
|
||||
if: always()
|
||||
run: |
|
||||
report_url="https://playwright.tldraw.xyz/${{ steps.s3.outputs.object_key }}/index.html"
|
||||
echo "Report: $report_url"
|
||||
echo "## Playwright report" >> $GITHUB_STEP_SUMMARY
|
||||
echo "* $report_url" >> $GITHUB_STEP_SUMMARY
|
||||
84
.github/workflows/playwright-update-snapshots.yml
vendored
Normal file
84
.github/workflows/playwright-update-snapshots.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
# Adapted from https://mmazzarolo.com/blog/2022-09-09-visual-regression-testing-with-playwright-and-github-actions/
|
||||
|
||||
# This workflow's goal is forcing an update of the reference snapshots used
|
||||
# by Playwright tests. Add the 'update-snapshots' label to a PR.
|
||||
# From a high-level perspective, it works like this:
|
||||
# 1. Because of a GitHub Action limitation, this workflow is triggered on every
|
||||
# label added to a PR. We manually interrupt it unless the label is
|
||||
# "update-snapshots".
|
||||
# 2. Use the GitHub API to grab the information about the branch name and SHA of
|
||||
# the latest commit of the current pull request.
|
||||
# 3. Remove the label from the PR.
|
||||
# 4. Update the Playwright reference snapshots based on the UI of this branch.
|
||||
# 5. Commit the newly generated Playwright reference snapshots into this branch.
|
||||
name: Update playwright snapshots
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
|
||||
env:
|
||||
CI: 1
|
||||
PRINT_GITHUB_ANNOTATIONS: 1
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
update_snapshots:
|
||||
name: 'Run'
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest-8-cores-open
|
||||
if: github.event.label.name == 'update-snapshots'
|
||||
|
||||
permissions:
|
||||
# Give the default GITHUB_TOKEN write permission to commit and push the
|
||||
# added or changed files to the repository.
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Remove the update-snapshots label
|
||||
uses: actions-ecosystem/action-remove-labels@v1
|
||||
with:
|
||||
labels: update-snapshots
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 5
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
- name: Run our setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Set up Playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
|
||||
- name: Build examples
|
||||
run: yarn run -T lazy refresh-assets && yarn run -T lazy run prebuild && cd apps/examples && yarn build
|
||||
|
||||
- name: Run Playwright tests & update snapshots
|
||||
run: 'yarn e2e --update-snapshots'
|
||||
working-directory: 'apps/examples'
|
||||
|
||||
- name: Commit and push changes
|
||||
if: always()
|
||||
run: |
|
||||
set -eux
|
||||
git config --global user.name 'huppy-bot[bot]'
|
||||
git config --global user.email '128400622+huppy-bot[bot]@users.noreply.github.com'
|
||||
git add -A
|
||||
if git diff --cached --quiet; then
|
||||
echo 'No snapshot changes to commit.'
|
||||
exit 0
|
||||
fi
|
||||
git commit --no-verify -m '[automated] update snapshots'
|
||||
git add -A && git reset --hard HEAD # we sometimes get a dirty tree because husky makes changes for some reason
|
||||
git pull --rebase
|
||||
git push
|
||||
56
.github/workflows/post-release-crew.yml
vendored
Normal file
56
.github/workflows/post-release-crew.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: Post release crew to Discord
|
||||
|
||||
# Every Wednesday at 07:07 UTC, post to #development naming the people whose critical
|
||||
# fixes / significant features are in this week's dotcom release.
|
||||
# GitHub cron is UTC-only. We use a single fixed UTC time (no BST/GMT adjustment) so
|
||||
# the job never fires twice: 07:07 UTC is 07:07 in London during winter (GMT) and
|
||||
# 08:07 during summer (BST). GitHub often starts scheduled runs 15-60 minutes late,
|
||||
# so this is scheduled early (and off the congested :00/:30 slots) to land before
|
||||
# the working day ramps up. To change the day, edit the day-of-week field
|
||||
# (`3` = Wednesday) in the cron line.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '7 7 * * 3' # Wednesday 07:07 UTC
|
||||
workflow_dispatch: # allow manual runs
|
||||
|
||||
concurrency:
|
||||
group: post-release-crew
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
post-release-crew:
|
||||
name: Post release crew to Discord
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Fail fast if the Discord webhook is not configured
|
||||
run: |
|
||||
if [ -z "$DISCORD_RELEASE_WEBHOOK_URL" ]; then
|
||||
echo "::error::DISCORD_RELEASE_WEBHOOK_URL secret is not set — the release crew message cannot be posted."
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
DISCORD_RELEASE_WEBHOOK_URL: ${{ secrets.DISCORD_RELEASE_WEBHOOK_URL }}
|
||||
|
||||
- name: Install Claude Code
|
||||
run: npm install -g @anthropic-ai/claude-code@2.1.206
|
||||
|
||||
- name: Run dotcom-release-crew skill
|
||||
run: claude --dangerously-skip-permissions --print "/dotcom-release-crew"
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
DISCORD_RELEASE_WEBHOOK_URL: ${{ secrets.DISCORD_RELEASE_WEBHOOK_URL }}
|
||||
57
.github/workflows/post-release-marketing.yml
vendored
Normal file
57
.github/workflows/post-release-marketing.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: Post release marketing summary to Discord
|
||||
|
||||
# Every Wednesday at 07:07 UTC, post a plain-language summary of what's shipping in
|
||||
# this week's dotcom release to the marketing team's Discord channel. Same schedule as
|
||||
# post-release-crew.yml.
|
||||
# GitHub cron is UTC-only. We use a single fixed UTC time (no BST/GMT adjustment) so
|
||||
# the job never fires twice: 07:07 UTC is 07:07 in London during winter (GMT) and
|
||||
# 08:07 during summer (BST). GitHub often starts scheduled runs 15-60 minutes late,
|
||||
# so this is scheduled early (and off the congested :00/:30 slots) to land before
|
||||
# the working day ramps up. To change the day, edit the day-of-week field
|
||||
# (`3` = Wednesday) in the cron line.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '7 7 * * 3' # Wednesday 07:07 UTC
|
||||
workflow_dispatch: # allow manual runs
|
||||
|
||||
concurrency:
|
||||
group: post-release-marketing
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
post-release-marketing:
|
||||
name: Post release marketing summary to Discord
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Fail fast if the Discord webhook is not configured
|
||||
run: |
|
||||
if [ -z "$DISCORD_MARKETING_WEBHOOK_URL" ]; then
|
||||
echo "::error::DISCORD_MARKETING_WEBHOOK_URL secret is not set — the release marketing summary cannot be posted."
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
DISCORD_MARKETING_WEBHOOK_URL: ${{ secrets.DISCORD_MARKETING_WEBHOOK_URL }}
|
||||
|
||||
- name: Install Claude Code
|
||||
run: npm install -g @anthropic-ai/claude-code@2.1.206
|
||||
|
||||
- name: Run dotcom-release-marketing skill
|
||||
run: claude --dangerously-skip-permissions --print "/dotcom-release-marketing"
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
DISCORD_MARKETING_WEBHOOK_URL: ${{ secrets.DISCORD_MARKETING_WEBHOOK_URL }}
|
||||
64
.github/workflows/prune-preview-deploys.yml
vendored
Normal file
64
.github/workflows/prune-preview-deploys.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
name: Prune preview deploys
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# run once per day at midnight or whatever
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
id-token: 'write'
|
||||
|
||||
env:
|
||||
CI: 1
|
||||
PRINT_GITHUB_ANNOTATIONS: 1
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Prune preview deploys
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
environment: deploy-staging
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
|
||||
- name: Setup Fly IO
|
||||
uses: superfly/flyctl-actions/setup-flyctl@ed8efb33836e8b2096c7fd3ba1c8afe303ebbff1 # 1.6
|
||||
|
||||
- name: Configure AWS credentials using OIDC
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::571248596162:role/huppy-bot
|
||||
aws-region: 'eu-north-1'
|
||||
|
||||
- name: Prune preview deploys
|
||||
run: yarn tsx internal/scripts/prune-preview-deploys.ts
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
|
||||
SUPABASE_PREVIEW_PROJECT_ID: ${{ vars.SUPABASE_PREVIEW_PROJECT_ID }}
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||
ZERO_R2_ENDPOINT: ${{ secrets.ZERO_R2_ENDPOINT }}
|
||||
ZERO_R2_BUCKET_NAME: ${{ secrets.ZERO_R2_BUCKET_NAME }}
|
||||
ZERO_R2_ACCESS_KEY_ID: ${{ secrets.ZERO_R2_ACCESS_KEY_ID }}
|
||||
ZERO_R2_SECRET_ACCESS_KEY: ${{ secrets.ZERO_R2_SECRET_ACCESS_KEY }}
|
||||
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
||||
R2_ACCESS_KEY_SECRET: ${{ secrets.R2_ACCESS_KEY_SECRET }}
|
||||
|
||||
- name: Notify Discord on failure
|
||||
if: failure()
|
||||
uses: ./.github/actions/discord-fail-notify
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_DEPLOY_WEBHOOK_URL }}
|
||||
73
.github/workflows/publish-editor-extensions.yml
vendored
Normal file
73
.github/workflows/publish-editor-extensions.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
name: Publish editor extensions
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- production
|
||||
|
||||
env:
|
||||
CI: 1
|
||||
PRINT_GITHUB_ANNOTATIONS: 1
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish editor extensions
|
||||
environment: vsce publish
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
concurrency: vscode-extension-publish
|
||||
|
||||
steps:
|
||||
- name: Generate GH token
|
||||
id: generate_token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ secrets.HUPPY_APP_ID }}
|
||||
private-key: ${{ secrets.HUPPY_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Check out code
|
||||
id: checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
|
||||
- name: Configure git credentials
|
||||
run: git config --global url."https://x-access-token:${{ steps.generate_token.outputs.token }}@github.com/".insteadOf "https://github.com/"
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
|
||||
- name: Build types
|
||||
run: yarn build-types
|
||||
|
||||
- name: Publish extension
|
||||
timeout-minutes: 10
|
||||
run: yarn tsx ./internal/scripts/publish-editor-extensions.ts
|
||||
env:
|
||||
VSCE_PAT: ${{ secrets.VSCE_PAT }}
|
||||
OVSX_PAT: ${{ secrets.OVSX_PAT }}
|
||||
TLDRAW_ENV: ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') }}
|
||||
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
|
||||
# Preserve the setup action for post-job cleanup when checkout already succeeded.
|
||||
- name: Check out Discord notify action
|
||||
if: failure() && steps.checkout.outcome != 'success'
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
sparse-checkout: .github/actions/discord-fail-notify
|
||||
|
||||
- name: Notify Discord on failure
|
||||
if: failure()
|
||||
uses: ./.github/actions/discord-fail-notify
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_DEPLOY_WEBHOOK_URL }}
|
||||
71
.github/workflows/publish-templates.yml
vendored
Normal file
71
.github/workflows/publish-templates.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
name: Publish templates
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
secrets:
|
||||
HUPPY_PRIVATE_KEY:
|
||||
required: true
|
||||
HUPPY_APP_ID:
|
||||
required: true
|
||||
DISCORD_DEPLOY_WEBHOOK_URL:
|
||||
required: false
|
||||
|
||||
env:
|
||||
CI: 1
|
||||
PRINT_GITHUB_ANNOTATIONS: 1
|
||||
ALLOW_REFRESH_ASSETS_CHANGES: 1
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish templates
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest-16-cores-arm-open
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
|
||||
- name: Generate a token
|
||||
id: generate_token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ secrets.HUPPY_APP_ID }}
|
||||
private-key: ${{ secrets.HUPPY_PRIVATE_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config --global user.name 'huppy-bot[bot]'
|
||||
git config --global user.email '128400622+huppy-bot[bot]@users.noreply.github.com'
|
||||
|
||||
- name: Export templates
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
run: |
|
||||
cd templates
|
||||
all_templates=$(ls -d */ | cut -f1 -d'/')
|
||||
cd ..
|
||||
|
||||
for template in $all_templates; do
|
||||
echo "::group::Exporting $template"
|
||||
yarn tsx ./internal/scripts/export-template.ts $template
|
||||
echo "::endgroup::"
|
||||
done
|
||||
|
||||
- name: Notify Discord on failure
|
||||
if: failure()
|
||||
uses: ./.github/actions/discord-fail-notify
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_DEPLOY_WEBHOOK_URL }}
|
||||
70
.github/workflows/publish-vscode-extension.yml
vendored
Normal file
70
.github/workflows/publish-vscode-extension.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: Publish VSCE/OVX
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CI: 1
|
||||
PRINT_GITHUB_ANNOTATIONS: 1
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish VSCE/OVX
|
||||
environment: vsce publish
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
concurrency: vscode-extension-publish
|
||||
|
||||
steps:
|
||||
- name: Generate GH token
|
||||
id: generate_token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ secrets.HUPPY_APP_ID }}
|
||||
private-key: ${{ secrets.HUPPY_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Check out code
|
||||
id: checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
|
||||
- name: Configure git credentials
|
||||
run: git config --global url."https://x-access-token:${{ steps.generate_token.outputs.token }}@github.com/".insteadOf "https://github.com/"
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
|
||||
- name: Build types
|
||||
run: yarn build-types
|
||||
|
||||
- name: Publish extension
|
||||
timeout-minutes: 10
|
||||
run: yarn tsx ./internal/scripts/publish-editor-extensions.ts
|
||||
env:
|
||||
VSCE_PAT: ${{ secrets.VSCE_PAT }}
|
||||
OVSX_PAT: ${{ secrets.OVSX_PAT }}
|
||||
TLDRAW_ENV: production
|
||||
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
|
||||
# Preserve the setup action for post-job cleanup when checkout already succeeded.
|
||||
- name: Check out Discord notify action
|
||||
if: failure() && steps.checkout.outcome != 'success'
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
sparse-checkout: .github/actions/discord-fail-notify
|
||||
|
||||
- name: Notify Discord on failure
|
||||
if: failure()
|
||||
uses: ./.github/actions/discord-fail-notify
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_DEPLOY_WEBHOOK_URL }}
|
||||
330
.github/workflows/publish.yml
vendored
Normal file
330
.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,330 @@
|
||||
name: Publish SDK packages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- production
|
||||
- 'v*.*.x'
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish_type:
|
||||
type: choice
|
||||
description: What to publish
|
||||
required: true
|
||||
default: manual
|
||||
options:
|
||||
- manual
|
||||
- new
|
||||
version:
|
||||
description: Version to republish (manual only)
|
||||
required: false
|
||||
bump_type:
|
||||
type: choice
|
||||
description: Version bump type (new release only)
|
||||
required: false
|
||||
default: minor
|
||||
options:
|
||||
- minor
|
||||
- major
|
||||
- override
|
||||
version_override:
|
||||
type: string
|
||||
description: Version override (new release only)
|
||||
required: false
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
remove-label:
|
||||
name: Remove the publish-packages label
|
||||
if: |
|
||||
github.event_name == 'pull_request' &&
|
||||
github.event.label.name == 'publish-packages' &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Remove the publish-packages label
|
||||
uses: actions-ecosystem/action-remove-labels@v1
|
||||
with:
|
||||
labels: publish-packages
|
||||
|
||||
publish:
|
||||
name: Publish SDK packages
|
||||
if: |
|
||||
github.event_name != 'pull_request' ||
|
||||
(
|
||||
github.event.label.name == 'publish-packages' &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
)
|
||||
environment: npm deploy
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest-16-cores-arm-open
|
||||
concurrency:
|
||||
group: ${{ (github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/heads/v')) && 'npm-publish' || format('npm-publish-{0}', github.run_id) }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
outputs:
|
||||
mode: ${{ steps.publish_mode.outputs.mode }}
|
||||
version: ${{ steps.version.outputs.value }}
|
||||
is_latest_version: ${{ steps.publish.outputs.is_latest_version }}
|
||||
|
||||
steps:
|
||||
- name: Determine publish mode
|
||||
id: publish_mode
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
EVENT_LABEL_NAME: ${{ github.event.label.name }}
|
||||
REF: ${{ github.ref }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
DISPATCH_PUBLISH_TYPE: ${{ inputs.publish_type }}
|
||||
DISPATCH_VERSION: ${{ inputs.version }}
|
||||
DISPATCH_BUMP_TYPE: ${{ inputs.bump_type }}
|
||||
DISPATCH_VERSION_OVERRIDE: ${{ inputs.version_override }}
|
||||
run: |
|
||||
mode=
|
||||
bemo_url=https://demo.tldraw.xyz
|
||||
use_huppy=false
|
||||
|
||||
case "$EVENT_NAME" in
|
||||
push)
|
||||
if [[ "$REF" == "refs/heads/main" ]]; then
|
||||
mode=canary
|
||||
bemo_url=https://canary-demo.tldraw.xyz
|
||||
elif [[ "$REF" == "refs/heads/production" ]]; then
|
||||
mode=next
|
||||
bemo_url=https://canary-demo.tldraw.xyz
|
||||
elif [[ "$REF_NAME" =~ ^v[0-9]+\.[0-9]+\.x$ ]]; then
|
||||
mode=patch
|
||||
use_huppy=true
|
||||
fi
|
||||
;;
|
||||
pull_request)
|
||||
if [[ "$EVENT_LABEL_NAME" == "publish-packages" ]]; then
|
||||
mode=internal
|
||||
bemo_url=https://canary-demo.tldraw.xyz
|
||||
fi
|
||||
;;
|
||||
workflow_dispatch)
|
||||
mode="$DISPATCH_PUBLISH_TYPE"
|
||||
if [[ "$mode" == "manual" || "$mode" == "new" ]]; then
|
||||
use_huppy=true
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -z "$mode" ]]; then
|
||||
echo "ERROR Could not determine publish mode for $EVENT_NAME on $REF."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$mode" == "manual" && -z "$DISPATCH_VERSION" ]]; then
|
||||
echo "ERROR You must provide a version when manually republishing packages."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$mode" == "new" ]]; then
|
||||
if [[ "$REF_NAME" != "production" ]]; then
|
||||
echo "ERROR You must be on the production branch to publish a new version."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$DISPATCH_BUMP_TYPE" != "minor" && "$DISPATCH_BUMP_TYPE" != "major" && "$DISPATCH_BUMP_TYPE" != "override" ]]; then
|
||||
echo "ERROR New releases must use a minor, major, or override bump type."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$DISPATCH_BUMP_TYPE" != "override" && -n "$DISPATCH_VERSION_OVERRIDE" ]]; then
|
||||
echo "ERROR You must set the bump type to override if you supply a custom version number override."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$DISPATCH_BUMP_TYPE" == "override" && -z "$DISPATCH_VERSION_OVERRIDE" ]]; then
|
||||
echo "ERROR You must supply a version override when using the override bump type."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
{
|
||||
echo "mode=$mode"
|
||||
echo "bemo_url=$bemo_url"
|
||||
echo "use_huppy=$use_huppy"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Generate GH token
|
||||
id: generate_token
|
||||
if: steps.publish_mode.outputs.use_huppy == 'true'
|
||||
uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92
|
||||
with:
|
||||
app_id: ${{ secrets.HUPPY_APP_ID }}
|
||||
private_key: ${{ secrets.HUPPY_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Check out code
|
||||
id: checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: ${{ steps.publish_mode.outputs.use_huppy != 'true' }}
|
||||
token: ${{ (steps.publish_mode.outputs.use_huppy == 'true' && steps.generate_token.outputs.token) || github.token }}
|
||||
|
||||
- name: Configure git credentials
|
||||
if: steps.publish_mode.outputs.use_huppy == 'true'
|
||||
run: |
|
||||
git config --global url."https://x-access-token:${{ steps.generate_token.outputs.token }}@github.com/".insteadOf "https://github.com/"
|
||||
git config --global user.name 'huppy-bot[bot]'
|
||||
git config --global user.email '128400622+huppy-bot[bot]@users.noreply.github.com'
|
||||
|
||||
- name: Run our setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Publish
|
||||
id: publish
|
||||
env:
|
||||
GH_TOKEN: ${{ (steps.publish_mode.outputs.use_huppy == 'true' && steps.generate_token.outputs.token) || github.token }}
|
||||
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
||||
R2_ACCESS_KEY_SECRET: ${{ secrets.R2_ACCESS_KEY_SECRET }}
|
||||
TLDRAW_BEMO_URL: ${{ steps.publish_mode.outputs.bemo_url }}
|
||||
TLDRAW_VERSION_STRING: ${{ inputs.version }}
|
||||
PUBLISH_MODE: ${{ steps.publish_mode.outputs.mode }}
|
||||
BUMP_TYPE: ${{ inputs.bump_type }}
|
||||
VERSION_OVERRIDE: ${{ inputs.version_override }}
|
||||
run: |
|
||||
case "$PUBLISH_MODE" in
|
||||
canary | internal | next)
|
||||
yarn tsx ./internal/scripts/publish-prerelease.ts "$PUBLISH_MODE"
|
||||
;;
|
||||
patch)
|
||||
yarn tsx ./internal/scripts/publish-patch.ts
|
||||
;;
|
||||
manual)
|
||||
yarn tsx ./internal/scripts/publish-manual.ts
|
||||
;;
|
||||
new)
|
||||
bump="${BUMP_TYPE:-minor}"
|
||||
if [[ "$bump" == "override" ]]; then
|
||||
bump="$VERSION_OVERRIDE"
|
||||
fi
|
||||
yarn tsx ./internal/scripts/publish-new.ts --bump "$bump"
|
||||
;;
|
||||
*)
|
||||
echo "ERROR Unknown publish mode: $PUBLISH_MODE"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Get published version
|
||||
id: version
|
||||
run: echo "value=$(jq -r .version ./packages/tldraw/package.json)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Tell tldraw-internal to update its pinned SDK version. Fires only on a
|
||||
# `next` publish — the prerelease channel the internal monorepo tracks.
|
||||
# The tldraw-internal "Update tldraw" workflow listens for the
|
||||
# `tldraw-release` repository_dispatch and resolves the latest `next`
|
||||
# version itself, so the payload version is informational.
|
||||
- name: Generate tldraw-internal token
|
||||
if: steps.publish_mode.outputs.mode == 'next'
|
||||
id: generate_internal_token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ vars.HUPPY_APP_ID }}
|
||||
private-key: ${{ secrets.HUPPY_PRIVATE_KEY }}
|
||||
owner: tldraw
|
||||
repositories: tldraw-internal
|
||||
|
||||
- name: Notify tldraw-internal
|
||||
if: steps.publish_mode.outputs.mode == 'next'
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${{ steps.generate_internal_token.outputs.token }}" \
|
||||
https://api.github.com/repos/tldraw/tldraw-internal/dispatches \
|
||||
-d '{"event_type":"tldraw-release","client_payload":{"version":"${{ steps.version.outputs.value }}","tag":"next"}}'
|
||||
|
||||
# Preserve the setup action for post-job cleanup when checkout already succeeded.
|
||||
- name: Check out Discord notify action
|
||||
if: failure() && steps.checkout.outcome != 'success'
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
sparse-checkout: .github/actions/discord-fail-notify
|
||||
|
||||
- name: Notify Discord on failure
|
||||
if: failure()
|
||||
uses: ./.github/actions/discord-fail-notify
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_DEPLOY_WEBHOOK_URL }}
|
||||
|
||||
comment:
|
||||
name: Tell user released version
|
||||
if: needs.publish.outputs.mode == 'internal'
|
||||
needs: publish
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Tell user released version
|
||||
uses: actions-ecosystem/action-create-comment@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
body: |
|
||||
Published version ${{ needs.publish.outputs.version }} to npm.
|
||||
|
||||
publish_templates:
|
||||
name: Publishes code templates to separate repositories
|
||||
uses: tldraw/tldraw/.github/workflows/publish-templates.yml@main
|
||||
if: needs.publish.outputs.mode == 'new' || ((needs.publish.outputs.mode == 'patch' || needs.publish.outputs.mode == 'manual') && needs.publish.outputs.is_latest_version == 'true')
|
||||
secrets:
|
||||
HUPPY_PRIVATE_KEY: ${{ secrets.HUPPY_PRIVATE_KEY }}
|
||||
HUPPY_APP_ID: ${{ vars.HUPPY_APP_ID }}
|
||||
DISCORD_DEPLOY_WEBHOOK_URL: ${{ secrets.DISCORD_DEPLOY_WEBHOOK_URL }}
|
||||
needs: publish
|
||||
|
||||
deploy_templates:
|
||||
name: Deploys the template demos with the new SDK version
|
||||
if: needs.publish.outputs.mode == 'new' || ((needs.publish.outputs.mode == 'patch' || needs.publish.outputs.mode == 'manual') && needs.publish.outputs.is_latest_version == 'true')
|
||||
needs: publish
|
||||
# CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID are secrets on this environment rather than on
|
||||
# the repository, and a job has to select the environment to see them. Calling deploy-templates.yml
|
||||
# as a reusable workflow doesn't do it: a called workflow only ever sees the secrets its caller
|
||||
# hands it, so the v5.4.0 release reached the deploy with both empty (#10636) even though the
|
||||
# called job selected deploy-production itself. The deploy runs here as a job of its own instead,
|
||||
# sharing its steps with deploy-templates.yml through the composite action.
|
||||
environment: deploy-production
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest-16-cores-arm-open
|
||||
# same group as deploy-templates.yml: a release deploy and a template-change deploy can otherwise
|
||||
# race each other onto the same custom domains, and the loser silently wins.
|
||||
concurrency:
|
||||
group: deploy-templates
|
||||
cancel-in-progress: false
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: ./.github/actions/deploy-templates
|
||||
with:
|
||||
license-key: ${{ secrets.TEMPLATES_TLDRAW_LICENSE_KEY }}
|
||||
cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
|
||||
- name: Notify Discord on failure
|
||||
if: failure()
|
||||
uses: ./.github/actions/discord-fail-notify
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_DEPLOY_WEBHOOK_URL }}
|
||||
38
.github/workflows/staging-cleanup-daily.yml
vendored
Normal file
38
.github/workflows/staging-cleanup-daily.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Staging E2E Daily Cleanup
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '45 1 * * *'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CI: 1
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
name: 'Daily Cleanup of Test Files'
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest
|
||||
environment: 'e2e-dotcom'
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run our setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Cleanup test files
|
||||
run: yarn tsx internal/scripts/dotcom/cleanup-test-files.ts
|
||||
env:
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
||||
R2_ACCESS_KEY_SECRET: ${{ secrets.R2_ACCESS_KEY_SECRET }}
|
||||
SUPABASE_STAGING_DB_URL: ${{ secrets.SUPABASE_STAGING_DB_URL }}
|
||||
STAGING_OWNER_ID: ${{ secrets.STAGING_OWNER_ID }}
|
||||
68
.github/workflows/staging-e2e.yml
vendored
Normal file
68
.github/workflows/staging-e2e.yml
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
name: Staging E2E Tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
test_files_count:
|
||||
description: 'Number of test files to copy to staging'
|
||||
required: false
|
||||
default: '10'
|
||||
type: number
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CI: 1
|
||||
PRINT_GITHUB_ANNOTATIONS: 1
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: 'Staging E2E Tests'
|
||||
timeout-minutes: 40
|
||||
runs-on: ubuntu-latest-8-cores-open
|
||||
environment: 'e2e-dotcom'
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run our setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Set up Playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
|
||||
- name: Run cleanup of test files
|
||||
run: yarn tsx internal/scripts/dotcom/cleanup-test-files.ts
|
||||
env:
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
||||
R2_ACCESS_KEY_SECRET: ${{ secrets.R2_ACCESS_KEY_SECRET }}
|
||||
SUPABASE_STAGING_DB_URL: ${{ secrets.SUPABASE_STAGING_DB_URL }}
|
||||
STAGING_OWNER_ID: ${{ secrets.STAGING_OWNER_ID }}
|
||||
|
||||
- name: Copy random files to staging
|
||||
run: yarn tsx internal/scripts/dotcom/copy-random-files-to-staging.ts
|
||||
env:
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
||||
R2_ACCESS_KEY_SECRET: ${{ secrets.R2_ACCESS_KEY_SECRET }}
|
||||
SUPABASE_PRODUCTION_DB_URL: ${{ secrets.SUPABASE_PRODUCTION_DB_URL }}
|
||||
SUPABASE_STAGING_DB_URL: ${{ secrets.SUPABASE_STAGING_DB_URL }}
|
||||
STAGING_OWNER_ID: ${{ secrets.STAGING_OWNER_ID }}
|
||||
STAGING_TEST_FILES_COUNT: ${{ github.event.inputs.test_files_count || '10' }}
|
||||
|
||||
- name: Run staging e2e tests
|
||||
run: yarn e2e-staging
|
||||
working-directory: apps/dotcom/client
|
||||
env:
|
||||
CLERK_PUBLISHABLE_KEY: ${{ secrets.VITE_CLERK_PUBLISHABLE_KEY }}
|
||||
CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }}
|
||||
STAGING_TEST_EMAIL: ${{ secrets.STAGING_TEST_EMAIL }}
|
||||
STAGING_TEST_PASSWORD: ${{ secrets.STAGING_TEST_PASSWORD }}
|
||||
STAGING_TESTS: 'true'
|
||||
69
.github/workflows/trigger-dotcom-hotfix.yml
vendored
Normal file
69
.github/workflows/trigger-dotcom-hotfix.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: Trigger dotcom hotfix
|
||||
|
||||
# pull_request_target rather than push/pull_request, for the same reason as
|
||||
# trigger-sdk-hotfix.yml: release-notes PRs squash-merge with `[skip ci]` in the title, which
|
||||
# suppresses the push-to-main run entirely, and the `labeled` event fires before the merge so the
|
||||
# `merged == true` gate rejects it. Neither path ever reached the script. GitHub's skip-ci markers
|
||||
# do not apply to pull_request_target. `closed` replaces the old push trigger (which ran on every
|
||||
# merge to main just to check labels), and `labeled` allows re-triggering by re-adding the label to
|
||||
# an already-merged PR. Secrets exposure is safe here: the job only runs for merged PRs carrying a
|
||||
# maintainer-applied hotfix label, checkout uses the trusted base branch, and the only PR-derived
|
||||
# code the script builds is the PR's already-merged commit from main.
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [closed, labeled]
|
||||
branches:
|
||||
- main
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
trigger:
|
||||
name: Trigger a dotcom hotfix
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
concurrency: dotcom-hotfix
|
||||
environment: npm deploy
|
||||
if: |
|
||||
github.event.pull_request.merged == true &&
|
||||
((github.event.action == 'closed' &&
|
||||
contains(github.event.pull_request.labels.*.name, 'dotcom-hotfix-please')) ||
|
||||
(github.event.action == 'labeled' &&
|
||||
github.event.label.name == 'dotcom-hotfix-please'))
|
||||
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate_token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ secrets.HUPPY_APP_ID }}
|
||||
private-key: ${{ secrets.HUPPY_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure git credentials
|
||||
run: git config --global url."https://x-access-token:${{ steps.generate_token.outputs.token }}@github.com/".insteadOf "https://github.com/"
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
|
||||
- name: Maybe trigger dotcom hotfix
|
||||
run: |
|
||||
set -e
|
||||
git config --global user.name 'huppy-bot[bot]'
|
||||
git config --global user.email '128400622+huppy-bot[bot]@users.noreply.github.com'
|
||||
git log --oneline -10
|
||||
yarn tsx internal/scripts/trigger-dotcom-hotfix.ts
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
DISCORD_DEPLOY_WEBHOOK_URL: ${{ secrets.DISCORD_DEPLOY_WEBHOOK_URL }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
118
.github/workflows/trigger-production-build.yml
vendored
Normal file
118
.github/workflows/trigger-production-build.yml
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
name: Trigger dotcom release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- hotfixes
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
target:
|
||||
description: 'Target ref to deploy'
|
||||
required: true
|
||||
default: 'main'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
TARGET: ${{ github.event.inputs.target }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
trigger:
|
||||
name: ${{ github.event_name == 'workflow_dispatch' && 'Manual trigger' || 'Hotfix' }}
|
||||
runs-on: ubuntu-latest
|
||||
concurrency: trigger-production
|
||||
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate_token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ vars.HUPPY_APP_ID }}
|
||||
private-key: ${{ secrets.HUPPY_PRIVATE_KEY }}
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
ref: refs/heads/production
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure git credentials
|
||||
run: git config --global url."https://x-access-token:${{ steps.generate_token.outputs.token }}@github.com/".insteadOf "https://github.com/"
|
||||
|
||||
- name: Get target commit hash (manual dispatch)
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
set -x
|
||||
|
||||
# if the target exists on its own use that
|
||||
if git rev-parse "$TARGET" --quiet; then
|
||||
target_hash=$(git rev-parse "$TARGET")
|
||||
fi
|
||||
# if not try prefixed with origin:
|
||||
if [ -z "$target_hash" ]; then
|
||||
target_hash=$(git rev-parse "origin/$TARGET")
|
||||
fi
|
||||
|
||||
echo "SHOULD_DEPLOY=true" >> $GITHUB_ENV
|
||||
echo "TARGET_HASH=$target_hash" >> $GITHUB_ENV
|
||||
|
||||
- name: Get target commit hash (hotfix)
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
set -x
|
||||
echo "TARGET_HASH=$GITHUB_SHA" >> $GITHUB_ENV
|
||||
echo "TARGET=hotfix" >> $GITHUB_ENV
|
||||
# is the hotfix sha already on production?
|
||||
if git merge-base --is-ancestor "$GITHUB_SHA" production; then
|
||||
echo "SHOULD_DEPLOY=false" >> $GITHUB_ENV
|
||||
else
|
||||
echo "SHOULD_DEPLOY=true" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Author setup (manual dispatch)
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
set -x
|
||||
git config --global user.name "${{ github.actor }}"
|
||||
git config --global user.email 'huppy+${{ github.actor }}@tldraw.com'
|
||||
|
||||
- name: Author setup (hotfix)
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
set -x
|
||||
commit_author_name=$(git log -1 --pretty=format:%cn "$TARGET_HASH")
|
||||
commit_author_email=$(git log -1 --pretty=format:%ce "$TARGET_HASH")
|
||||
git config --global user.name "$commit_author_name"
|
||||
git config --global user.email "$commit_author_email"
|
||||
|
||||
- name: Get target tree hash
|
||||
run: |
|
||||
set -x
|
||||
tree_hash=$(git show --quiet --pretty=format:%T "$TARGET_HASH")
|
||||
echo "TREE_HASH=$tree_hash" >> $GITHUB_ENV
|
||||
|
||||
- name: Create commit & update production branch
|
||||
run: |
|
||||
set -eux
|
||||
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
message="Deploy from $TARGET ($TARGET_HASH) at $now"
|
||||
current_prod_hash=$(git rev-parse HEAD)
|
||||
|
||||
commit=$(git commit-tree -m "$message" -p "$current_prod_hash" -p "$TARGET_HASH" "$TREE_HASH")
|
||||
|
||||
git update-ref refs/heads/production "$commit"
|
||||
git checkout production
|
||||
|
||||
- name: Push commit to trigger deployment
|
||||
if: env.SHOULD_DEPLOY == 'true'
|
||||
run: |
|
||||
set -x
|
||||
git push origin production
|
||||
# reset hotfixes to the latest production
|
||||
git push origin production:hotfixes --force
|
||||
70
.github/workflows/trigger-sdk-hotfix.yml
vendored
Normal file
70
.github/workflows/trigger-sdk-hotfix.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: Trigger sdk hotfix
|
||||
|
||||
# pull_request_target rather than push/pull_request: hotfix PRs (e.g. the automated
|
||||
# release-notes updates) carry `[skip ci]` in their commit messages, which suppresses
|
||||
# push- and pull_request-triggered workflows entirely — the label had no effect.
|
||||
# GitHub's skip-ci markers do not apply to pull_request_target. The `closed` event
|
||||
# replaces the old push-to-main trigger (which ran on every merge just to check
|
||||
# labels), and `labeled` allows re-triggering a hotfix by re-adding the label to an
|
||||
# already-merged PR. Secrets exposure is safe here: the job only runs for merged PRs
|
||||
# carrying a maintainer-applied hotfix label, checkout uses the trusted base branch,
|
||||
# and the only PR-derived code the script builds is the PR's already-merged commit
|
||||
# from main.
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [closed, labeled]
|
||||
branches:
|
||||
- main
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
trigger:
|
||||
name: Trigger an SDK hotfix
|
||||
runs-on: ubuntu-latest
|
||||
concurrency: npm-publish
|
||||
environment: npm deploy
|
||||
if: |
|
||||
github.event.pull_request.merged == true &&
|
||||
((github.event.action == 'closed' &&
|
||||
(contains(github.event.pull_request.labels.*.name, 'sdk-hotfix-please') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'docs-hotfix-please'))) ||
|
||||
(github.event.action == 'labeled' &&
|
||||
(github.event.label.name == 'sdk-hotfix-please' || github.event.label.name == 'docs-hotfix-please')))
|
||||
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate_token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ secrets.HUPPY_APP_ID }}
|
||||
private-key: ${{ secrets.HUPPY_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure git credentials
|
||||
run: git config --global url."https://x-access-token:${{ steps.generate_token.outputs.token }}@github.com/".insteadOf "https://github.com/"
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
|
||||
- name: Maybe trigger sdk hotfix
|
||||
run: |
|
||||
set -e
|
||||
git config --global user.name 'huppy-bot[bot]'
|
||||
git config --global user.email '128400622+huppy-bot[bot]@users.noreply.github.com'
|
||||
git log --oneline -10
|
||||
yarn tsx internal/scripts/trigger-sdk-hotfix.ts
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
TLDRAW_BEMO_URL: https://demo.tldraw.xyz
|
||||
DISCORD_DEPLOY_WEBHOOK_URL: ${{ secrets.DISCORD_DEPLOY_WEBHOOK_URL }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
160
.github/workflows/update-release-notes.yml
vendored
Normal file
160
.github/workflows/update-release-notes.yml
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
name: Update release notes
|
||||
|
||||
on:
|
||||
# Update release notes whenever production is updated, keeping next.mdx (and the
|
||||
# draft GitHub release synced by the skill) in step with each dotcom release —
|
||||
# the same production push that deploys dotcom and publishes the `next` package.
|
||||
# Production only: a push to main must not trigger a notes run on every merge.
|
||||
# Release-notes PR titles carry `[skip ci]` so their squash-merge commits skip main
|
||||
# push workflows. Branch commits omit the marker so required PR checks run.
|
||||
push:
|
||||
branches:
|
||||
- production
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: update-release-notes
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
update-release-notes:
|
||||
name: 'Update release notes with Claude Code'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Generate GH token
|
||||
id: generate_token
|
||||
uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92
|
||||
with:
|
||||
app_id: ${{ secrets.HUPPY_APP_ID }}
|
||||
private_key: ${{ secrets.HUPPY_PRIVATE_KEY }}
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
|
||||
- name: Configure git credentials
|
||||
run: git config --global url."https://x-access-token:${{ steps.generate_token.outputs.token }}@github.com/".insteadOf "https://github.com/"
|
||||
|
||||
- name: Run our setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config --global user.name 'huppy-bot[bot]'
|
||||
git config --global user.email '128400622+huppy-bot[bot]@users.noreply.github.com'
|
||||
|
||||
- name: Create branch
|
||||
id: branch
|
||||
run: |
|
||||
BRANCH_NAME="release-notes/update-$(date +%Y%m%d-%H%M%S)"
|
||||
git checkout -b "$BRANCH_NAME"
|
||||
echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Recorded before the skill runs so the close step below can tell an
|
||||
# already-open PR from an earlier run apart from the one this run opens.
|
||||
- name: Find open release notes PRs
|
||||
id: existing_prs
|
||||
run: |
|
||||
{
|
||||
echo 'numbers<<PR_LIST_EOF'
|
||||
gh pr list --state open --base main --limit 100 --json number,headRefName \
|
||||
--jq '.[] | select(.headRefName | test("^(release-notes/update-|update-release-notes-)")) | .number'
|
||||
echo 'PR_LIST_EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
|
||||
- name: Install Claude Code
|
||||
run: npm install -g @anthropic-ai/claude-code
|
||||
|
||||
- name: Run update-release-notes skill
|
||||
run: |
|
||||
claude --dangerously-skip-permissions --print "/update-release-notes"
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
|
||||
- name: Check for changes
|
||||
id: changes
|
||||
run: |
|
||||
if [ -z "$(git status --porcelain)" ]; then
|
||||
echo "has_changes=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "has_changes=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Commit and push
|
||||
if: steps.changes.outputs.has_changes == 'true'
|
||||
run: |
|
||||
git add -A
|
||||
git commit -m "Update release notes"
|
||||
git push -u origin "${{ steps.branch.outputs.branch_name }}"
|
||||
|
||||
- name: Create PR
|
||||
if: steps.changes.outputs.has_changes == 'true'
|
||||
run: |
|
||||
gh pr create \
|
||||
--title "docs(releases): update release notes [skip ci]" \
|
||||
--body "$(cat <<'EOF'
|
||||
In order to keep the release notes up to date, this PR adds entries for PRs merged since the last update.
|
||||
|
||||
### Change type
|
||||
|
||||
- [x] `other`
|
||||
|
||||
### Test plan
|
||||
|
||||
- [ ] Unit tests
|
||||
- [ ] End to end tests
|
||||
EOF
|
||||
)" \
|
||||
--base main \
|
||||
--head "${{ steps.branch.outputs.branch_name }}" \
|
||||
--label "docs-hotfix-please"
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
|
||||
# Each run regenerates the notes from scratch, so a PR opened by this run
|
||||
# supersedes any release notes PR left open by an earlier run. The PR may come
|
||||
# from the step above or from the skill itself, so this compares the currently
|
||||
# open PRs against the ones recorded before the skill ran rather than relying on
|
||||
# a single known PR number. When this run opened nothing, the older PR is still
|
||||
# the pending update and is left alone.
|
||||
- name: Close superseded PRs
|
||||
run: |
|
||||
EXISTING="${{ steps.existing_prs.outputs.numbers }}"
|
||||
CURRENT=$(gh pr list --state open --base main --limit 100 --json number,headRefName \
|
||||
--jq '.[] | select(.headRefName | test("^(release-notes/update-|update-release-notes-)")) | .number')
|
||||
|
||||
NEW_PRS=$(comm -13 <(echo "$EXISTING" | sort) <(echo "$CURRENT" | sort))
|
||||
if [ -z "$NEW_PRS" ]; then
|
||||
echo "This run did not open a release notes PR, nothing to supersede"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# The new PR is already open at this point, so a failure to tidy up an old
|
||||
# one is a warning, not a reason to fail the run.
|
||||
NEWEST=$(echo "$NEW_PRS" | sort -n | tail -n 1)
|
||||
echo "$EXISTING" | while read -r PR; do
|
||||
[ -n "$PR" ] || continue
|
||||
echo "Closing superseded release notes PR #$PR"
|
||||
gh pr close "$PR" --delete-branch --comment "Superseded by #$NEWEST." \
|
||||
|| echo "Warning: could not close #$PR"
|
||||
done
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
|
||||
- name: No changes
|
||||
if: steps.changes.outputs.has_changes == 'false'
|
||||
run: echo "No release notes updates needed"
|
||||
Reference in New Issue
Block a user