diff --git a/tools/gulp/tasks/publish.ts b/tools/gulp/tasks/publish.ts index 6c65a3eb666..0cf92199b6d 100644 --- a/tools/gulp/tasks/publish.ts +++ b/tools/gulp/tasks/publish.ts @@ -25,6 +25,7 @@ const argv = minimist(process.argv.slice(3)); task('publish', sequenceTask( ':publish:whoami', ':publish:build-releases', + 'validate-release:check-remote-tag', 'validate-release:check-bundles', ':publish', ':publish:logout', @@ -52,8 +53,8 @@ task(':publish', async () => { 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: d.d.d, d.d.d-beta.x, d.d.d-alpha.x, ` + - `d.d.d-rc.x`)); + 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`)); return; } diff --git a/tools/gulp/tasks/validate-release.ts b/tools/gulp/tasks/validate-release.ts index 1c8a266865a..b6ba651401a 100644 --- a/tools/gulp/tasks/validate-release.ts +++ b/tools/gulp/tasks/validate-release.ts @@ -4,10 +4,16 @@ import {join} from 'path'; import {green, red} from 'chalk'; import {releasePackages} from './publish'; import {sync as glob} from 'glob'; +import {spawnSync} from 'child_process'; 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; + /** Path to the directory where all releases are created. */ -const releasesDir = join(buildConfig.outputDir, 'releases'); +const releasesDir = join(outputDir, 'releases'); /** RegExp that matches Angular component inline styles that contain a sourcemap reference. */ const inlineStylesSourcemapRegex = /styles: ?\[["'].*sourceMappingURL=.*["']/; @@ -17,6 +23,18 @@ const externalReferencesRegex = /(templateUrl|styleUrls): *["'[]/; task('validate-release', sequenceTask(':publish:build-releases', 'validate-release:check-bundles')); +task('validate-release:check-remote-tag', () => { + // Since we cannot assume that every developer uses `origin` as the default name for the upstream + // remote, we just pass in the Git URL that refers to angular/material2 repository on Github. + const tagCommitSha = spawnSync('git', ['ls-remote', '--tags', repositoryGitUrl, projectVersion], + {cwd: projectDir}).stdout.toString().trim(); + + if (!tagCommitSha) { + throw Error(red(`Cannot publish v${projectVersion} because the release is not ` + + `tagged on upstream yet. Please tag the release before publishing to NPM.`)); + } +}); + /** Task that checks the release bundles for any common mistakes before releasing to the public. */ task('validate-release:check-bundles', () => { const releaseFailures = releasePackages diff --git a/tools/package-tools/build-release.ts b/tools/package-tools/build-release.ts index f3fc35f3dff..1f44b1f4b6b 100644 --- a/tools/package-tools/build-release.ts +++ b/tools/package-tools/build-release.ts @@ -7,6 +7,7 @@ import {copyFiles} from './copy-files'; import {createEntryPointPackageJson} from './entry-point-package-json'; import {inlinePackageMetadataFiles} from './metadata-inlining'; import {createMetadataReexportFile} from './metadata-reexport'; +import {insertPackageJsonVersionStamp} from './package-version-stamp'; import {createTypingsReexportFile} from './typings-reexport'; import {replaceVersionPlaceholders} from './version-placeholders'; @@ -48,6 +49,8 @@ export function composeRelease(buildPackage: BuildPackage) { copyFiles(sourceDir, 'package.json', releasePath); replaceVersionPlaceholders(releasePath); + insertPackageJsonVersionStamp(join(releasePath, 'package.json')); + createTypingsReexportFile(releasePath, './typings/index', name); createMetadataReexportFile(releasePath, './typings/index', name, importAsName); diff --git a/tools/package-tools/package-version-stamp.ts b/tools/package-tools/package-version-stamp.ts new file mode 100644 index 00000000000..fab20422f7a --- /dev/null +++ b/tools/package-tools/package-version-stamp.ts @@ -0,0 +1,42 @@ +import {writeFileSync} from 'fs'; +import {spawnSync} from 'child_process'; +import {buildConfig} from './build-config'; + +/** + * Inserts the a version stamp, which consists of the current commit SHA and the current + * branch name, into the specified package.json file. + * + * This makes it easy to quickly verify the exact snapshot from which the release originated. + */ +export function insertPackageJsonVersionStamp(packageJsonPath: string) { + const packageJson = require(packageJsonPath); + + packageJson['releaseGitCommitSha'] = getCurrentCommitSha(); + packageJson['releaseGitBranch'] = getCurrentBranchName(); + packageJson['releaseGitUser'] = getCurrentGitUser(); + + writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); +} + +/** Returns the commit SHA for the current git HEAD of the project. */ +function getCurrentCommitSha(): string { + return spawnSync('git', ['rev-parse', 'HEAD'], {cwd: buildConfig.projectDir}) + .stdout.toString().trim(); +} + +/** Returns the name of the currently checked out branch of the project. */ +function getCurrentBranchName(): string { + return spawnSync('git', ['symbolic-ref', '--short', 'HEAD'], {cwd: buildConfig.projectDir}) + .stdout.toString().trim(); +} + +/** Returns the name and email of the Git user that creates this release build. */ +function getCurrentGitUser() { + const userName = spawnSync('git', ['config', 'user.name'], {cwd: buildConfig.projectDir}) + .stdout.toString().trim(); + + const userEmail = spawnSync('git', ['config', 'user.email'], {cwd: buildConfig.projectDir}) + .stdout.toString().trim(); + + return `${userName} <${userEmail}>`; +}