Skip to content

Commit 93fb3ae

Browse files
committed
New github workflow
1 parent 1f2688f commit 93fb3ae

18 files changed

+1179
-125
lines changed

.github/scripts/git-utils.mjs

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { exec, getExecOutput } from "@actions/exec"
2+
import github from "@actions/github"
3+
import fs from "fs"
4+
5+
export async function checkout(branch) {
6+
let { stderr } = await getExecOutput("git", ["checkout", branch], {
7+
ignoreReturnCode: true,
8+
})
9+
let isCreatingBranch = !stderr
10+
.toString()
11+
.includes(`Switched to a new branch '${branch}'`)
12+
if (isCreatingBranch) {
13+
await exec("git", ["checkout", "-b", branch])
14+
}
15+
}
16+
17+
export async function resetBranch() {
18+
// reset current branch to the commit that triggered the workflow
19+
await exec("git", ["reset", `--hard`, github.context.sha])
20+
}
21+
22+
export async function commitAll(message) {
23+
await exec("git", ["add", "."])
24+
await exec("git", ["commit", "-m", message])
25+
}
26+
27+
export async function forcePush(branch) {
28+
await exec("git", ["push", "origin", `HEAD:${branch}`, "--force"])
29+
}
30+
31+
export async function setupUser() {
32+
await exec("git", ["config", "user.name", `"github-actions[bot]"`])
33+
await exec("git", [
34+
"config",
35+
"user.email",
36+
`"github-actions[bot]@users.noreply.github.com"`,
37+
])
38+
await fs.promises.writeFile(
39+
`${process.env.HOME}/.netrc`,
40+
`machine github.com\nlogin github-actions[bot]\npassword ${process.env.GITHUB_TOKEN}`,
41+
)
42+
}
43+
44+
export async function pushTags() {
45+
await exec("git", ["push", "origin", "--tags"])
46+
}

.github/scripts/md-utils.mjs

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { unified } from "unified"
2+
import remarkParse from "remark-parse"
3+
import remarkStringify from "remark-stringify"
4+
import * as mdastToString from "mdast-util-to-string"
5+
6+
const BumpLevels = {
7+
dep: 0,
8+
patch: 1,
9+
minor: 2,
10+
major: 3,
11+
}
12+
13+
export function getChangelogEntry(changelog, version) {
14+
let ast = unified().use(remarkParse).parse(changelog)
15+
16+
let highestLevel = BumpLevels.dep
17+
18+
let nodes = ast.children
19+
let headingStartInfo
20+
let endIndex
21+
22+
for (let i = 0; i < nodes.length; i++) {
23+
let node = nodes[i]
24+
if (node.type === "heading") {
25+
let stringified = mdastToString.toString(node)
26+
let match = stringified.toLowerCase().match(/(major|minor|patch)/)
27+
if (match !== null) {
28+
let level = BumpLevels[match[0]]
29+
highestLevel = Math.max(level, highestLevel)
30+
}
31+
if (headingStartInfo === undefined && stringified === version) {
32+
headingStartInfo = {
33+
index: i,
34+
depth: node.depth,
35+
}
36+
continue
37+
}
38+
if (
39+
endIndex === undefined &&
40+
headingStartInfo !== undefined &&
41+
headingStartInfo.depth === node.depth
42+
) {
43+
endIndex = i
44+
break
45+
}
46+
}
47+
}
48+
if (headingStartInfo) {
49+
ast.children = ast.children.slice(headingStartInfo.index + 1, endIndex)
50+
}
51+
return {
52+
content: unified().use(remarkStringify).stringify(ast),
53+
highestLevel: highestLevel,
54+
}
55+
}

.github/scripts/params.mjs

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export const PACKAGE_NAME = "codehike"
2+
export const VERSION_COMMAND = "pnpm version-packages"
3+
export const PUBLISH_COMMAND = "pnpm release"
4+
export const RELEASE_BRANCH = "release"
5+
export const BASE_BRANCH = "next"
6+
export const PACKAGE_DIR = `packages/${PACKAGE_NAME}`
7+
8+
export const IDENTIFIER = "<!-- CH_ACTION -->"

.github/scripts/pr-merged.mjs

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { Octokit } from "@octokit/action"
2+
import { IDENTIFIER, PACKAGE_NAME } from "./params.mjs"
3+
import github from "@actions/github"
4+
5+
const octokit = new Octokit({})
6+
const prNumber = github.context.payload.pull_request.number
7+
8+
console.log("Querying closing issues")
9+
const query = `query ($owner: String!, $repo: String!, $prNumber: Int!) {
10+
repository(owner: $owner, name: $repo) {
11+
pullRequest(number: $prNumber) {
12+
title
13+
state
14+
closingIssuesReferences(first: 10) {
15+
nodes {
16+
number
17+
}
18+
}
19+
}
20+
}
21+
}`
22+
const result = await octokit.graphql(query, {
23+
...github.context.repo,
24+
prNumber: Number(prNumber),
25+
})
26+
27+
const body = `${IDENTIFIER}
28+
This issue has been fixed but not yet released.
29+
30+
Try it in your project before the release with:
31+
32+
${"```"}
33+
npm i https://pkg.pr.new/${PACKAGE_NAME}@${prNumber}
34+
${"```"}
35+
36+
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).
37+
`
38+
39+
console.log("Commenting issues")
40+
await Promise.all(
41+
result.repository.pullRequest.closingIssuesReferences.nodes.map(
42+
async ({ number }) => {
43+
console.log("Commenting issue", number)
44+
await octokit.issues.createComment({
45+
...github.context.repo,
46+
issue_number: number,
47+
body,
48+
})
49+
},
50+
),
51+
)

.github/scripts/pr-updated.mjs

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { Octokit } from "@octokit/action"
2+
import { humanId } from "human-id"
3+
import fs from "fs"
4+
import { IDENTIFIER, PACKAGE_NAME } from "./params.mjs"
5+
import github from "@actions/github"
6+
7+
const octokit = new Octokit({})
8+
const prNumber = github.context.payload.pull_request.number
9+
10+
async function createOrUpdateComment(prevComment, prNumber, body) {
11+
if (prevComment) {
12+
console.log("Updating comment")
13+
await octokit.issues.updateComment({
14+
...github.context.repo,
15+
comment_id: prevComment.id,
16+
body,
17+
})
18+
} else {
19+
console.log("Creating comment", prNumber)
20+
await octokit.issues.createComment({
21+
...github.context.repo,
22+
issue_number: prNumber,
23+
body,
24+
})
25+
}
26+
}
27+
28+
console.log("Listing comments", prNumber)
29+
const { data: comments } = await octokit.issues.listComments({
30+
...github.context.repo,
31+
issue_number: prNumber,
32+
})
33+
const prevComment = comments.find((comment) =>
34+
comment.body.startsWith(IDENTIFIER),
35+
)
36+
console.log("prevComment", !!prevComment)
37+
38+
console.log("Finding changeset")
39+
let changedFiles = await octokit.pulls.listFiles({
40+
...github.context.repo,
41+
pull_number: prNumber,
42+
})
43+
const changeset = changedFiles.data.find(
44+
(file) =>
45+
file.status === "added" &&
46+
/^\.changeset\/.+\.md$/.test(file.filename) &&
47+
file.filename !== ".changeset/README.md",
48+
)
49+
const hasChangesets = !!changeset
50+
console.log({ hasChangesets })
51+
52+
if (!hasChangesets) {
53+
console.log("Getting PR")
54+
const pr = await octokit.pulls.get({
55+
...github.context.repo,
56+
pull_number: prNumber,
57+
})
58+
const filename = humanId({
59+
separator: "-",
60+
capitalize: false,
61+
})
62+
const value = encodeURIComponent(`---
63+
"${PACKAGE_NAME}": patch
64+
---
65+
66+
${pr.data.title}
67+
`)
68+
const repoURL = pr.data.head.repo.html_url
69+
const addChangesetURL = `${repoURL}/new/${pr.data.head.ref}?filename=.changeset/${filename}.md&value=${value}`
70+
const body = `${IDENTIFIER}
71+
No changeset detected. If you are changing ${
72+
"`" + PACKAGE_NAME + "`"
73+
} [click here to add a changeset](${addChangesetURL}).
74+
`
75+
await createOrUpdateComment(prevComment, prNumber, body)
76+
process.exit(0)
77+
}
78+
79+
// if has changesets
80+
81+
console.log("Adding label")
82+
await octokit.issues.addLabels({
83+
...github.context.repo,
84+
issue_number: prNumber,
85+
labels: ["changeset"],
86+
})
87+
88+
console.log("Reading canary.json")
89+
const canary = await fs.promises.readFile("canary.json", "utf8")
90+
console.log({ canary })
91+
const { packages } = JSON.parse(canary)
92+
const { name, url } = packages[0]
93+
const body = `${IDENTIFIER}
94+
Try ${"`" + name + "`"} from this pull request in your project with:
95+
96+
${"```"}
97+
npm i https://pkg.pr.new/${name}@${prNumber}
98+
${"```"}
99+
`
100+
await createOrUpdateComment(prevComment, prNumber, body)

.github/scripts/prepare-release.mjs

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { exec } from "@actions/exec"
2+
import readChangesets from "@changesets/read"
3+
import {
4+
checkout,
5+
commitAll,
6+
forcePush,
7+
resetBranch,
8+
setupUser,
9+
} from "./git-utils.mjs"
10+
import fs from "fs"
11+
import { getChangelogEntry } from "./md-utils.mjs"
12+
import { Octokit } from "@octokit/action"
13+
import github from "@actions/github"
14+
import {
15+
BASE_BRANCH,
16+
PACKAGE_DIR,
17+
PACKAGE_NAME,
18+
RELEASE_BRANCH,
19+
VERSION_COMMAND,
20+
} from "./params.mjs"
21+
22+
const cwd = process.cwd()
23+
const octokit = new Octokit({})
24+
25+
console.log("Reading changesets")
26+
const changesets = await readChangesets(cwd)
27+
if (changesets.length === 0) {
28+
console.log("No changesets found")
29+
process.exit(0)
30+
}
31+
32+
console.log("Setting up user")
33+
await setupUser()
34+
35+
console.log("Checking out release branch")
36+
await checkout(RELEASE_BRANCH)
37+
await resetBranch()
38+
39+
console.log("Running version command")
40+
const [versionCommand, ...versionArgs] = VERSION_COMMAND.split(/\s+/)
41+
await exec(versionCommand, versionArgs, { cwd })
42+
43+
console.log("Reading package files")
44+
const pkg = JSON.parse(
45+
await fs.promises.readFile(`${PACKAGE_DIR}/package.json`, "utf8"),
46+
)
47+
const changelog = await fs.promises.readFile(
48+
`${PACKAGE_DIR}/CHANGELOG.md`,
49+
"utf8",
50+
)
51+
const canary = JSON.parse(await fs.promises.readFile("canary.json", "utf8"))
52+
53+
console.log("Committing changes")
54+
await commitAll(`${PACKAGE_NAME}@${pkg.version}`)
55+
console.log("Pushing changes")
56+
await forcePush(RELEASE_BRANCH)
57+
58+
console.log("Find existing release PR")
59+
const { data: prs } = await octokit.pulls.list({
60+
...github.context.repo,
61+
state: "open",
62+
base: BASE_BRANCH,
63+
head: `${github.context.repo.owner}:${RELEASE_BRANCH}`,
64+
})
65+
console.log("Existing PRs", prs)
66+
67+
const entry = getChangelogEntry(changelog, pkg.version)
68+
const title = `🚀 Release ${"`" + pkg.name}@${pkg.version + "`"} 🚀`
69+
const canaryUrl = canary.packages[0].url
70+
const body = `${entry.content}
71+
72+
---
73+
74+
You can try ${
75+
"`" + PACKAGE_NAME + "@" + pkg.version + "`"
76+
} in your project before it's released with:
77+
78+
${"```"}
79+
npm i ${canaryUrl}
80+
${"```"}
81+
`
82+
83+
if (prs.length === 0) {
84+
console.log("Creating new release PR")
85+
const { data: pr } = await octokit.rest.pulls.create({
86+
...github.context.repo,
87+
base: BASE_BRANCH,
88+
head: RELEASE_BRANCH,
89+
title,
90+
body,
91+
})
92+
console.log("Adding `release` label")
93+
await octokit.rest.issues.addLabels({
94+
...github.context.repo,
95+
issue_number: pr.number,
96+
labels: ["release"],
97+
})
98+
} else {
99+
console.log("Updating existing release PR")
100+
const { number } = prs[0]
101+
await octokit.rest.pulls.update({
102+
...github.context.repo,
103+
pull_number: number,
104+
title,
105+
body,
106+
})
107+
}

0 commit comments

Comments
 (0)