-
-
Notifications
You must be signed in to change notification settings - Fork 154
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #454 from code-hike/new-ci
New github workflow
- Loading branch information
Showing
19 changed files
with
1,250 additions
and
125 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { exec, getExecOutput } from "@actions/exec" | ||
import github from "@actions/github" | ||
import fs from "fs" | ||
|
||
export async function checkout(branch) { | ||
let { stderr } = await getExecOutput("git", ["checkout", branch], { | ||
ignoreReturnCode: true, | ||
}) | ||
let isCreatingBranch = !stderr | ||
.toString() | ||
.includes(`Switched to a new branch '${branch}'`) | ||
if (isCreatingBranch) { | ||
await exec("git", ["checkout", "-b", branch]) | ||
} | ||
} | ||
|
||
export async function resetBranch() { | ||
// reset current branch to the commit that triggered the workflow | ||
await exec("git", ["reset", `--hard`, github.context.sha]) | ||
} | ||
|
||
export async function commitAll(message) { | ||
await exec("git", ["add", "."]) | ||
await exec("git", ["commit", "-m", message]) | ||
} | ||
|
||
export async function forcePush(branch) { | ||
await exec("git", ["push", "origin", `HEAD:${branch}`, "--force"]) | ||
} | ||
|
||
export async function setupUser() { | ||
await exec("git", ["config", "user.name", `"github-actions[bot]"`]) | ||
await exec("git", [ | ||
"config", | ||
"user.email", | ||
`"github-actions[bot]@users.noreply.github.com"`, | ||
]) | ||
await fs.promises.writeFile( | ||
`${process.env.HOME}/.netrc`, | ||
`machine github.com\nlogin github-actions[bot]\npassword ${process.env.GITHUB_TOKEN}`, | ||
) | ||
} | ||
|
||
export async function pushTags() { | ||
await exec("git", ["push", "origin", "--tags"]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { unified } from "unified" | ||
import remarkParse from "remark-parse" | ||
import remarkStringify from "remark-stringify" | ||
import * as mdastToString from "mdast-util-to-string" | ||
|
||
const BumpLevels = { | ||
dep: 0, | ||
patch: 1, | ||
minor: 2, | ||
major: 3, | ||
} | ||
|
||
export function getChangelogEntry(changelog, version) { | ||
let ast = unified().use(remarkParse).parse(changelog) | ||
|
||
let highestLevel = BumpLevels.dep | ||
|
||
let nodes = ast.children | ||
let headingStartInfo | ||
let endIndex | ||
|
||
for (let i = 0; i < nodes.length; i++) { | ||
let node = nodes[i] | ||
if (node.type === "heading") { | ||
let stringified = mdastToString.toString(node) | ||
let match = stringified.toLowerCase().match(/(major|minor|patch)/) | ||
if (match !== null) { | ||
let level = BumpLevels[match[0]] | ||
highestLevel = Math.max(level, highestLevel) | ||
} | ||
if (headingStartInfo === undefined && stringified === version) { | ||
headingStartInfo = { | ||
index: i, | ||
depth: node.depth, | ||
} | ||
continue | ||
} | ||
if ( | ||
endIndex === undefined && | ||
headingStartInfo !== undefined && | ||
headingStartInfo.depth === node.depth | ||
) { | ||
endIndex = i | ||
break | ||
} | ||
} | ||
} | ||
if (headingStartInfo) { | ||
ast.children = ast.children.slice(headingStartInfo.index + 1, endIndex) | ||
} | ||
return { | ||
content: unified().use(remarkStringify).stringify(ast), | ||
highestLevel: highestLevel, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export const PACKAGE_NAME = "codehike" | ||
export const VERSION_COMMAND = "pnpm version-packages" | ||
export const PUBLISH_COMMAND = "pnpm release" | ||
export const RELEASE_BRANCH = "release" | ||
export const BASE_BRANCH = "next" | ||
export const PACKAGE_DIR = `packages/${PACKAGE_NAME}` | ||
|
||
export const IDENTIFIER = "<!-- CH_ACTION -->" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { Octokit } from "@octokit/action" | ||
import { IDENTIFIER, PACKAGE_NAME } from "./params.mjs" | ||
import github from "@actions/github" | ||
|
||
const octokit = new Octokit({}) | ||
const prNumber = github.context.payload.pull_request.number | ||
|
||
console.log("Querying closing issues") | ||
const query = `query ($owner: String!, $repo: String!, $prNumber: Int!) { | ||
repository(owner: $owner, name: $repo) { | ||
pullRequest(number: $prNumber) { | ||
title | ||
state | ||
closingIssuesReferences(first: 10) { | ||
nodes { | ||
number | ||
} | ||
} | ||
} | ||
} | ||
}` | ||
const result = await octokit.graphql(query, { | ||
...github.context.repo, | ||
prNumber: Number(prNumber), | ||
}) | ||
|
||
const body = `${IDENTIFIER} | ||
This issue has been fixed but not yet released. | ||
Try it in your project before the release with: | ||
${"```"} | ||
npm i https://pkg.pr.new/${PACKAGE_NAME}@${prNumber} | ||
${"```"} | ||
Or wait for the next [release](https://github.com/${github.context.repo.owner}/${github.context.repo.repo}/pulls?q=is%3Aopen+is%3Apr+label%3Arelease). | ||
` | ||
|
||
console.log("Commenting issues") | ||
await Promise.all( | ||
result.repository.pullRequest.closingIssuesReferences.nodes.map( | ||
async ({ number }) => { | ||
console.log("Commenting issue", number) | ||
await octokit.issues.createComment({ | ||
...github.context.repo, | ||
issue_number: number, | ||
body, | ||
}) | ||
}, | ||
), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import { Octokit } from "@octokit/action" | ||
import { humanId } from "human-id" | ||
import fs from "fs" | ||
import { IDENTIFIER, PACKAGE_NAME } from "./params.mjs" | ||
import github from "@actions/github" | ||
|
||
const octokit = new Octokit({}) | ||
const prNumber = github.context.payload.pull_request.number | ||
|
||
async function createOrUpdateComment(prevComment, prNumber, body) { | ||
if (prevComment) { | ||
console.log("Updating comment") | ||
await octokit.issues.updateComment({ | ||
...github.context.repo, | ||
comment_id: prevComment.id, | ||
body, | ||
}) | ||
} else { | ||
console.log("Creating comment", prNumber) | ||
await octokit.issues.createComment({ | ||
...github.context.repo, | ||
issue_number: prNumber, | ||
body, | ||
}) | ||
} | ||
} | ||
|
||
console.log("Listing comments", prNumber) | ||
const { data: comments } = await octokit.issues.listComments({ | ||
...github.context.repo, | ||
issue_number: prNumber, | ||
}) | ||
const prevComment = comments.find((comment) => | ||
comment.body.startsWith(IDENTIFIER), | ||
) | ||
console.log("prevComment", !!prevComment) | ||
|
||
console.log("Finding changeset") | ||
let changedFiles = await octokit.pulls.listFiles({ | ||
...github.context.repo, | ||
pull_number: prNumber, | ||
}) | ||
const changeset = changedFiles.data.find( | ||
(file) => | ||
file.status === "added" && | ||
/^\.changeset\/.+\.md$/.test(file.filename) && | ||
file.filename !== ".changeset/README.md", | ||
) | ||
const hasChangesets = !!changeset | ||
console.log({ hasChangesets }) | ||
|
||
if (!hasChangesets) { | ||
console.log("Getting PR") | ||
const pr = await octokit.pulls.get({ | ||
...github.context.repo, | ||
pull_number: prNumber, | ||
}) | ||
const filename = humanId({ | ||
separator: "-", | ||
capitalize: false, | ||
}) | ||
const value = encodeURIComponent(`--- | ||
"${PACKAGE_NAME}": patch | ||
--- | ||
${pr.data.title} | ||
`) | ||
const repoURL = pr.data.head.repo.html_url | ||
const addChangesetURL = `${repoURL}/new/${pr.data.head.ref}?filename=.changeset/${filename}.md&value=${value}` | ||
const body = `${IDENTIFIER} | ||
No changeset detected. If you are changing ${ | ||
"`" + PACKAGE_NAME + "`" | ||
} [click here to add a changeset](${addChangesetURL}). | ||
` | ||
await createOrUpdateComment(prevComment, prNumber, body) | ||
process.exit(0) | ||
} | ||
|
||
// if has changesets | ||
|
||
console.log("Adding label") | ||
await octokit.issues.addLabels({ | ||
...github.context.repo, | ||
issue_number: prNumber, | ||
labels: ["changeset"], | ||
}) | ||
|
||
console.log("Reading canary.json") | ||
const canary = await fs.promises.readFile("canary.json", "utf8") | ||
console.log({ canary }) | ||
const { packages } = JSON.parse(canary) | ||
const { name, url } = packages[0] | ||
const body = `${IDENTIFIER} | ||
Try ${"`" + name + "`"} from this pull request in your project with: | ||
${"```"} | ||
npm i https://pkg.pr.new/${name}@${prNumber} | ||
${"```"} | ||
` | ||
await createOrUpdateComment(prevComment, prNumber, body) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import { exec } from "@actions/exec" | ||
import readChangesets from "@changesets/read" | ||
import { | ||
checkout, | ||
commitAll, | ||
forcePush, | ||
resetBranch, | ||
setupUser, | ||
} from "./git-utils.mjs" | ||
import fs from "fs" | ||
import { getChangelogEntry } from "./md-utils.mjs" | ||
import { Octokit } from "@octokit/action" | ||
import github from "@actions/github" | ||
import { | ||
BASE_BRANCH, | ||
PACKAGE_DIR, | ||
PACKAGE_NAME, | ||
RELEASE_BRANCH, | ||
VERSION_COMMAND, | ||
} from "./params.mjs" | ||
|
||
const cwd = process.cwd() | ||
const octokit = new Octokit({}) | ||
|
||
console.log("Reading changesets") | ||
const changesets = await readChangesets(cwd) | ||
if (changesets.length === 0) { | ||
console.log("No changesets found") | ||
process.exit(0) | ||
} | ||
|
||
console.log("Setting up user") | ||
await setupUser() | ||
|
||
console.log("Checking out release branch") | ||
await checkout(RELEASE_BRANCH) | ||
await resetBranch() | ||
|
||
console.log("Running version command") | ||
const [versionCommand, ...versionArgs] = VERSION_COMMAND.split(/\s+/) | ||
await exec(versionCommand, versionArgs, { cwd }) | ||
|
||
console.log("Reading package files") | ||
const pkg = JSON.parse( | ||
await fs.promises.readFile(`${PACKAGE_DIR}/package.json`, "utf8"), | ||
) | ||
const changelog = await fs.promises.readFile( | ||
`${PACKAGE_DIR}/CHANGELOG.md`, | ||
"utf8", | ||
) | ||
const canary = JSON.parse(await fs.promises.readFile("canary.json", "utf8")) | ||
|
||
console.log("Committing changes") | ||
await commitAll(`${PACKAGE_NAME}@${pkg.version}`) | ||
console.log("Pushing changes") | ||
await forcePush(RELEASE_BRANCH) | ||
|
||
console.log("Find existing release PR") | ||
const { data: prs } = await octokit.pulls.list({ | ||
...github.context.repo, | ||
state: "open", | ||
base: BASE_BRANCH, | ||
head: `${github.context.repo.owner}:${RELEASE_BRANCH}`, | ||
}) | ||
console.log("Existing PRs", prs) | ||
|
||
const entry = getChangelogEntry(changelog, pkg.version) | ||
const title = `🚀 Release ${"`" + pkg.name}@${pkg.version + "`"} 🚀` | ||
const canaryUrl = canary.packages[0].url | ||
const body = `${entry.content} | ||
--- | ||
You can try ${ | ||
"`" + PACKAGE_NAME + "@" + pkg.version + "`" | ||
} in your project before it's released with: | ||
${"```"} | ||
npm i ${canaryUrl} | ||
${"```"} | ||
` | ||
|
||
if (prs.length === 0) { | ||
console.log("Creating new release PR") | ||
const { data: pr } = await octokit.rest.pulls.create({ | ||
...github.context.repo, | ||
base: BASE_BRANCH, | ||
head: RELEASE_BRANCH, | ||
title, | ||
body, | ||
}) | ||
console.log("Adding `release` label") | ||
await octokit.rest.issues.addLabels({ | ||
...github.context.repo, | ||
issue_number: pr.number, | ||
labels: ["release"], | ||
}) | ||
} else { | ||
console.log("Updating existing release PR") | ||
const { number } = prs[0] | ||
await octokit.rest.pulls.update({ | ||
...github.context.repo, | ||
pull_number: number, | ||
title, | ||
body, | ||
}) | ||
} |
Oops, something went wrong.