Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci(release): sync with remix #9813

Merged
merged 4 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 0 additions & 17 deletions .github/workflows/postrelease.yml

This file was deleted.

71 changes: 50 additions & 21 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
name: 🕊 Release
name: 🦋 Changesets Release
on:
push:
branches:
- release
- "release-*"
- "!release-experimental"
- "!release-experimental-*"

concurrency: ${{ github.workflow }}-${{ github.ref }}

env:
CI: true
- "!release-manual"
- "!release-manual-*"

jobs:
release:
name: 🦋 Changesets Release
if: github.repository == 'remix-run/react-router'
runs-on: ubuntu-latest

outputs:
publishedPackages: ${{ steps.changesets.outputs.publishedPackages }}
published: ${{ steps.changesets.outputs.published }}
steps:
- name: 🛑 Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0

- name: ⬇️ Checkout repo
uses: actions/checkout@v3
with:
fetch-depth: 0

- name: ⎔ Setup Node
- name: ⎔ Setup node
uses: actions/setup-node@v3
with:
node-version-file: ".nvmrc"
cache: yarn
cache: "yarn"

- name: 📥 Install dependencies
# even though this is called "npm-install" it does use yarn to install
# because we have a yarn.lock and caches efficiently.
uses: bahmutov/npm-install@v1
- name: 📥 Install deps
run: yarn --frozen-lockfile

- name: 🔐 Setup npm auth
run: |
Expand All @@ -52,16 +52,45 @@ jobs:
version: yarn run version
commit: "chore: Update version for release"
title: "chore: Update version for release"
publish: yarn release
publish: yarn run release
createGithubReleases: false
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN_SO_OTHER_ACTIONS_RUN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

# comment:
# needs: [release]
# name: 📝 Comment on related issues and pull requests
# if: github.repository == 'remix-run/react-router'
# uses: remix-run/react-router/.github/workflows/release-comments.yml@main
# with:
# ref: ${{ github.ref }}
findPackage:
name: 🦋 Find Package
needs: [release]
runs-on: ubuntu-latest
if: github.repository == 'remix-run/react-router' && needs.release.outputs.published == 'true'
outputs:
package: ${{ steps.findPackage.outputs.package }}
steps:
- name: 🛑 Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0

- name: ⬇️ Checkout repo
uses: actions/checkout@v3

- name: ⎔ Setup node
uses: actions/setup-node@v3
with:
node-version: 16
cache: "npm"

- id: findPackage
run: |
package=$(node ./scripts/release/find-release-from-changeset.js)
echo "package=${package}" >> $GITHUB_OUTPUT
env:
packageVersionToFollow: "react-router"
publishedPackages: ${{ needs.release.outputs.publishedPackages }}

comment:
name: 📝 Comment on related issues and pull requests
if: github.repository == 'remix-run/react-router' && needs.findPackage.outputs.package != ''
needs: [release, findPackage]
uses: ./.github/workflows/release-comments.yml
with:
ref: refs/tags/${{ needs.findPackage.outputs.package }}
packageVersionToFollow: "react-router"
41 changes: 15 additions & 26 deletions scripts/release/comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
OWNER,
REPO,
PR_FILES_STARTS_WITH,
IS_NIGHTLY_RELEASE,
IS_STABLE_RELEASE,
AWAITING_RELEASE_LABEL,
} from "./constants";
import {
Expand Down Expand Up @@ -53,7 +53,7 @@ async function commentOnIssuesAndPrsAboutRelease() {
let prLabels = pr.labels.map((label) => label.name);
let prIsAwaitingRelease = prLabels.includes(AWAITING_RELEASE_LABEL);

if (!IS_NIGHTLY_RELEASE && prIsAwaitingRelease) {
if (IS_STABLE_RELEASE && prIsAwaitingRelease) {
promises.push(
removeLabel({ owner: OWNER, repo: REPO, issue: pr.number })
);
Expand All @@ -73,27 +73,19 @@ async function commentOnIssuesAndPrsAboutRelease() {

issuesCommentedOn.add(issue.number);
let issueUrl = getGitHubUrl("issue", issue.number);
console.log(`commenting on issue ${issueUrl}`);

if (IS_NIGHTLY_RELEASE || !prIsAwaitingRelease) {
console.log(`commenting on ${issueUrl}`);
promises.push(
commentOnIssue({
owner: OWNER,
repo: REPO,
issue: issue.number,
version: VERSION,
})
);
} else {
console.log(`commenting on and closing ${issueUrl}`);
promises.push(
commentOnIssue({
owner: OWNER,
repo: REPO,
issue: issue.number,
version: VERSION,
})
);
promises.push(
commentOnIssue({
owner: OWNER,
repo: REPO,
issue: issue.number,
version: VERSION,
})
);

if (IS_STABLE_RELEASE) {
console.log(`closing issue ${issueUrl}`);
promises.push(
closeIssue({ owner: OWNER, repo: REPO, issue: issue.number })
);
Expand All @@ -104,10 +96,7 @@ async function commentOnIssuesAndPrsAboutRelease() {
let result = await Promise.allSettled(promises);
let rejected = result.filter((r) => r.status === "rejected");
if (rejected.length > 0) {
console.log(
"🚨 failed to comment on some issues/prs - the most likely reason is they were issues that were turned into discussions, which don't have an api to comment with"
);
console.log(rejected);
console.error("🚨 failed to comment on some issues/prs", rejected);
}
}

Expand Down
3 changes: 2 additions & 1 deletion scripts/release/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cleanupRef, cleanupTagName, isNightly } from "./utils";
import { cleanupRef, cleanupTagName, isNightly, isStable } from "./utils";

if (!process.env.DEFAULT_BRANCH) {
throw new Error("DEFAULT_BRANCH is required");
Expand Down Expand Up @@ -32,3 +32,4 @@ export const NIGHTLY_BRANCH = process.env.NIGHTLY_BRANCH;
export const PR_FILES_STARTS_WITH = ["packages/"];
export const IS_NIGHTLY_RELEASE = isNightly(VERSION);
export const AWAITING_RELEASE_LABEL = "awaiting release";
export const IS_STABLE_RELEASE = isStable(VERSION);
38 changes: 38 additions & 0 deletions scripts/release/find-release-from-changeset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
*
* @param {string | undefined} publishedPackages
* @param {string | undefined} packageVersionToFollow
* @returns {string | undefined}
*/
function findReleaseFromChangeset(publishedPackages, packageVersionToFollow) {
if (!publishedPackages) {
throw new Error("No published packages found");
}

let packages = JSON.parse(publishedPackages);

if (!Array.isArray(packages)) {
throw new Error("Published packages is not an array");
}

/** @see https://github.com/changesets/action#outputs */
/** @type { { name: string; version: string }[] } */
let typed = packages.filter((pkg) => "name" in pkg && "version" in pkg);

let found = typed.find((pkg) => pkg.name === packageVersionToFollow);

if (!found) {
throw new Error(
`${packageVersionToFollow} was not found in the published packages`
);
}

let result = `${found.name}@${found.version}`;
console.log(result);
return result;
}

findReleaseFromChangeset(
process.env.publishedPackages,
process.env.packageVersionToFollow
);
70 changes: 48 additions & 22 deletions scripts/release/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import {
DEFAULT_BRANCH,
PACKAGE_VERSION_TO_FOLLOW,
AWAITING_RELEASE_LABEL,
IS_NIGHTLY_RELEASE,
IS_STABLE_RELEASE,
} from "./constants";
import { gql, graphqlWithAuth, octokit } from "./octokit";
import type { MinimalTag } from "./utils";
import { isNightly, isStable } from "./utils";
import { cleanupTagName } from "./utils";
import { checkIfStringStartsWith } from "./utils";

Expand Down Expand Up @@ -140,34 +143,32 @@ function getPreviousTagFromCurrentTag(

return { tag: tagName, date, isPrerelease };
})
.filter((v: any): v is MinimalTag => typeof v !== "undefined");
.filter((v: any): v is MinimalTag => typeof v !== "undefined")
.filter((tag) => {
if (IS_STABLE_RELEASE) return isStable(tag.tag);
let isNightlyTag = isNightly(tag.tag);
if (IS_NIGHTLY_RELEASE) return isNightlyTag;
return !isNightlyTag;
})
.sort((a, b) => {
if (IS_NIGHTLY_RELEASE) {
return b.date.getTime() - a.date.getTime();
}

return semver.rcompare(a.tag, b.tag);
});

let currentTagIndex = validTags.findIndex((tag) => tag.tag === currentTag);
let currentTagInfo: MinimalTag | undefined = validTags.at(currentTagIndex);
let previousTagInfo: MinimalTag | undefined;

if (!currentTagInfo) {
throw new Error(`Could not find last tag ${currentTag}`);
}

// if the currentTag was a stable tag, then we want to find the previous stable tag
if (!currentTagInfo.isPrerelease) {
validTags = validTags
.filter((tag) => !tag.isPrerelease)
.sort((a, b) => semver.rcompare(a.tag, b.tag));

currentTagIndex = validTags.findIndex((tag) => tag.tag === currentTag);
currentTagInfo = validTags.at(currentTagIndex);
if (!currentTagInfo) {
throw new Error(`Could not find last stable tag ${currentTag}`);
}
throw new Error(`Could not find tag ${currentTag}`);
}

previousTagInfo = validTags.at(currentTagIndex + 1);
if (!previousTagInfo) {
throw new Error(
`Could not find previous prerelease tag from ${currentTag}`
);
throw new Error(`Could not find previous tag from ${currentTag}`);
}

return {
Expand Down Expand Up @@ -232,21 +233,35 @@ interface GitHubGraphqlTag {
interface GitHubGraphqlTagResponse {
repository: {
refs: {
pageInfo: {
hasNextPage: boolean;
endCursor: string;
};
nodes: Array<GitHubGraphqlTag>;
};
};
}

async function getTags(owner: string, repo: string) {
async function getTags(
owner: string,
repo: string,
endCursor?: string,
nodes: Array<GitHubGraphqlTag> = []
): Promise<GitHubGraphqlTag[]> {
let response: GitHubGraphqlTagResponse = await graphqlWithAuth(
gql`
query GET_TAGS($owner: String!, $repo: String!) {
query GET_TAGS($owner: String!, $repo: String!, $endCursor: String) {
repository(owner: $owner, name: $repo) {
refs(
refPrefix: "refs/tags/"
first: 100
orderBy: { field: TAG_COMMIT_DATE, direction: DESC }
after: $endCursor
) {
pageInfo {
hasNextPage
endCursor
}
nodes {
name
target {
Expand All @@ -267,15 +282,26 @@ async function getTags(owner: string, repo: string) {
}
}
`,
{ owner, repo }
{ owner, repo, endCursor }
);

return response.repository.refs.nodes.filter((node) => {
let filtered = response.repository.refs.nodes.filter((node) => {
return (
node.name.startsWith(PACKAGE_VERSION_TO_FOLLOW) ||
node.name.startsWith("v0.0.0-nightly-")
);
});

if (response.repository.refs.pageInfo.hasNextPage) {
console.log("has next page", response.repository.refs.pageInfo.endCursor);

return getTags(owner, repo, response.repository.refs.pageInfo.endCursor, [
...nodes,
...filtered,
]);
}

return [...nodes, ...filtered];
}

export async function getIssuesClosedByPullRequests(
Expand Down
6 changes: 6 additions & 0 deletions scripts/release/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as semver from "semver";

import { GITHUB_REPOSITORY, PACKAGE_VERSION_TO_FOLLOW } from "./constants";

export function checkIfStringStartsWith(
Expand Down Expand Up @@ -34,3 +36,7 @@ export function cleanupRef(ref: string) {
export function isNightly(tagName: string) {
return tagName.startsWith("v0.0.0-nightly-");
}

export function isStable(tagName: string) {
return semver.prerelease(tagName) === null;
}