diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3e99c2602..35e7f5b96 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,17 +2,25 @@ Describe what your pull request does. If appropriate, add GIFs or images showing ### Change Type -- [ ] `patch` — Bug fix -- [ ] `minor` — New feature -- [ ] `major` — Breaking change -- [ ] `dependencies` — Changes to package dependencies[^1] -- [ ] `documentation` — Changes to the documentation only[^2] -- [ ] `tests` — Changes to any test code only[^2] -- [ ] `internal` — Any other changes that don't affect the published package[^2] -- [ ] I don't know + + +- [ ] `sdk` — Changes the tldraw SDK +- [ ] `dotcom` — Changes the tldraw.com web app +- [ ] `docs` — Changes to the documentation, examples, or templates. +- [ ] `vs code` — Changes to the vscode plugin +- [ ] `internal` — Does not affect user-facing stuff + + + +- [ ] `bugfix` — Bug fix +- [ ] `feature` — New feature +- [ ] `improvement` — Improving existing features +- [ ] `chore` — Updating dependencies, other boring stuff +- [ ] `galaxy brain` — Architectural changes +- [ ] `tests` — Changes to any test code +- [ ] `tools` — Changes to infrastructure, CI, internal scripts, debugging tools, etc. +- [ ] `dunno` — I don't know -[^1]: publishes a `patch` release, for devDependencies use `internal` -[^2]: will not publish a new version ### Test Plan diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 780e08332..8435dc2f5 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -47,6 +47,9 @@ jobs: - name: Check tsconfigs run: yarn check-tsconfigs + - name: Check PR template + run: yarn update-pr-template --check + - name: Lint run: yarn lint diff --git a/apps/huppy/README.md b/apps/huppy/README.md index 91a71a655..bbe38acc8 100644 --- a/apps/huppy/README.md +++ b/apps/huppy/README.md @@ -13,8 +13,8 @@ REPO_SYNC_HOOK_SECRET= DM alex to get hold of these credentials. -To start the server, run `yarn dev-repo-sync`. Once running, you can go to -https://localhost:3000/deliveries to get to a list of github webhook event +To start the server, run `yarn dev-huppy`. Once running, you can go to +http://localhost:3000/deliveries to get to a list of github webhook event deliveries. To test your code, pick an event that does roughly what you want and hit 'simulate'. You can also ask GitHub to re-deliver events to the production version of repo-sync through this UI. @@ -22,7 +22,7 @@ version of repo-sync through this UI. Huppy-bot isn't currently deployed automatically. To deploy, use: ```sh -fly deploy --config apps/repo-sync/fly.toml --dockerfile apps/repo-sync/Dockerfile +fly deploy --config apps/huppy/fly.toml --dockerfile apps/huppy/Dockerfile ``` from the repo root. diff --git a/apps/huppy/src/flows/enforcePrLabels.tsx b/apps/huppy/src/flows/enforcePrLabels.tsx index dd760902f..c73092abc 100644 --- a/apps/huppy/src/flows/enforcePrLabels.tsx +++ b/apps/huppy/src/flows/enforcePrLabels.tsx @@ -39,81 +39,34 @@ export const enforcePrLabels: Flow = { return await succeed('Closed PR, skipping label check') } - const currentReleaseLabels = pull.labels - .map((label) => label.name) - .filter((label) => VALID_LABELS.includes(label)) - - if (currentReleaseLabels.length > 1 && !allHaveSameBumpType(currentReleaseLabels)) { - return fail(`PR has multiple release labels: ${currentReleaseLabels.join(', ')}`) - } + const availableLabels = ( + await ctx.octokit.rest.issues.listLabelsForRepo({ + owner: event.repository.owner.login, + repo: event.repository.name, + }) + ).data.map((x) => x.name) const prBody = pull.body - const selectedReleaseLabels = VALID_LABELS.filter((label) => + const selectedReleaseLabels = availableLabels.filter((label) => prBody?.match(new RegExp(`^\\s*?-\\s*\\[\\s*x\\s*\\]\\s+\`${label}\``, 'm')) - ) as (keyof typeof LABEL_TYPES)[] + ) as string[] - if (selectedReleaseLabels.length > 1 && !allHaveSameBumpType(selectedReleaseLabels)) { - return await fail( - `PR has multiple checked labels: ${selectedReleaseLabels.join( - ', ' - )}. Please select only one` - ) - } - - const [current] = currentReleaseLabels - const [selected] = selectedReleaseLabels - - if (!current && !selected) { - return await fail( - `Please assign one of the following release labels to this PR: ${VALID_LABELS.join(', ')}` - ) - } - - if (current === selected || (current && !selected)) { - return succeed(`PR has label: ${current}`) - } - - // otherwise the label has changed or is being set for the first time - // from the pr body - if (current) { - await ctx.octokit.rest.issues.removeLabel({ - issue_number: event.number, - name: current, - owner: 'ds300', - repo: 'lazyrepo', - }) + if (selectedReleaseLabels.length === 0 && pull.labels.length === 0) { + return fail('Please add a label to the PR.') } + // add any labels that are checked console.log('adding labels') - await ctx.octokit.rest.issues.addLabels({ - issue_number: pull.number, - owner: event.repository.organization ?? event.repository.owner.login, - repo: event.repository.name, - labels: [selected], - } as any) + if (selectedReleaseLabels.length > 0) { + await ctx.octokit.rest.issues.addLabels({ + issue_number: pull.number, + owner: event.repository.organization ?? event.repository.owner.login, + repo: event.repository.name, + labels: selectedReleaseLabels, + } as any) + } - return await succeed(`PR label set to: ${selected}`) + return await succeed(`PR is labelled!`) }, } - -const LABEL_TYPES = { - tests: 'none', - internal: 'none', - documentation: 'none', - dependencies: 'patch', - major: 'major', - minor: 'minor', - patch: 'patch', -} - -const VALID_LABELS = Object.keys(LABEL_TYPES) - -function allHaveSameBumpType(labels: string[]) { - const [first] = labels - return labels.every( - (label) => - LABEL_TYPES[label as keyof typeof LABEL_TYPES] === - LABEL_TYPES[first as keyof typeof LABEL_TYPES] - ) -} diff --git a/package.json b/package.json index be9ae6468..4e540c10d 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "typecheck": "yarn refresh-assets && tsx scripts/typecheck.ts", "check-scripts": "tsx scripts/check-scripts.ts", "check-tsconfigs": "tsx scripts/check-tsconfigs.ts", + "update-pr-template": "tsx scripts/update-pr-template.ts", "api-check": "lazy api-check", "test-ci": "lazy test-ci", "test": "lazy test", diff --git a/scripts/lib/labels.ts b/scripts/lib/labels.ts new file mode 100644 index 000000000..fa51ad5c4 --- /dev/null +++ b/scripts/lib/labels.ts @@ -0,0 +1,98 @@ +import { join } from 'path' +import { REPO_ROOT, writeJsonFile } from './file' + +type Label = { + // this is what the label is 'called' on github + name: string + // this is how we describe the label in our pull request template + description: string + // this is the section title for the label in our changelogs + changelogTitle: string +} + +const SCOPE_LABELS = [ + { + name: `sdk`, + description: `Changes the tldraw SDK`, + changelogTitle: '📚 SDK Changes', + }, + { + name: `dotcom`, + description: `Changes the tldraw.com web app`, + changelogTitle: '🖥️ tldraw.com Changes', + }, + { + name: `docs`, + description: `Changes to the documentation, examples, or templates.`, + changelogTitle: '📖 Documentation changes', + }, + { + name: `vs code`, + description: `Changes to the vscode plugin`, + changelogTitle: '👩‍💻 VS Code Plugin Changes', + }, + { + name: `internal`, + description: `Does not affect user-facing stuff`, + changelogTitle: '🕵️‍♀️ Internal Changes', + }, +] as const satisfies Label[] + +const TYPE_LABELS = [ + { name: `bugfix`, description: `Bug fix`, changelogTitle: '🐛 Bug Fixes' }, + { name: `feature`, description: `New feature`, changelogTitle: '🚀 Features' }, + { + name: `improvement`, + description: `Improving existing features`, + changelogTitle: '💄 Improvements', + }, + { + name: `chore`, + description: `Updating dependencies, other boring stuff`, + changelogTitle: '🧹 Chores', + }, + { + name: `galaxy brain`, + description: `Architectural changes`, + changelogTitle: '🤯 Architectural changes', + }, + { name: `tests`, description: `Changes to any test code`, changelogTitle: '🧪 Tests' }, + { + name: `tools`, + description: `Changes to infrastructure, CI, internal scripts, debugging tools, etc.`, + changelogTitle: '🛠️ Tools', + }, + { name: `dunno`, description: `I don't know`, changelogTitle: '🤷 Dunno' }, +] as const satisfies Label[] + +export function getLabelNames() { + return [...SCOPE_LABELS, ...TYPE_LABELS].map((label) => label.name) +} + +function formatTemplateOption(label: Label) { + return `- [ ] \`${label.name}\` — ${label.description}` +} + +export function formatLabelOptionsForPRTemplate() { + let result = `\n\n` + for (const label of SCOPE_LABELS) { + result += formatTemplateOption(label) + '\n' + } + result += `\n\n\n` + for (const label of TYPE_LABELS) { + result += formatTemplateOption(label) + '\n' + } + return result +} + +export async function generateAutoRcFile() { + const autoRcPath = join(REPO_ROOT, '.autorc') + await writeJsonFile(autoRcPath, { + plugins: ['npm'], + labels: [...SCOPE_LABELS, ...TYPE_LABELS].map(({ name, changelogTitle }) => ({ + name, + changelogTitle, + releaseType: 'none', + })), + }) +} diff --git a/scripts/package.json b/scripts/package.json index ccaecde84..7d950bc6f 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -54,6 +54,7 @@ "lint": "yarn run -T tsx lint.ts" }, "dependencies": { + "@octokit/rest": "^20.0.2", "@types/minimist": "^1.2.5", "ignore": "^5.2.4", "minimist": "^1.2.8", diff --git a/scripts/publish-new.ts b/scripts/publish-new.ts index 0ea0e0dca..41f212003 100644 --- a/scripts/publish-new.ts +++ b/scripts/publish-new.ts @@ -5,6 +5,7 @@ import minimist from 'minimist' import { assert } from 'node:console' import { SemVer, parse } from 'semver' import { exec } from './lib/exec' +import { generateAutoRcFile } from './lib/labels' import { nicelog } from './lib/nicelog' import { getLatestVersion, publish, setAllVersions } from './lib/publishing' import { getAllWorkspacePackages } from './lib/workspace' @@ -105,6 +106,7 @@ async function main() { disableTsNode: true, }) + await generateAutoRcFile() await auto.loadConfig() // this creates a new commit diff --git a/scripts/publish-patch.ts b/scripts/publish-patch.ts index 96f7e5710..848573d55 100644 --- a/scripts/publish-patch.ts +++ b/scripts/publish-patch.ts @@ -4,6 +4,7 @@ import glob from 'glob' import { assert } from 'node:console' import { appendFileSync } from 'node:fs' import { exec } from './lib/exec' +import { generateAutoRcFile } from './lib/labels' import { nicelog } from './lib/nicelog' import { getLatestVersion, publish, setAllVersions } from './lib/publishing' import { getAllWorkspacePackages } from './lib/workspace' @@ -73,6 +74,7 @@ async function main() { disableTsNode: true, }) + await generateAutoRcFile() await auto.loadConfig() // this creates a new commit diff --git a/scripts/update-pr-template.ts b/scripts/update-pr-template.ts new file mode 100644 index 000000000..5360c000b --- /dev/null +++ b/scripts/update-pr-template.ts @@ -0,0 +1,70 @@ +import { Octokit } from '@octokit/rest' +import { existsSync, readFileSync, writeFileSync } from 'fs' +import { join } from 'path' +import { REPO_ROOT } from './lib/file' +import { formatLabelOptionsForPRTemplate, getLabelNames } from './lib/labels' + +const prTemplatePath = join(REPO_ROOT, '.github', 'pull_request_template.md') + +const octo = new Octokit({}) + +async function updatePRTemplate(check: boolean) { + if (!existsSync(prTemplatePath)) { + console.error('❌ Could not find PR template at', prTemplatePath) + process.exit(1) + } + + const prTemplate = readFileSync(prTemplatePath).toString() + const labelsPart = prTemplate.match(/(### Change Type(.|\s)*?\n)###/)?.[1] + if (!labelsPart) { + console.error( + '❌ Could not find the labels section of the pull request template! It should start with "### Change Type"' + ) + process.exit(1) + } + const updated = prTemplate.replace( + labelsPart, + `### Change Type\n\n${formatLabelOptionsForPRTemplate()}\n\n` + ) + if (check && updated !== prTemplate) { + console.error( + '❌ PR template labels section is out of date. Run `yarn update-pr-template` to fix it.' + ) + console.error( + '💡 Were you trying to change the labels section manually? Update scripts/lib/labels.ts instead.' + ) + process.exit(1) + } + + // make sure all labels exist + const repoLabels = new Set( + ( + await octo.issues.listLabelsForRepo({ + owner: 'tldraw', + repo: 'tldraw', + per_page: 100, + }) + ).data.map((x) => x.name) + ) + + const missingLabels = getLabelNames().filter((x) => !repoLabels.has(x)) + if (missingLabels.length > 0) { + console.error( + '❌ The following labels do not exist in the tldraw repo:', + missingLabels.map((l) => JSON.stringify(l)).join(', ') + ) + console.error( + `Add them yourself or update scripts/lib/labels.ts and re-run \`yarn update-pr-template\` to remove them.` + ) + process.exit(1) + } + + if (!check) { + console.log('Writing template to', prTemplatePath) + writeFileSync(prTemplatePath, updated) + } else { + console.log('All good!') + } +} + +updatePRTemplate(process.argv.includes('--check')) diff --git a/yarn.lock b/yarn.lock index c53bbc5fd..22803447b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4502,6 +4502,15 @@ __metadata: languageName: node linkType: hard +"@octokit/plugin-request-log@npm:^4.0.0": + version: 4.0.1 + resolution: "@octokit/plugin-request-log@npm:4.0.1" + peerDependencies: + "@octokit/core": 5 + checksum: fd8c0a201490cba00084689a0d1d54fc7b5ab5b6bdb7e447056b947b1754f78526e9685400eab10d3522bfa7b5bc49c555f41ec412c788610b96500b168f3789 + languageName: node + linkType: hard + "@octokit/plugin-rest-endpoint-methods@npm:^10.0.0": version: 10.2.0 resolution: "@octokit/plugin-rest-endpoint-methods@npm:10.2.0" @@ -4632,6 +4641,18 @@ __metadata: languageName: node linkType: hard +"@octokit/rest@npm:^20.0.2": + version: 20.0.2 + resolution: "@octokit/rest@npm:20.0.2" + dependencies: + "@octokit/core": "npm:^5.0.0" + "@octokit/plugin-paginate-rest": "npm:^9.0.0" + "@octokit/plugin-request-log": "npm:^4.0.0" + "@octokit/plugin-rest-endpoint-methods": "npm:^10.0.0" + checksum: 527e1806ca274209a2a7daa485010dafb2ebb6c9b0b44c1d33a8f1f16f10e54a96386a4f642dc416160842a4b367d3953d27f8b827b9a94600709d2ac5e95d21 + languageName: node + linkType: hard + "@octokit/types@npm:^12.0.0, @octokit/types@npm:^12.2.0, @octokit/types@npm:^12.3.0, @octokit/types@npm:^12.4.0": version: 12.4.0 resolution: "@octokit/types@npm:12.4.0" @@ -7326,6 +7347,7 @@ __metadata: "@auto-it/core": "npm:^11.1.1" "@aws-sdk/client-s3": "npm:^3.440.0" "@aws-sdk/lib-storage": "npm:^3.440.0" + "@octokit/rest": "npm:^20.0.2" "@types/is-ci": "npm:^3.0.0" "@types/minimist": "npm:^1.2.5" "@types/node": "npm:~20.11"