diff --git a/tools/gulp/gulpfile.ts b/tools/gulp/gulpfile.ts index c7bd8048df41..2def6858c270 100644 --- a/tools/gulp/gulpfile.ts +++ b/tools/gulp/gulpfile.ts @@ -28,7 +28,8 @@ import './tasks/example-module'; import './tasks/lint'; import './tasks/material-release'; import './tasks/payload'; -import './tasks/publish'; import './tasks/unit-test'; import './tasks/universal'; -import './tasks/validate-release'; + +import './tasks/publish/publish-task'; +import './tasks/publish/validate-release'; diff --git a/tools/gulp/tasks/publish/branch-check.ts b/tools/gulp/tasks/publish/branch-check.ts new file mode 100644 index 000000000000..33ff59919744 --- /dev/null +++ b/tools/gulp/tasks/publish/branch-check.ts @@ -0,0 +1,61 @@ +import {bold} from 'chalk'; +import {spawnSync} from 'child_process'; +import {buildConfig} from 'material2-build-tools'; + +/** Regular expression that matches version names and the individual version segments. */ +export const versionNameRegex = /^(\d+)\.(\d+)\.(\d+)(?:-(alpha|beta|rc)\.(\d)+)?/; + +/** Regular expression that matches publish branch names and their Semver digits. */ +const publishBranchNameRegex = /^([0-9]+)\.([x0-9]+)(?:\.([x0-9]+))?$/; + +/** Checks if the specified version can be released from the current Git branch. */ +export function checkPublishBranch(version: string) { + const versionType = getSemverVersionType(version); + const branchName = spawnSync('git', ['symbolic-ref', '--short', 'HEAD'], + {cwd: buildConfig.projectDir}).stdout.toString().trim(); + + if (branchName === 'master') { + if (versionType === 'major') { + return; + } + + throw `Publishing of "${versionType}" releases should not happen inside of the ` + + `${bold('master')} branch.`; + } + + const branchNameMatch = branchName.match(publishBranchNameRegex) || []; + const branchDigits = branchNameMatch.slice(1, 4); + + if (branchDigits[2] === 'x' && versionType !== 'patch') { + throw `Cannot publish a "${versionType}" release inside of a patch branch (${branchName})`; + } + + if (branchDigits[1] === 'x' && versionType !== 'minor') { + throw `Cannot publish a "${versionType}" release inside of a minor branch (${branchName})`; + } + + throw `Cannot publish a "${versionType}" release from branch: "${branchName}". Releases should ` + + `be published from "master" or the according publish branch (e.g. "6.x", "6.4.x")`; +} + +/** + * Determines the type of the specified semver version. Can be either a major, minor or + * patch version. + */ +export function getSemverVersionType(version: string): 'major' | 'minor' | 'patch' { + const versionNameMatch = version.match(versionNameRegex); + + if (!versionNameMatch) { + throw `Could not parse version: ${version}. Cannot properly determine version type.`; + } + + const versionDigits = versionNameMatch.slice(1, 4); + + if (versionDigits[1] === '0' && versionDigits[2] === '0') { + return 'major'; + } else if (versionDigits[2] === '0') { + return 'minor'; + } else { + return 'patch'; + } +} diff --git a/tools/gulp/tasks/publish.ts b/tools/gulp/tasks/publish/publish-task.ts similarity index 76% rename from tools/gulp/tasks/publish.ts rename to tools/gulp/tasks/publish/publish-task.ts index 0cf92199b6d0..9b3faa5ba6fc 100644 --- a/tools/gulp/tasks/publish.ts +++ b/tools/gulp/tasks/publish/publish-task.ts @@ -1,11 +1,12 @@ +import {green, grey, red, yellow} from 'chalk'; import {spawn} from 'child_process'; import {existsSync, statSync} from 'fs-extra'; -import {join} from 'path'; import {task} from 'gulp'; -import {execTask} from '../util/task_helpers'; import {buildConfig, sequenceTask} from 'material2-build-tools'; -import {yellow, green, red, grey} from 'chalk'; import * as minimist from 'minimist'; +import {join} from 'path'; +import {execTask} from '../../util/task_helpers'; +import {checkPublishBranch, versionNameRegex} from './branch-check'; /** Packages that will be published to NPM by the release task. */ export const releasePackages = [ @@ -16,9 +17,6 @@ export const releasePackages = [ 'material-moment-adapter' ]; -/** Regular Expression that matches valid version numbers of Angular Material. */ -export const validVersionRegex = /^\d+\.\d+\.\d+(-(alpha|beta|rc)\.\d+)?$/; - /** Parse command-line arguments for release task. */ const argv = minimist(process.argv.slice(3)); @@ -50,38 +48,39 @@ task(':publish', async () => { const version = buildConfig.projectVersion; const currentDir = process.cwd(); - if (!version.match(validVersionRegex)) { - console.log(red(`Error: Cannot publish due to an invalid version name. Version "${version}" ` + - `is not following our semver format.`)); - console.log(yellow(`A version should follow this format: X.X.X, X.X.X-beta.X, X.X.X-alpha.X, ` + - `X.X.X-rc.X`)); + if (!version.match(versionNameRegex)) { + console.error(red(`Error: Cannot publish due to an invalid version name. Version ` + + `"${version}" is not following our semver format.`)); + console.error(yellow(`A version should follow this format: X.X.X, X.X.X-beta.X, ` + + `X.X.X-alpha.X, X.X.X-rc.X`)); return; } - console.log(''); + console.log(); if (!tag) { console.log(grey('> You can specify the tag by passing --tag=labelName.\n')); console.log(green(`Publishing version "${version}" to the latest tag...`)); } else { console.log(yellow(`Publishing version "${version}" to the ${tag} tag...`)); } - console.log(''); - + console.log(); if (version.match(/(alpha|beta|rc)/) && (!tag || tag === 'latest')) { - console.log(red(`Publishing ${version} to the "latest" tag is not allowed.`)); - console.log(red(`Alpha, Beta or RC versions shouldn't be published to "latest".`)); - console.log(); + console.error(red(`Publishing ${version} to the "latest" tag is not allowed.`)); + console.error(red(`Alpha, Beta or RC versions shouldn't be published to "latest".`)); + console.error(); return; } if (releasePackages.length > 1) { - console.warn(red('Warning: Multiple packages will be released if proceeding.')); - console.warn(red('Warning: Packages to be released:', releasePackages.join(', '))); - console.log(); + console.warn(yellow('Warning: Multiple packages will be released.')); + console.warn(yellow('Warning: Packages to be released:', releasePackages.join(', '))); + console.warn(); } - console.log(yellow('> Make sure to check the "angularVersion" in the build config.')); + checkPublishBranch(version); + + console.log(yellow('> Make sure to check the "requiredAngularVersion" in the package.json.')); console.log(yellow('> The version in the config defines the peer dependency of Angular.')); console.log(); diff --git a/tools/gulp/tasks/validate-release.ts b/tools/gulp/tasks/publish/validate-release.ts similarity index 97% rename from tools/gulp/tasks/validate-release.ts rename to tools/gulp/tasks/publish/validate-release.ts index b6ba651401a7..2857778058d2 100644 --- a/tools/gulp/tasks/validate-release.ts +++ b/tools/gulp/tasks/publish/validate-release.ts @@ -2,7 +2,7 @@ import {task} from 'gulp'; import {readFileSync, existsSync} from 'fs'; import {join} from 'path'; import {green, red} from 'chalk'; -import {releasePackages} from './publish'; +import {releasePackages} from './publish-task'; import {sync as glob} from 'glob'; import {spawnSync} from 'child_process'; import {buildConfig, sequenceTask} from 'material2-build-tools'; @@ -10,7 +10,7 @@ import {buildConfig, sequenceTask} from 'material2-build-tools'; const {projectDir, projectVersion, outputDir} = buildConfig; /** Git repository URL that has been read out from the project package.json file. */ -const repositoryGitUrl = require('../../../package.json').repository.url; +const repositoryGitUrl = require('../../../../package.json').repository.url; /** Path to the directory where all releases are created. */ const releasesDir = join(outputDir, 'releases');