Add beachball change transformer (#9488)

* Add beachball change transformer

Beachball has a relatively recent option, which allows modifying changefiles before trying to bump/publish. We can use this to workaround two common painpoints.

1. Changes backported to 0.69 and later will not need any modification to changefiles when cherry-picking.
2. We do some formatting to create consistency of omiting the version in change messages in our changelogs

* Remove stale file

* Change files

* Fix bug around missing packages

* Fuller JS env in setup

* Simplify logic

* Fix template path

* use remote midgard-yarn on ubuntu agent

* yaml

* Build more

* Move beachball config to its own package

* Consistency

* Cleanup yarn install logic for hosted vs managed images

* Fixup lockfile issues

* Update min to node 14

* Import shared variables and rename

* Variable fixup
This commit is contained in:
Nick Gerleman 2022-02-12 07:11:21 -08:00 коммит произвёл GitHub
Родитель e8f0f0d844
Коммит dfc6ca9c55
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
23 изменённых файлов: 240 добавлений и 121 удалений

Просмотреть файл

@ -2,30 +2,16 @@
jobs:
- job: MacTests
displayName: macOS Tests
variables:
- template: ../variables/macos.yml
pool:
vmImage: $(VmImage)
timeoutInMinutes: 20 # how long to run the job before automatically cancelling
cancelTimeoutInMinutes: 5 # how much time to give 'run always even if cancelled tasks' before killing them
timeoutInMinutes: 20
variables: [template: ../variables/shared.yml]
pool: {vmImage: macOS-10.15}
steps:
- template: ../templates/checkout-shallow.yml
# Explicitly set Node version and install midgard-yarn since we are
# running on a public pool
- task: NodeTool@0
displayName: Set Node Version
inputs:
versionSpec: '16.x'
- script: npm install -g midgard-yarn@1.23.33
displayName: Install midgard-yarn
- template: ../templates/prepare-js-env.yml
parameters:
agentImage: HostedImage
- script: yarn test
displayName: yarn test

Просмотреть файл

@ -7,9 +7,11 @@ parameters:
jobs:
- job: Setup
timeoutInMinutes: 3
pool:
vmImage: ubuntu-latest
displayName: Setup
timeoutInMinutes: 3 # Kill the job early to catch Beachball hangs
variables: [template: ../variables/shared.yml]
pool: {vmImage: ubuntu-latest}
steps:
- template: ../templates/checkout-full.yml
@ -18,8 +20,12 @@ jobs:
- template: ../templates/compute-beachball-branch-name.yml
- script: npm install -g beachball@2.18.0
displayName: Install beachball
- template: ../templates/yarn-install.yml
parameters:
agentImage: HostedImage
- script: npx lage build --scope @rnw-scripts/beachball-config --no-deps
displayName: Minimal build
- ${{ if eq(parameters.buildEnvironment, 'PullRequest') }}:
- script: npx beachball check --branch origin/$(BeachBallBranchName) --verbose --changehint "##vso[task.logissue type=error]Run `yarn change` from root of repo to generate a change file."

Просмотреть файл

@ -1,7 +1,21 @@
# Steps to setup an environment that can run JavaScript executables
parameters:
- name: agentImage
type: string
default: ManagedImage
values:
- ManagedImage
- HostedImage
steps:
- ${{ if eq(parameters.agentImage, 'HostedImage') }}:
- task: NodeTool@0
displayName: Set Node Version
inputs:
versionSpec: '16.x'
- template: yarn-install.yml
parameters:
agentImage: ${{ parameters.agentImage }}
- script: yarn build
displayName: yarn build

Просмотреть файл

@ -2,15 +2,22 @@ parameters:
- name: workingDirectory
type: string
default: .
- name: frozenLockfile
type: boolean
default: true
- name: agentImage
type: string
default: ManagedImage
values:
- ManagedImage
- HostedImage
steps:
- ${{ if eq(parameters.frozenLockfile, true) }}:
# When using our own images, prefer the machine-installed version of
# `midgard-yarn`.
- ${{ if eq(parameters.agentImage, 'ManagedImage') }}:
- script: midgard-yarn --frozen-lockfile --cwd ${{ parameters.workingDirectory }}
displayName: midgard-yarn (faster yarn install)
- ${{ if eq(parameters.frozenLockfile, false) }}:
- script: midgard-yarn --cwd ${{ parameters.workingDirectory }}
# If using an image we don't control, acquire a fixed version of midgard-yarn
# before install
- ${{ else }}:
- script: npx --yes midgard-yarn@1.23.34 --frozen-lockfile --cwd ${{ parameters.workingDirectory }}
displayName: midgard-yarn (faster yarn install)

Просмотреть файл

@ -1,5 +0,0 @@
variables:
- template: shared.yml
- name: VmImage
value: macOS-10.15

1
.gitignore поставляемый
Просмотреть файл

@ -174,5 +174,6 @@ nul
.verdaccio-db.json
# Graveyard of old packages (whose build-outputs may exist on disk after pull)
/packages/@rnw-bots/*
/packages/jest-environment-winappdriver/*
/packages/node-rnw-rpc/*

2
.vscode/extensions.json поставляемый
Просмотреть файл

@ -4,5 +4,7 @@
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"orta.vscode-jest",
"ms-azure-devops.azure-pipelines",
"streetsidesoftware.code-spell-checker",
]
}

7
.vscode/settings.json поставляемый
Просмотреть файл

@ -2,10 +2,13 @@
"files.exclude": {
"**/.git": true
},
"files.associations": {
"**/.ado/**/*.yml": "azure-pipelines"
},
"search.exclude": {
"**/node_modules": true,
"**/lib": true,
"**/dist": true
"**/lib/**/*.js": true,
"**/dist/**/*.js": true
},
"editor.formatOnSave": true,
"eslint.format.enable": true,

Просмотреть файл

@ -1,26 +1 @@
/**
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
* @format
* @ts-check
*/
const {execSync} = require('child_process');
module.exports = {
...require(`${__dirname}/packages/@rnw-scripts/generated-beachball-config/beachball.config.g.json`),
// Do not generate tags for monorepo packages by default, to avoid a GitHub
// release for every package.
gitTags: false,
hooks: {
// Stamp versions when we publish a new package
postbump: (_packagePath, name, version) => {
if (name === 'react-native-windows') {
console.log(`Stamping RNW Version ${version}`);
execSync(`yarn stamp-version ${version}`);
}
}
}
}
module.exports = require("@rnw-scripts/beachball-config");

Просмотреть файл

@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Sync variants",
"packageName": "@react-native-windows/find-repo-root",
"email": "ngerlem@microsoft.com",
"dependentChangeType": "patch"
}

Просмотреть файл

@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Sync variants",
"packageName": "@react-native-windows/package-utils",
"email": "ngerlem@microsoft.com",
"dependentChangeType": "patch"
}

Просмотреть файл

@ -1,7 +0,0 @@
{
"type": "prerelease",
"comment": "Use PackageReference for C++ dependencies",
"packageName": "node-rnw-rpc",
"email": "julio.rocha@microsoft.com",
"dependentChangeType": "patch"
}

Просмотреть файл

@ -16,6 +16,7 @@
"directory": "packages/@react-native-windows/find-repo-root"
},
"dependencies": {
"@react-native-windows/fs": "^1.0.1",
"find-up": "^4.1.0"
},
"devDependencies": {

Просмотреть файл

@ -7,12 +7,13 @@
*/
import findUp from 'find-up';
import fs from '@react-native-windows/fs';
import path from 'path';
/**
* Find the root directory of a repo upward from cwd
*/
export default async (): Promise<string> => {
async function findRepoRoot(): Promise<string> {
const root = await findUp(
async (dir: string): Promise<findUp.Match> => {
return (await findUp.exists(path.join(dir, '.git'))) ? dir : undefined;
@ -27,4 +28,27 @@ export default async (): Promise<string> => {
}
return root;
};
}
/**
* Synchronously finds the root directory of a repo upward from cwd
*/
function findRepoRootSync(): string {
const root = findUp.sync(
(dir: string): findUp.Match => {
return fs.existsSync(path.join(dir, '.git')) ? dir : undefined;
},
{type: 'directory'},
);
if (!root) {
throw new Error(
'Unable to find the root of react-native-windows. Are you running within the repo?',
);
}
return root;
}
const exportObj = Object.assign(findRepoRoot, {sync: findRepoRootSync});
export default exportObj;

Просмотреть файл

@ -120,6 +120,23 @@ export async function enumerateRepoPackages(
return filteredPackages;
}
/**
* Synchronously Finds monorepo-local packages matching a given predicate. The
* root package is not included.
*
* @param pred predicate describing whether to match a package
*/
export function enumerateRepoPackagesSync(
pred: (pkg: NpmPackage) => boolean = () => true,
): WritableNpmPackage[] {
const repoRoot = findRepoRoot.sync();
const allPackges = getMonorepoPackages(repoRoot).map(
(pkg) => new WritableNpmPackage(pkg.location, pkg.package),
);
return allPackges.filter(pred);
}
/**
* Finds a package with a given name (local or dependency)
*/
@ -164,3 +181,16 @@ export async function findRepoPackage(
return packages[0];
}
}
/**
* Synchronously a monorepo-local package with a given name
*/
export function findRepoPackageSync(name: string): WritableNpmPackage | null {
const packages = enumerateRepoPackagesSync((p) => p.json.name === name);
if (packages.length === 0) {
return null;
} else {
return packages[0];
}
}

Просмотреть файл

@ -1,3 +0,0 @@
import { Context } from "@azure/functions";
declare const _default: (context: Context) => Promise<void>;
export default _default;

Просмотреть файл

@ -1,6 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = async (context) => {
context.log('Hello world');
};
//# sourceMappingURL=index.js.map

Просмотреть файл

@ -1 +0,0 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../HeartbeatFunction/index.ts"],"names":[],"mappings":";;AAEA,kBAAe,KAAK,EAAE,OAAgB,EAAE,EAAE;IACtC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;AAC/B,CAAC,CAAC","sourcesContent":["import { Context } from \"@azure/functions\"\n\nexport default async (context: Context) => {\n context.log('Hello world');\n};\n"]}

Просмотреть файл

@ -1,38 +0,0 @@
{
"name": "@rnw-bots/coordinator",
"version": "1.0.12",
"license": "MIT",
"description": "Azure functions application for coordinating react-native-windows bots",
"repository": {
"type": "git",
"url": "https://github.com/microsoft/react-native-windows.git",
"directory": "packages/@rnw-bots/coordinator"
},
"scripts": {
"build": "rnw-scripts build",
"clean": "rnw-scripts clean",
"lint": "rnw-scripts lint",
"lint:fix": "rnw-scripts lint:fix",
"start": "func start --typescript",
"test": "rnw-scripts test",
"watch": "rnw-scripts watch"
},
"dependencies": {},
"devDependencies": {
"@azure/functions": "^1.0.2-beta2",
"@rnw-scripts/eslint-config": "1.1.9",
"@rnw-scripts/jest-unittest-config": "1.2.4",
"@rnw-scripts/just-task": "2.2.1",
"@rnw-scripts/ts-config": "2.0.1",
"@types/node": "^14.14.22",
"babel-jest": "^26.3.0",
"eslint": "^7.32.0",
"jest": "^26.6.3",
"just-scripts": "^1.3.3",
"prettier": "^2.4.1",
"typescript": "^4.4.4"
},
"engines": {
"node": ">= 14"
}
}

2
packages/@rnw-scripts/beachball-config/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,2 @@
lib/
lib-commonjs/

Просмотреть файл

@ -0,0 +1,41 @@
{
"name": "@rnw-scripts/beachball-config",
"private": true,
"version": "0.0.0",
"license": "MIT",
"scripts": {
"build": "rnw-scripts build",
"clean": "rnw-scripts clean",
"lint": "rnw-scripts lint",
"lint:fix": "rnw-scripts lint:fix",
"watch": "rnw-scripts watch"
},
"main": "lib-commonjs/beachball.config.js",
"repository": {
"type": "git",
"url": "https://github.com/microsoft/react-native-windows.git",
"directory": "packages/@rnw-scripts/beachball-config"
},
"dependencies": {
"@react-native-windows/package-utils": "0.0.0-canary.25",
"@rnw-scripts/stamp-version": "0.0.0",
"find-up": "^4.1.0"
},
"devDependencies": {
"@rnw-scripts/eslint-config": "1.1.11",
"@rnw-scripts/just-task": "2.2.3",
"@rnw-scripts/ts-config": "2.0.2",
"@types/node": "^14.14.22",
"beachball": "^2.20.0",
"eslint": "^7.32.0",
"just-scripts": "^1.3.3",
"prettier": "^2.4.1",
"typescript": "^4.4.4"
},
"files": [
"lib-commonjs"
],
"engines": {
"node": ">= 14.0.0"
}
}

Просмотреть файл

@ -0,0 +1,68 @@
/**
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*
* @format
*/
import {execSync} from 'child_process';
import {findRepoPackageSync} from '@react-native-windows/package-utils';
import type {BeachballOptions} from 'beachball/lib/types/BeachballOptions';
import type {ChangeInfo} from 'beachball/lib/types/ChangeInfo';
const Options: BeachballOptions = {
...require("@rnw-scripts/generated-beachball-config"),
// Do not generate tags for monorepo packages by default, to avoid a GitHub
// release for every package.
gitTags: false,
hooks: {
// Stamp versions when we publish a new package
postbump: (_packagePath, name, version) => {
if (name === 'react-native-windows') {
console.log(`Stamping RNW Version ${version}`);
execSync(`yarn stamp-version ${version}`);
}
}
},
transform: {
changeFiles: (changeInfo) => Array.isArray(changeInfo.changes)
? {...changeInfo, changes: changeInfo.changes.map(transformChangeInfo)}
: transformChangeInfo(changeInfo as ChangeInfo),
}
}
function transformChangeInfo(changeInfo: ChangeInfo) : ChangeInfo {
return {
...changeInfo,
type: correctChangeType(changeInfo),
comment: formatComment(changeInfo.comment),
};
}
function correctChangeType(changeInfo: ChangeInfo) {
// Changes made to our main branch are often rolled into prerelease packages,
// where a released branch should treat these changes as creating a new patch
// release.
if (changeInfo.type === 'prerelease' && !isPrerelease(changeInfo.packageName)) {
return 'patch';
}
return changeInfo.type;
}
function isPrerelease(packageName: string): boolean {
const packageJson = findRepoPackageSync(packageName)?.json;
return packageJson && packageJson.version.includes('-');
}
function formatComment(comment: string): string {
// Remove versions from messages that look like "[0.xx] Message"
return comment.match(/(\s*\[[\d\.]+\]\s*)?((.|\n)*)/)?.[2] ?? comment;
}
module.exports = Options;

Просмотреть файл

@ -0,0 +1,5 @@
{
"extends": "@rnw-scripts/ts-config",
"include": ["src"],
"exclude": ["node_modules"]
}