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: New action to publish releases #29519

Merged
merged 8 commits into from
Jun 14, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tiny-cups-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/release-action': major
---

New action to publish package releases
57 changes: 57 additions & 0 deletions .github/workflows/new-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Start new release

on:
workflow_dispatch:
inputs:
name:
type: choice
description: Release type
default: next
required: true
options:
- next
- patch
base-ref:
description: Base version
default: master
required: false

jobs:
new-release:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
# with:
# ref: ${{ github.event.inputs.base-ref }}

- name: Setup Node.js 16
uses: actions/setup-node@v3
with:
node-version: 16

- run: yarn install

- name: Build
run: yarn build

- name: Start next release
if: ${{ github.event.inputs.name == 'next' }}
uses: ./packages/release-action
with:
action: bump
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Start patch release
if: ${{ github.event.inputs.name == 'patch' }}
uses: ./packages/release-action
with:
action: patch
base-ref: ${{ github.event.inputs.base-ref }}
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35 changes: 35 additions & 0 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Publish Release

on:
push:
branches:
- master

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

jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v3

- name: Setup Node.js 16
uses: actions/setup-node@v3
with:
node-version: 16

- name: Install Dependencies
run: yarn

- name: Build
run: yarn build

- name: Cut release
uses: ./packages/release-action
with:
action: cut
sampaiodiego marked this conversation as resolved.
Show resolved Hide resolved
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4 changes: 4 additions & 0 deletions packages/release-action/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": ["@rocket.chat/eslint-config"],
"ignorePatterns": ["**/dist"]
}
1 change: 1 addition & 0 deletions packages/release-action/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist/
1 change: 1 addition & 0 deletions packages/release-action/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# @rocket.chat/release-action
1 change: 1 addition & 0 deletions packages/release-action/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# release-action
18 changes: 18 additions & 0 deletions packages/release-action/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Changeset release
description: Action to cut and publish releases using changesets

inputs:
action:
description: "The main action to perform: cut, bump, or both"
required: true
base-ref:
description: "Base ref to use for the release"
required: false

runs:
using: "node16"
main: "dist/index.js"

branding:
icon: "package"
color: "blue"
30 changes: 30 additions & 0 deletions packages/release-action/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@rocket.chat/release-action",
"version": "0.0.1",
"private": true,
"scripts": {
"build": "tsc",
"lint": "eslint src",
"lint:fix": "eslint --fix src"
},
"main": "dist/index.js",
"packageManager": "yarn@3.5.1",
"devDependencies": {
"@types/eslint": "^8",
"@types/node": "^16",
"typescript": "^5.1.3"
},
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/exec": "^1.1.1",
"@actions/github": "^5.1.1",
"@octokit/plugin-throttling": "^6.0.0",
"@rocket.chat/eslint-config": "workspace:^",
"eslint": "^8.42.0",
"mdast-util-to-string": "2",
"remark-parse": "9",
"remark-stringify": "9",
"semver": "^7.5.1",
"unified": "9"
}
}
92 changes: 92 additions & 0 deletions packages/release-action/src/bumpNextVersion.ts
sampaiodiego marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import fs from 'fs';
import path from 'path';

import { exec } from '@actions/exec';
import * as core from '@actions/core';
import * as github from '@actions/github';

import { setupOctokit } from './setupOctokit';
import { createNpmFile } from './createNpmFile';
import { getChangelogEntry, updateVersionPackageJson } from './utils';
import { fixWorkspaceVersionsBeforePublish } from './fixWorkspaceVersionsBeforePublish';

export async function bumpNextVersion({
githubToken,
mainPackagePath,
cwd = process.cwd(),
}: {
githubToken: string;
mainPackagePath: string;
cwd?: string;
}) {
const octokit = setupOctokit(githubToken);

// TODO do this only if publishing to npm
await createNpmFile();

// TODO need to check if there is any change to 'main package', if not, there is no need to enter rc
// and instead a normal release of the other packages should be done

// start release candidate
await exec('yarn', ['changeset', 'pre', 'enter', 'rc']);

// bump version of all packages to rc
await exec('yarn', ['changeset', 'version']);

// get version from main package
const mainPackageJsonPath = path.join(mainPackagePath, 'package.json');
// eslint-disable-next-line import/no-dynamic-require, @typescript-eslint/no-var-requires
const { version: newVersion } = require(mainPackageJsonPath);

const mainPackageChangelog = path.join(mainPackagePath, 'CHANGELOG.md');

const changelogContents = fs.readFileSync(mainPackageChangelog, 'utf8');
const changelogEntry = getChangelogEntry(changelogContents, newVersion);
if (!changelogEntry) {
// we can find a changelog but not the entry for this version
// if this is true, something has probably gone wrong
throw new Error('Could not find changelog entry for version newVersion');
}

const prBody = changelogEntry.content;

const finalVersion = newVersion.split('-')[0];

const newBranch = `release-${finalVersion}`;

// update root package.json
updateVersionPackageJson(cwd, newVersion);

// TODO check if branch exists
await exec('git', ['checkout', '-b', newBranch]);

await exec('git', ['add', '.']);
await exec('git', ['commit', '-m', newVersion]);

await fixWorkspaceVersionsBeforePublish();

await exec('yarn', ['changeset', 'publish']);

await exec('git', ['push', '--force', '--follow-tags', 'origin', `HEAD:refs/heads/${newBranch}`]);

if (newVersion.includes('rc.0')) {
const finalPrTitle = `Release ${finalVersion}`;

core.info('creating pull request');
await octokit.rest.pulls.create({
base: 'master',
head: newBranch,
title: finalPrTitle,
body: prBody,
...github.context.repo,
});
}

await octokit.rest.repos.createRelease({
name: newVersion,
tag_name: newVersion,
body: prBody,
prerelease: newVersion.includes('-'),
...github.context.repo,
});
}
26 changes: 26 additions & 0 deletions packages/release-action/src/createNpmFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import fs from 'fs';
import fsPromise from 'fs/promises';

import * as core from '@actions/core';

export async function createNpmFile() {
const userNpmrcPath = `${process.env.HOME}/.npmrc`;

if (fs.existsSync(userNpmrcPath)) {
core.info('Found existing user .npmrc file');
const userNpmrcContent = await fsPromise.readFile(userNpmrcPath, 'utf8');
const authLine = userNpmrcContent.split('\n').find((line) => {
// check based on https://github.com/npm/cli/blob/8f8f71e4dd5ee66b3b17888faad5a7bf6c657eed/test/lib/adduser.js#L103-L105
return /^\s*\/\/registry\.npmjs\.org\/:[_-]authToken=/i.test(line);
});
if (authLine) {
core.info('Found existing auth token for the npm registry in the user .npmrc file');
} else {
core.info("Didn't find existing auth token for the npm registry in the user .npmrc file, creating one");
fs.appendFileSync(userNpmrcPath, `\n//registry.npmjs.org/:_authToken=${process.env.NPM_TOKEN}\n`);
}
} else {
core.info('No user .npmrc file found, creating one');
fs.writeFileSync(userNpmrcPath, `//registry.npmjs.org/:_authToken=${process.env.NPM_TOKEN}\n`);
}
}
81 changes: 81 additions & 0 deletions packages/release-action/src/cutFinalRelease.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import fs from 'fs';
import path from 'path';

import { exec } from '@actions/exec';
import * as github from '@actions/github';

import { createNpmFile } from './createNpmFile';
import { setupOctokit } from './setupOctokit';
import { getChangelogEntry, updateVersionPackageJson } from './utils';
import { fixWorkspaceVersionsBeforePublish } from './fixWorkspaceVersionsBeforePublish';

export async function cutFinalRelease({
githubToken,
mainPackagePath,
cwd = process.cwd(),
}: {
githubToken: string;
mainPackagePath: string;
cwd?: string;
}) {
const octokit = setupOctokit(githubToken);

// TODO do this only if publishing to npm
await createNpmFile();

let preRelease = false;
try {
fs.accessSync(path.resolve(cwd, '.changeset', 'pre.json'));

preRelease = true;
} catch (e) {
// nothing to do, not a pre release
}

if (preRelease) {
// finish release candidate
await exec('yarn', ['changeset', 'pre', 'exit']);
}

// bump version of all packages to rc
await exec('yarn', ['changeset', 'version']);

// TODO if main package has no changes, throw error

// get version from main package
const mainPackageJsonPath = path.join(mainPackagePath, 'package.json');
// eslint-disable-next-line import/no-dynamic-require, @typescript-eslint/no-var-requires
const { version: newVersion } = require(mainPackageJsonPath);

const mainPackageChangelog = path.join(mainPackagePath, 'CHANGELOG.md');

const changelogContents = fs.readFileSync(mainPackageChangelog, 'utf8');
const changelogEntry = getChangelogEntry(changelogContents, newVersion);
if (!changelogEntry) {
// we can find a changelog but not the entry for this version
// if this is true, something has probably gone wrong
throw new Error('Could not find changelog entry for version newVersion');
}

const releaseBody = changelogEntry.content;

// update root package.json
updateVersionPackageJson(cwd, newVersion);

await exec('git', ['add', '.']);
await exec('git', ['commit', '-m', newVersion]);

await fixWorkspaceVersionsBeforePublish();

await exec('yarn', ['changeset', 'publish']);

await exec('git', ['push', '--follow-tags']);

await octokit.rest.repos.createRelease({
name: newVersion,
tag_name: newVersion,
body: releaseBody,
prerelease: newVersion.includes('-'),
...github.context.repo,
});
}