voluptasmollitia
Version:
Monorepo for the Firebase JavaScript SDK
213 lines (198 loc) • 7.3 kB
text/typescript
/**
* @license
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { resolve } from 'path';
import { existsSync } from 'fs';
import { exec } from 'child-process-promise';
import chalk from 'chalk';
import simpleGit from 'simple-git/promise';
import fs from 'mz/fs';
const root = resolve(__dirname, '..');
const git = simpleGit(root);
// Version bump text converted to rankable numbers.
const bumpRank: Record<string, number> = {
'patch': 0,
'minor': 1,
'major': 2
};
/**
* Get highest bump that isn't the main firebase package, return
// numerical rank, bump text, package name.
*/
function getHighestBump(changesetPackages: Record<string, string>) {
const firebasePkgJson = require(resolve(
root,
'packages/firebase/package.json'
));
let highestBump = bumpRank.patch;
let highestBumpText = 'patch';
let bumpPackage = '';
for (const pkgName of Object.keys(changesetPackages)) {
if (
pkgName !== 'firebase' &&
pkgName in firebasePkgJson.dependencies &&
bumpRank[changesetPackages[pkgName]] > highestBump
) {
highestBump = bumpRank[changesetPackages[pkgName]];
highestBumpText = changesetPackages[pkgName];
bumpPackage = pkgName;
}
}
return { highestBump, bumpText: highestBumpText, bumpPackage };
}
/**
* Identify modified packages.
*/
async function getDiffData(): Promise<{
changedPackages: Set<string>;
changesetFile: string;
} | null> {
const diff = await git.diff(['--name-only', 'origin/master...HEAD']);
const changedFiles = diff.split('\n');
let changesetFile = '';
const changedPackages = new Set<string>();
for (const filename of changedFiles) {
// Check for an existing .changeset
const changesetMatch = filename.match(/^\.changeset\/[a-zA-Z-]+\.md/);
if (changesetMatch) {
changesetFile = changesetMatch[0];
}
// Check for changed files inside package dirs.
const pkgMatch = filename.match('^(packages(-exp)?/[a-zA-Z0-9-]+)/.*');
if (pkgMatch && pkgMatch[1]) {
// skip packages without package.json
// It could happen when we rename a package or remove a package from the repo
const pkgJsonPath = resolve(root, pkgMatch[1], 'package.json');
if (!existsSync(pkgJsonPath)) {
continue;
}
const changedPackage = require(pkgJsonPath);
if (changedPackage) {
// Add the package itself.
changedPackages.add(changedPackage.name);
}
}
}
if (!changesetFile || changedPackages.size === 0) {
return null;
}
return { changedPackages, changesetFile };
}
async function parseChangesetFile(changesetFile: string) {
const fileExists = await fs.exists(changesetFile);
if (!fileExists) {
process.exit();
}
const fileText: string = await fs.readFile(changesetFile, 'utf8');
const fileParts = fileText.split('---\n');
const packageLines = fileParts[1].split('\n');
const changesetPackages: Record<string, string> = {};
packageLines
.filter(line => line)
.forEach(line => {
const [packageName, bumpType] = line.split(':');
changesetPackages[packageName.replace(/['"]/g, '')] = bumpType.trim();
});
return changesetPackages;
}
async function main() {
const errors = [];
try {
await exec('yarn changeset status');
console.log(`::set-output name=BLOCKING_FAILURE::false`);
} catch (e) {
if (e.message.match('No changesets present')) {
console.log(`::set-output name=BLOCKING_FAILURE::false`);
} else {
const messageLines = e.message.replace(/🦋 error /g, '').split('\n');
let formattedStatusError =
'- Changeset formatting error in following file:%0A';
formattedStatusError += ' ```%0A';
formattedStatusError += messageLines
.filter(
(line: string) => !line.match(/^ at [\w\.]+ \(.+:[0-9]+:[0-9]+\)/)
)
.filter((line: string) => !line.includes('Command failed'))
.filter((line: string) => !line.includes('exited with error code 1'))
.map((line: string) => ` ${line}`)
.join('%0A');
formattedStatusError += '%0A ```%0A';
errors.push(formattedStatusError);
/**
* Sets Github Actions output for a step. Pass changeset error message to next
* step. See:
* https://github.com/actions/toolkit/blob/master/docs/commands.md#set-outputs
*/
console.log(`::set-output name=BLOCKING_FAILURE::true`);
}
}
try {
const diffData = await getDiffData();
if (diffData != null) {
const { changedPackages, changesetFile } = diffData;
const changesetPackages = await parseChangesetFile(changesetFile);
// Check for packages where files were modified but there's no changeset.
const missingPackages = [...changedPackages].filter(
changedPkg => !Object.keys(changesetPackages).includes(changedPkg)
);
if (missingPackages.length > 0) {
const missingPackagesLines = [
'- Warning: This PR modifies files in the following packages but they have not been included in the changeset file:'
];
for (const missingPackage of missingPackages) {
missingPackagesLines.push(` - ${missingPackage}`);
}
missingPackagesLines.push('');
missingPackagesLines.push(' Make sure this was intentional.');
errors.push(missingPackagesLines.join('%0A'));
}
// Check for packages with a minor or major bump where 'firebase' hasn't been
// bumped high enough or at all.
const { highestBump, bumpText, bumpPackage } = getHighestBump(
changesetPackages
);
if (highestBump > bumpRank.patch) {
if (changesetPackages['firebase'] == null) {
errors.push(
`- Package ${bumpPackage} has a ${bumpText} bump which requires an ` +
`additional line to bump the main "firebase" package to ${bumpText}.`
);
console.log(`::set-output name=BLOCKING_FAILURE::true`);
} else if (bumpRank[changesetPackages['firebase']] < highestBump) {
errors.push(
`- Package ${bumpPackage} has a ${bumpText} bump. ` +
`Increase the bump for the main "firebase" package to ${bumpText}.`
);
console.log(`::set-output name=BLOCKING_FAILURE::true`);
}
}
}
} catch (e) {
console.error(chalk`{red ${e}}`);
process.exit(1);
}
/**
* Sets Github Actions output for a step. Pass changeset error message to next
* step. See:
* https://github.com/actions/toolkit/blob/master/docs/commands.md#set-outputs
*/
if (errors.length > 0)
console.log(
`::set-output name=CHANGESET_ERROR_MESSAGE::${errors.join('%0A')}`
);
process.exit();
}
main();