snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

1
packages/release-action/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
dist/

View File

@@ -0,0 +1,166 @@
# @rocket.chat/release-action
## 2.2.5
### Patch Changes
- ([#41653](https://github.com/RocketChat/Rocket.Chat/pull/41653)) Bumps patch-level dependency updates: `react`, `react-dom` and `react-is` to 19.2.8, `ws` to 8.21.3, `nodemailer` to 9.0.5, `mailparser` to 3.9.15, `google-libphonenumber` to 3.2.46, `twitter-api-v2` to 1.29.1, `ip-range-check` to 0.2.1, `esbuild` to 0.28.2, `@octokit/core` to 7.0.7 and `@octokit/plugin-throttling` to 11.0.5, plus build and test tooling (`@swc/core`, `ts-jest`, `rollup`, `html-webpack-plugin`, `@vitejs/plugin-react`, `@testing-library/user-event`, and the `@types/*` packages). No behavior changes are expected.
## 2.2.5-rc.0
### Patch Changes
- ([#41653](https://github.com/RocketChat/Rocket.Chat/pull/41653)) Bumps patch-level dependency updates: `react`, `react-dom` and `react-is` to 19.2.8, `ws` to 8.21.3, `nodemailer` to 9.0.5, `mailparser` to 3.9.15, `google-libphonenumber` to 3.2.46, `twitter-api-v2` to 1.29.1, `ip-range-check` to 0.2.1, `esbuild` to 0.28.2, `@octokit/core` to 7.0.7 and `@octokit/plugin-throttling` to 11.0.5, plus build and test tooling (`@swc/core`, `ts-jest`, `rollup`, `html-webpack-plugin`, `@vitejs/plugin-react`, `@testing-library/user-event`, and the `@types/*` packages). No behavior changes are expected.
## 2.2.4
### Patch Changes
- ([#38989](https://github.com/RocketChat/Rocket.Chat/pull/38989)) chore(eslint): Upgrades ESLint and its configuration
## 2.2.4-rc.0
### Patch Changes
- ([#38989](https://github.com/RocketChat/Rocket.Chat/pull/38989)) chore(eslint): Upgrades ESLint and its configuration
## 2.2.3
### Patch Changes
- <details><summary>Updated dependencies [b7f5035442]:</summary>
- @rocket.chat/eslint-config@0.7.0
</details>
## 2.2.3-rc.0
### Patch Changes
- <details><summary>Updated dependencies [b7f5035442]:</summary>
- @rocket.chat/eslint-config@0.7.0-rc.0
</details>
## 2.2.2
### Patch Changes
- <details><summary>Updated dependencies [7ac473df7a]:</summary>
- @rocket.chat/eslint-config@0.6.2
</details>
## 2.2.2-rc.0
### Patch Changes
- <details><summary>Updated dependencies [7ac473df7a]:</summary>
- @rocket.chat/eslint-config@0.6.2-rc.0
</details>
## 2.2.1
### Patch Changes
- ([#31138](https://github.com/RocketChat/Rocket.Chat/pull/31138)) feat(uikit): Move `@rocket.chat/ui-kit` package to the main monorepo
- <details><summary>Updated dependencies [642745f289, b223cbde14]:</summary>
- @rocket.chat/eslint-config@0.6.1
</details>
## 2.2.1-rc.0
### Patch Changes
- b223cbde14: feat(uikit): Move `@rocket.chat/ui-kit` package to the main monorepo
- Updated dependencies [642745f289]
- Updated dependencies [b223cbde14]
- @rocket.chat/eslint-config@0.6.1-rc.0
## 2.2.0
### Minor Changes
- f93648a5df: Add back "Engine Versions" to the release notes
### Patch Changes
- Updated dependencies [0f56aacc4d]
- @rocket.chat/eslint-config@0.6.0
## 2.2.0-rc.0
### Minor Changes
- f93648a5df: Add back "Engine Versions" to the release notes
### Patch Changes
- Updated dependencies [0f56aacc4d]
- @rocket.chat/eslint-config@0.6.0-rc.0
## 2.1.0
### Minor Changes
- a56f5f0a5f: Add back "Engine Versions" to the release notes
## 2.0.0
### Major Changes
- 956dbb5520: New action to publish package releases
### Minor Changes
- 0f2f37d4db: Use `release-automation` branch to perform the release
### Patch Changes
- 677919a23e: Use CI user PAT token
- a430336869: Create patch changeset file on every publish
- 677919a23e: Update versions in configured files
## 2.0.0-rc.1
### Patch Changes
- a430336869: Create patch changeset file on every publish
## 2.0.0-rc.0
### Major Changes
- 956dbb5520: New action to publish package releases
### Minor Changes
- 0f2f37d4db: Use `release-automation` branch to perform the release
### Patch Changes
- 677919a23e: Use CI user PAT token
- 677919a23e: Update versions in configured files
## 1.1.0
### Minor Changes
- 63c91f24a9: Use `release-automation` branch to perform the release
## 1.0.1
### Patch Changes
- [#29596](https://github.com/RocketChat/Rocket.Chat/pull/29596) [`a19359b3f5`](https://github.com/RocketChat/Rocket.Chat/commit/a19359b3f5b09270fe1644df0706c6b1ebb979ef) Thanks [@rocketchat-github-ci](https://github.com/rocketchat-github-ci)! - Use CI user PAT token
- [#29596](https://github.com/RocketChat/Rocket.Chat/pull/29596) [`a19359b3f5`](https://github.com/RocketChat/Rocket.Chat/commit/a19359b3f5b09270fe1644df0706c6b1ebb979ef) Thanks [@rocketchat-github-ci](https://github.com/rocketchat-github-ci)! - Update versions in configured files
## 1.0.0
### Major Changes
- [#29545](https://github.com/RocketChat/Rocket.Chat/pull/29545) [`a728082848`](https://github.com/RocketChat/Rocket.Chat/commit/a72808284870af04a6457af6f2f79b0a0c38b7cb) Thanks [@github-actions](https://github.com/apps/github-actions)! - New action to publish package releases

View File

@@ -0,0 +1 @@
# release-action

View File

@@ -0,0 +1,18 @@
name: Changeset release
description: Action to cut and publish releases using changesets
inputs:
action:
description: "The main action to perform: publish, publish-final, bump or patch"
required: true
base-ref:
description: "Base ref to use for the release"
required: false
runs:
using: "node24"
main: "dist/index.js"
branding:
icon: "package"
color: "blue"

View File

@@ -0,0 +1,32 @@
{
"name": "@rocket.chat/release-action",
"version": "2.2.5",
"private": true,
"main": "dist/index.js",
"scripts": {
"build": "tsc --noEmit && esbuild src/index.ts --bundle --platform=node --target=node24 --format=cjs --outfile=dist/index.js",
"lint": "eslint .",
"lint:fix": "eslint --fix src"
},
"dependencies": {
"@actions/core": "^3.0.1",
"@actions/exec": "^3.0.0",
"@actions/github": "^9.1.1",
"@octokit/core": "^7.0.7",
"@octokit/plugin-throttling": "^11.0.5",
"mdast-util-to-string": "2.0.0",
"remark-parse": "9.0.0",
"remark-stringify": "9.0.1",
"semver": "^7.6.3",
"unified": "9.2.2"
},
"devDependencies": {
"@types/node": "~24.13.2",
"esbuild": "~0.28.2",
"eslint": "~9.39.5",
"typescript": "~5.9.3"
},
"volta": {
"extends": "../../package.json"
}
}

View File

@@ -0,0 +1,99 @@
import fs from 'node:fs';
import path from 'node:path';
import * as core from '@actions/core';
import { exec } from '@actions/exec';
import * as github from '@actions/github';
import { createNpmFile } from './createNpmFile';
import { fixWorkspaceVersionsBeforePublish } from './fixWorkspaceVersionsBeforePublish';
import { commitChanges, createBranch, createTag, pushNewBranch } from './gitUtils';
import { setupOctokit } from './setupOctokit';
import { getChangelogEntry, bumpFileVersions, readPackageJson, getEngineVersionsMd, createTempReleaseNotes } from './utils';
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
const { version: currentVersion } = await readPackageJson(cwd);
// 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 { version: newVersion } = await readPackageJson(mainPackagePath);
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 = (await getEngineVersionsMd(cwd)) + changelogEntry.content;
const finalVersion = newVersion.split('-')[0];
const newBranch = `release-${finalVersion}`;
// update root package.json
core.info('update version in all files to new');
await bumpFileVersions(cwd, currentVersion, newVersion);
// TODO check if branch exists
await createBranch(newBranch);
await commitChanges(`Release ${newVersion}`);
core.info('fix dependencies in workspace packages');
await fixWorkspaceVersionsBeforePublish();
await exec('yarn', ['changeset', 'publish', '--no-git-tag']);
await createTag(newVersion);
await pushNewBranch(newBranch, true);
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: createTempReleaseNotes(finalVersion, prBody),
...github.context.repo,
});
} else {
core.info('no pull request created: release is not the first candidate');
}
core.info('create release');
await octokit.rest.repos.createRelease({
name: newVersion,
tag_name: newVersion,
body: prBody,
prerelease: newVersion.includes('-'),
...github.context.repo,
});
}

View File

@@ -0,0 +1,33 @@
import fs from 'node:fs';
import fsPromise from 'node:fs/promises';
import * as core from '@actions/core';
export async function createNpmFile() {
// With trusted publishing (OIDC) there is no token, and a bogus auth line in .npmrc
// takes precedence over the OIDC exchange, so leave the file alone.
if (!process.env.NPM_TOKEN) {
core.info('No NPM_TOKEN provided, relying on the registry authentication already in place');
return;
}
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`);
}
}

View File

@@ -0,0 +1,66 @@
// Changesets doesn't currently support workspace versions:
// https://github.com/changesets/changesets/issues/432
// https://github.com/changesets/action/issues/246
// To work around that, we'll manually resolve any `workspace:` version ranges
// with this tool prior to publishing. If/when changesets adds native support for
// publishing with Yarn 3, we can remove this script.
//
// We'll only support the `workspace:^` range, which is the only one we
// generally want to use.
import fs from 'node:fs/promises';
import path from 'node:path';
import { getExecOutput } from '@actions/exec';
import { readPackageJson } from './utils';
const DEPENDENCY_TYPES = ['dependencies', 'devDependencies', 'peerDependencies'];
export async function fixWorkspaceVersionsBeforePublish() {
const rawWorkspaces = await getExecOutput('yarn workspaces list --json');
const workspaces = rawWorkspaces.stdout
.trim()
.split('\n')
.map((line) => JSON.parse(line))
.filter((workspace) => workspace.location !== '.');
// Get the version of each workspace package.
const workspaceVersions = new Map();
for (const workspace of workspaces) {
const packageJson = await readPackageJson(workspace.location);
workspaceVersions.set(workspace.name, packageJson.version);
}
// Replace any `workspace:^` version ranges with the actual version.
for (const workspace of workspaces) {
const packageJson = await readPackageJson(workspace.location);
for (const dependencyType of DEPENDENCY_TYPES) {
const dependencies = Object.keys(packageJson[dependencyType] ?? {});
for (const dependency of dependencies) {
const dependencyVersion = packageJson[dependencyType][dependency];
if (dependencyVersion.startsWith('workspace:')) {
const realVersion = workspaceVersions.get(dependency);
if (!realVersion) {
throw new Error(`Could not find version for workspace ${dependency}`);
}
const semver = dependencyVersion.slice('workspace:'.length);
if (semver === '*') {
packageJson[dependencyType][dependency] = `=${realVersion}`;
} else if (semver === '^') {
packageJson[dependencyType][dependency] = `^${realVersion}`;
} else if (semver === '~') {
packageJson[dependencyType][dependency] = `~${realVersion}`;
} else {
packageJson[dependencyType][dependency] = semver;
}
}
}
}
await fs.writeFile(path.join(workspace.location, 'package.json'), `${JSON.stringify(packageJson, null, 2)}\n`);
}
}

View File

@@ -0,0 +1,54 @@
import { readFile } from 'node:fs/promises';
import { EOL } from 'node:os';
import path from 'node:path';
import { readPackageJson } from './utils';
export async function getMongoVersion(cwd: string) {
try {
const workflows = await readFile(path.join(cwd, '.github/workflows/ci.yml'), 'utf8');
const mongoMatch = workflows.match(/compatibleMongoVersions\\": \[([^\]]+)\]/);
if (!mongoMatch) {
return [];
}
return mongoMatch[1].replace(/["'\\ ]/g, '').split(',');
} catch (e) {
console.error(e);
}
return [];
}
export async function getNodeNpmVersions(cwd: string): Promise<{ node: string; yarn: string; npm: string }> {
const packageJson = await readPackageJson(cwd);
return packageJson.engines;
}
export async function getAppsEngineVersion(cwd: string) {
try {
const result = await readPackageJson(path.join(cwd, 'packages/apps-engine'));
return result.version ?? 'Not Available';
} catch (e) {
console.error(e);
}
}
export async function getDenoVersion(cwd: string) {
try {
const toolVersionsContent = await readFile(path.join(cwd, '.tool-versions'));
const data = toolVersionsContent.toString().replace(EOL, ' ').split(' ');
for (let i = 0; i < data.length; i++) {
if (data[i] === 'deno') {
return data[i + 1];
}
}
return 'Not available';
} catch (e) {
console.error(e);
}
}

View File

@@ -0,0 +1,52 @@
import { exec, getExecOutput } from '@actions/exec';
export async function setupGitUser() {
await exec('git', ['config', 'user.name', '"rocketchat-github-ci"']);
await exec('git', ['config', 'user.email', '"buildmaster@rocket.chat"']);
}
export async function createBranch(newBranch: string) {
await exec('git', ['checkout', '-b', newBranch]);
}
export async function checkoutBranch(branchName: string) {
await exec('git', ['checkout', branchName]);
}
export async function mergeBranch(branchName: string) {
await exec('git', ['merge', '--no-edit', branchName]);
}
export async function commitChanges(commitMessage: string) {
await exec('git', ['add', '.']);
await exec('git', ['commit', '-m', commitMessage]);
}
export async function createTag(version: string) {
// create an annotated tag so git push --follow-tags will push the tag
await exec('git', ['tag', version, '-m', version]);
}
export async function getCurrentBranch() {
const { stdout: branchName } = await getExecOutput('git', ['rev-parse', '--abbrev-ref', 'HEAD']);
return branchName.trim();
}
export async function pushChanges() {
await exec('git', ['push', '--follow-tags']);
}
export async function pushNewBranch(newBranch: string, force = false) {
const params = ['push'];
if (force) {
params.push('--force');
}
params.push('--follow-tags');
params.push('origin');
params.push(`HEAD:refs/heads/${newBranch}`);
await exec('git', params);
}

View File

@@ -0,0 +1,55 @@
import fs from 'node:fs';
import path from 'node:path';
import * as core from '@actions/core';
import { bumpNextVersion } from './bumpNextVersion';
import { setupGitUser } from './gitUtils';
import { publishRelease } from './publishRelease';
import { startPatchRelease } from './startPatchRelease';
import { updatePRDescription } from './updatePRDescription';
// const getOptionalInput = (name: string) => core.getInput(name) || undefined;
(async () => {
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
core.setFailed('Please add the GITHUB_TOKEN to the changesets action');
return;
}
// const inputCwd = core.getInput('cwd');
// if (inputCwd) {
// core.info('changing directory to the one given as the input');
// process.chdir(inputCwd);
// }
core.info('setting git user');
await setupGitUser();
core.info('setting GitHub credentials');
fs.writeFileSync(`${process.env.HOME}/.netrc`, `machine github.com\nlogin github-actions[bot]\npassword ${githubToken}`);
const action = core.getInput('action');
const baseRef = core.getInput('base-ref');
const cwd = process.cwd();
// TODO this could be configurable
const mainPackagePath = path.join(cwd, 'apps', 'meteor');
if (action === 'publish-final') {
await publishRelease({ githubToken, mergeFinal: true, mainPackagePath });
} else if (action === 'cut') {
await publishRelease({ githubToken, baseRef, mainPackagePath });
} else if (action === 'next') {
await bumpNextVersion({ githubToken, mainPackagePath });
} else if (action === 'patch') {
await startPatchRelease({ baseRef, githubToken, mainPackagePath });
} else if (action === 'update-pr-description') {
await updatePRDescription({ githubToken, mainPackagePath });
}
})().catch((err) => {
core.error(err);
core.setFailed(err.message);
});

View File

@@ -0,0 +1,127 @@
import fs from 'node:fs';
import path from 'node:path';
import * as core from '@actions/core';
import { exec } from '@actions/exec';
import * as github from '@actions/github';
import semver from 'semver';
import { createNpmFile } from './createNpmFile';
import { fixWorkspaceVersionsBeforePublish } from './fixWorkspaceVersionsBeforePublish';
import { checkoutBranch, commitChanges, createTag, getCurrentBranch, mergeBranch, pushChanges } from './gitUtils';
import { setupOctokit } from './setupOctokit';
import { bumpFileVersions, createBumpFile, getChangelogEntry, getEngineVersionsMd, isPreRelease, readPackageJson } from './utils';
export async function publishRelease({
githubToken,
mainPackagePath,
mergeFinal = false,
baseRef,
cwd = process.cwd(),
}: {
githubToken: string;
mainPackagePath: string;
baseRef?: string;
mergeFinal?: boolean;
cwd?: string;
}) {
const octokit = setupOctokit(githubToken);
// TODO do this only if publishing to npm
await createNpmFile();
if (baseRef) {
await checkoutBranch(baseRef);
}
const { version: currentVersion } = await readPackageJson(cwd);
const startAsPreRelease = isPreRelease(cwd);
if (mergeFinal && startAsPreRelease) {
// finish release candidate
await exec('yarn', ['changeset', 'pre', 'exit']);
}
const prerelease = !mergeFinal && startAsPreRelease;
const { name: mainPkgName } = await readPackageJson(mainPackagePath);
// by creating a changeset we make sure we'll always bump the version
core.info('create a changeset for main package');
await createBumpFile(cwd, mainPkgName);
// bump version of all packages
await exec('yarn', ['changeset', 'version']);
// get version from main package
const { version: newVersion } = await readPackageJson(mainPackagePath);
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 = (await getEngineVersionsMd(cwd)) + changelogEntry.content;
core.info('update version in all files to new');
await bumpFileVersions(cwd, currentVersion, newVersion);
await commitChanges(`Release ${newVersion}\n\n[no ci]`);
const { data: latestRelease } = await octokit.rest.repos.getLatestRelease({
...github.context.repo,
});
core.info(`latest release tag: ${latestRelease.tag_name}`);
const isLatestRelease = semver.gt(newVersion, latestRelease.tag_name) && !prerelease;
/**
* These conditions are set to allow a patch release, which will be the latest, to be made without the need to merge into master (normalizing how patch releases are done, always via the 'cut' action)
*
* Strangely before, if mergeFinal was true a checkout was performed and then a push was made, which didn’t make sense because in theory mergeFinal is when merging into master (it was redundant but didn’t cause any issues)
*
* Today, we want that if the action is `cut` and the version is a patch, the merge should be performed, the pull request will automatically be closed, and the release will be made.
* However, if the `cut` is for a pre-release version, the merge to master should not be performed, because minor/major releases are still done manually.
*
* by `mergeFinal` we can know it was triggered by a pull request merge to master
*/
if (!mergeFinal && isLatestRelease) {
// get current branch name
const branchName = await getCurrentBranch();
// merge release changes to master
await checkoutBranch('master');
await mergeBranch(branchName);
await pushChanges();
await checkoutBranch(branchName);
}
core.info('fix dependencies in workspace packages');
await fixWorkspaceVersionsBeforePublish();
await exec('yarn', ['changeset', 'publish', '--no-git-tag']);
await createTag(newVersion);
await pushChanges();
core.info('create release');
await octokit.rest.repos.createRelease({
name: newVersion,
tag_name: newVersion,
body: releaseBody,
prerelease,
make_latest: isLatestRelease ? 'true' : 'false',
...github.context.repo,
});
}

View File

@@ -0,0 +1,28 @@
import * as core from '@actions/core';
import { GitHub, getOctokitOptions } from '@actions/github/lib/utils';
import { throttling } from '@octokit/plugin-throttling';
export const setupOctokit = (githubToken: string) => {
return new (GitHub.plugin(throttling))(
getOctokitOptions(githubToken, {
throttle: {
onRateLimit: (retryAfter: number, options: object, _octokit: unknown, retryCount: number) => {
core.warning(`Request quota exhausted for request ${(options as any).method} ${(options as any).url}`);
if (retryCount <= 2) {
core.info(`Retrying after ${retryAfter} seconds!`);
return true;
}
},
onSecondaryRateLimit: (retryAfter: number, options: object, _octokit: unknown, retryCount: number) => {
core.warning(`SecondaryRateLimit detected for request ${(options as any).method} ${(options as any).url}`);
if (retryCount <= 2) {
core.info(`Retrying after ${retryAfter} seconds!`);
return true;
}
},
},
}),
);
};

View File

@@ -0,0 +1,60 @@
import * as core from '@actions/core';
import * as github from '@actions/github';
import semver from 'semver';
import { checkoutBranch, commitChanges, createBranch, pushNewBranch } from './gitUtils';
import { setupOctokit } from './setupOctokit';
import { createBumpFile, readPackageJson } from './utils';
export async function startPatchRelease({
githubToken,
baseRef,
mainPackagePath,
cwd = process.cwd(),
}: {
baseRef: string;
mainPackagePath: string;
githubToken: string;
cwd?: string;
}) {
const octokit = setupOctokit(githubToken);
await checkoutBranch(baseRef);
// get version from main package
const { version, name: mainPkgName } = await readPackageJson(mainPackagePath);
const newVersion = semver.inc(version, 'patch');
if (!newVersion) {
throw new Error(`Could not increment version ${version}`);
}
const newBranch = `release-${newVersion}`;
// TODO check if branch exists
await createBranch(newBranch);
// by creating a changeset we make sure we'll always bump the version
core.info('create a changeset for main package');
await createBumpFile(cwd, mainPkgName);
await commitChanges(`Bump ${newVersion}`);
await pushNewBranch(newBranch);
// create a pull request only if the patch is for current version
if (baseRef === 'master') {
const finalPrTitle = `Release ${newVersion}`;
core.info('creating pull request');
await octokit.rest.pulls.create({
base: 'master',
head: newBranch,
title: finalPrTitle,
body: '',
...github.context.repo,
});
} else {
core.info('no pull request created: patch is not for current version');
}
}

View File

@@ -0,0 +1,69 @@
import fs from 'node:fs';
import path from 'node:path';
import * as core from '@actions/core';
import { exec } from '@actions/exec';
import * as github from '@actions/github';
import { setupOctokit } from './setupOctokit';
import { createTempReleaseNotes, getChangelogEntry, getEngineVersionsMd, isPreRelease, readPackageJson } from './utils';
export async function updatePRDescription({
githubToken,
mainPackagePath,
cwd = process.cwd(),
}: {
githubToken: string;
mainPackagePath: string;
cwd?: string;
}) {
const octokit = setupOctokit(githubToken);
// in case it is a pre release we exit to get the changelog of the final version
if (isPreRelease(cwd)) {
await exec('yarn', ['changeset', 'pre', 'exit']);
}
// generate change logs from changesets
await exec('yarn', ['changeset', 'version']);
// get version from main package
const { version: newVersion } = await readPackageJson(mainPackagePath);
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');
}
core.info('get PR description');
const result = await octokit.rest.pulls.get({
pull_number: github.context.issue.number,
...github.context.repo,
});
const { body: originalBody = '' } = result.data;
const cleanBody = originalBody?.replace(/<!-- release-notes-start -->.*<!-- release-notes-end -->/s, '').trim() || '';
const releaseBody = (await getEngineVersionsMd(cwd)) + changelogEntry.content;
const bodyUpdated = `${cleanBody}\n${createTempReleaseNotes(newVersion, releaseBody)}`;
if (bodyUpdated === originalBody) {
core.info('no changes to PR description');
return;
}
core.info('update PR description');
await octokit.rest.pulls.update({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
pull_number: github.context.issue.number,
body: bodyUpdated,
});
}

View File

@@ -0,0 +1,149 @@
import fs from 'node:fs';
import { readFile, writeFile } from 'node:fs/promises';
import path from 'node:path';
import mdastToString from 'mdast-util-to-string';
import remarkParse from 'remark-parse';
import remarkStringify from 'remark-stringify';
import unified from 'unified';
import { getAppsEngineVersion, getDenoVersion, getMongoVersion, getNodeNpmVersions } from './getMetadata';
export const BumpLevels = {
dep: 0,
patch: 1,
minor: 2,
major: 3,
} as const;
export function getChangelogEntry(changelog: string, version: string) {
const ast = unified().use(remarkParse).parse(changelog);
let highestLevel: number = BumpLevels.dep;
const nodes = (ast as any).children as Array<any>;
let headingStartInfo:
| {
index: number;
depth: number;
}
| undefined;
let endIndex: number | undefined;
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node.type === 'heading') {
const stringified: string = mdastToString(node);
const match = stringified.toLowerCase().match(/(major|minor|patch)/);
if (match !== null) {
const level = BumpLevels[match[0] as 'major' | 'minor' | 'patch'];
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 as any).children = ((ast as any).children as any).slice(headingStartInfo.index + 1, endIndex);
}
return {
content: unified().use(remarkStringify).stringify(ast),
highestLevel,
};
}
export async function readPackageJson(cwd: string) {
const filePath = path.resolve(cwd, 'package.json');
return JSON.parse(await readFile(filePath, 'utf-8'));
}
async function getUpdateFilesList(cwd: string): Promise<string[]> {
const file = await readPackageJson(cwd);
if (!file.houston) {
return [];
}
const { houston } = file;
if (!houston.updateFiles) {
return [];
}
return houston.updateFiles;
}
export async function bumpFileVersions(cwd: string, oldVersion: string, newVersion: string) {
const files = await getUpdateFilesList(cwd);
await Promise.all(
files.map(async (file) => {
const filePath = path.join(cwd, file);
const data = await readFile(filePath, 'utf8');
await writeFile(filePath, data.replace(oldVersion, newVersion), 'utf8');
}),
);
}
export async function createBumpFile(cwd: string, pkgName: string) {
const filePath = path.join(cwd, '.changeset', `bump-patch-${Date.now()}.md`);
const data = `---
'${pkgName}': patch
---
Bump ${pkgName} version.
`;
await writeFile(filePath, data, 'utf8');
}
export async function getEngineVersionsMd(cwd: string) {
const { node } = await getNodeNpmVersions(cwd);
const appsEngine = await getAppsEngineVersion(cwd);
const deno = await getDenoVersion(cwd);
const mongo = await getMongoVersion(cwd);
return `### Engine versions
- Node: \`${node}\`
- Deno: \`${deno}\`
- MongoDB: \`${mongo.join(', ')}\`
- Apps-Engine: \`${appsEngine}\`
`;
}
export function isPreRelease(cwd: string) {
try {
fs.accessSync(path.resolve(cwd, '.changeset', 'pre.json'));
return true;
} catch (e) {
// nothing to do, not a pre release
}
return false;
}
export function createTempReleaseNotes(version: string, releaseBody: string) {
return `
<!-- release-notes-start -->
<!-- This content is automatically generated. Changing this will not reflect on the final release log -->
_You can see below a preview of the release change log:_
# ${version}
${releaseBody}
<!-- release-notes-end -->`;
}

View File

@@ -0,0 +1,109 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "esnext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
"rootDir": "src/", /* Specify the root folder within your source files. */
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "dist/", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}