UNPKG

voluptasmollitia

Version:
213 lines (198 loc) 7.3 kB
/** * @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();