Skip to content

Instantly share code, notes, and snippets.

@eliashussary
Created March 17, 2021 17:30
Show Gist options
  • Save eliashussary/63e3ed1f7d1b2e063f99c431f237279e to your computer and use it in GitHub Desktop.
Save eliashussary/63e3ed1f7d1b2e063f99c431f237279e to your computer and use it in GitHub Desktop.
Yarn v2 workspaces changeset publish workaround
# .github/actions/yarn-changeset-publish/action.yml
name: "Yarn Changeset Publish"
description: "Creates release tags from changesets"
outputs:
published:
description: "The published state"
runs:
using: "node12"
main: "index.js"
name: Release
on:
push:
tags-ignore:
- "**"
branches:
- main
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@master
with:
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
fetch-depth: 0
- name: Use Node 14
uses: actions/setup-node@v1
with:
node-version: 14.x
- name: Use cached node_modules
uses: actions/cache@v1
with:
path: node_modules
key: nodeModules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
nodeModules-
- name: Setup .yarnrc.yml
run: |
yarn config set npmAuthToken $NPM_TOKEN
yarn config set npmAlwaysAuth true
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Install
run: yarn install --immutable
env:
CI: true
# this order matters
- name: Publish & Release or Skip to Changesets
id: create_github_releases
uses: ./.github/actions/yarn-changeset-publish
env:
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
- name: Create Release Pull Request
id: changesets
uses: changesets/action@master
with:
commit: "docs(changeset): changelogs and version bump"
version: yarn version
env:
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
// .github/actions/yarn-changeset-publish/index.js
// @ts-check
const core = require("@actions/core");
const { getOctokit, context } = require("@actions/github");
const { exec } = require("@actions/exec");
const fs = require("fs");
const readChangesets = require("@changesets/read").default;
async function execWithOutput(command, args, options) {
let myOutput = "";
let myError = "";
return {
code: await exec(command, args, {
listeners: {
stdout: (data) => {
myOutput += data.toString();
},
stderr: (data) => {
myError += data.toString();
},
},
...options,
}),
stdout: myOutput,
stderr: myError,
};
}
function nthIndexOf(str, re, n) {
let match = re.exec(str);
let i = 1;
let lastIndex = -1;
while ((match = re.exec(str)) != null) {
if (i >= n) {
break;
}
lastIndex = match.index;
i++;
}
return lastIndex;
}
function getChangeLogForRelease(pkg) {
try {
const VERSION_MD_HEADING = /## \d+\.\d+\.\d+/g;
const changeLogPath = require.resolve(pkg.name + "/CHANGELOG.md");
const changelog = fs.readFileSync(changeLogPath, "utf-8");
const lastVersionHeadingIdx = nthIndexOf(changelog, VERSION_MD_HEADING, 2);
if (lastVersionHeadingIdx === -1) {
return changelog;
}
return changelog.substring(0, lastVersionHeadingIdx);
} catch (err) {
console.error(err);
return "";
}
}
async function runPublish() {
const { stdout } = await execWithOutput(
"yarn",
[
"workspaces",
"foreach",
"-ipv",
"--no-private",
"npm",
"publish",
"--tolerate-republish",
],
{
cwd: process.cwd(),
},
);
const lines = stdout.split("\n");
const publishedRgx = /\[([@?a-zA-Z\-\/]+)\]:.*Package archive published/;
const publishedPackages = [];
for (const line of lines) {
const didPublish = line.match(publishedRgx);
if (didPublish) {
const pkgName = didPublish[1];
const pkgJson = require(pkgName + "/package.json");
publishedPackages.push(pkgJson);
}
}
return publishedPackages;
}
async function main() {
const githubToken = process.env.GITHUB_TOKEN;
const client = getOctokit(githubToken);
const changesets = await readChangesets(process.cwd());
const shouldRelease = changesets.length === 0;
if (!shouldRelease) {
core.info("changesets exist, skipping publish");
process.exit(0);
}
const publishedPackages = await runPublish();
for (const pkg of publishedPackages) {
const tag_name = `${pkg.name}@${pkg.version}`;
core.info(`creating release for ${tag_name}`);
const body = getChangeLogForRelease(pkg);
await client.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name,
body,
name: tag_name,
});
}
}
main().catch((err) => core.setFailed(err));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment