Skip to content

Commit 26f2e9c

Browse files
committed
build: add validation check for migration collection
1 parent 5a22c09 commit 26f2e9c

File tree

7 files changed

+114
-21
lines changed

7 files changed

+114
-21
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
"@types/node": "^12.11.1",
9494
"@types/parse5": "^5.0.0",
9595
"@types/run-sequence": "^0.0.29",
96+
"@types/semver": "^6.2.0",
9697
"@types/send": "^0.14.5",
9798
"autoprefixer": "^6.7.6",
9899
"axe-webdriverjs": "^1.1.1",
@@ -147,6 +148,7 @@
147148
"run-sequence": "^1.2.2",
148149
"scss-bundle": "^2.5.1",
149150
"selenium-webdriver": "^3.6.0",
151+
"semver": "^6.3.0",
150152
"send": "^0.17.1",
151153
"shelljs": "^0.8.3",
152154
"sorcery": "^0.10.0",

tools/release/check-release-output.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ import chalk from 'chalk';
22
import {join} from 'path';
33
import {checkReleasePackage} from './release-output/check-package';
44
import {releasePackages} from './release-output/release-packages';
5+
import {parseVersionName, Version} from './version-name/parse-version';
56

67
/**
78
* Checks the release output by running the release-output validations for each
89
* release package.
910
*/
10-
export function checkReleaseOutput(releaseOutputDir: string) {
11+
export function checkReleaseOutput(releaseOutputDir: string, currentVersion: Version) {
1112
let hasFailed = false;
1213

1314
releasePackages.forEach(packageName => {
14-
if (!checkReleasePackage(releaseOutputDir, packageName)) {
15+
if (!checkReleasePackage(releaseOutputDir, packageName, currentVersion)) {
1516
hasFailed = true;
1617
}
1718
});
@@ -29,5 +30,9 @@ export function checkReleaseOutput(releaseOutputDir: string) {
2930

3031

3132
if (require.main === module) {
32-
checkReleaseOutput(join(__dirname, '../../dist/releases'));
33+
const currentVersion = parseVersionName(require('../../package.json').version);
34+
if (currentVersion === null) {
35+
throw Error('Version in project "package.json" is invalid.');
36+
}
37+
checkReleaseOutput(join(__dirname, '../../dist/releases'), currentVersion);
3338
}

tools/release/publish-release.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ class PublishReleaseTask extends BaseReleaseTask {
9191
console.info(chalk.green(` ✓ Built the release output.`));
9292

9393
// Checks all release packages against release output validations before releasing.
94-
checkReleaseOutput(this.releaseOutputPath);
94+
checkReleaseOutput(this.releaseOutputPath, this.currentVersion);
9595

9696
// Extract the release notes for the new version from the changelog file.
9797
const extractedReleaseNotes = extractReleaseNotes(

tools/release/release-output/check-package.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import chalk from 'chalk';
22
import {existsSync} from 'fs';
33
import {sync as glob} from 'glob';
44
import {join} from 'path';
5+
import {Version} from '../version-name/parse-version';
56

67
import {
78
checkCdkPackage,
89
checkMaterialPackage,
910
checkPackageJsonFile,
11+
checkPackageJsonMigrations,
1012
checkReleaseBundle,
1113
checkTypeDefinitionFile
1214
} from './output-validations';
@@ -32,7 +34,8 @@ type PackageFailures = Map<string, string[]>;
3234
* unexpected release output (e.g. the theming bundle is no longer generated)
3335
* @returns Whether the package passed all checks or not.
3436
*/
35-
export function checkReleasePackage(releasesPath: string, packageName: string): boolean {
37+
export function checkReleasePackage(
38+
releasesPath: string, packageName: string, currentVersion: Version): boolean {
3639
const packagePath = join(releasesPath, packageName);
3740
const failures = new Map() as PackageFailures;
3841
const addFailure = (message, filePath?) => {
@@ -79,6 +82,9 @@ export function checkReleasePackage(releasesPath: string, packageName: string):
7982
addFailure('No "README.md" file found in package output.');
8083
}
8184

85+
checkPackageJsonMigrations(join(packagePath, 'package.json'), currentVersion)
86+
.forEach(f => addFailure(f));
87+
8288
// In case there are failures for this package, we want to print those
8389
// and return a value that implies that there were failures.
8490
if (failures.size) {

tools/release/release-output/output-validations.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import {existsSync, readFileSync} from 'fs';
22
import {sync as glob} from 'glob';
33
import {basename, dirname, isAbsolute, join} from 'path';
4+
import * as semver from 'semver';
45
import * as ts from 'typescript';
6+
import {Version} from '../version-name/parse-version';
57

68
/** RegExp that matches Angular component inline styles that contain a sourcemap reference. */
79
const inlineStylesSourcemapRegex = /styles: ?\[["'].*sourceMappingURL=.*["']/;
@@ -101,6 +103,21 @@ export function checkTypeDefinitionFile(filePath: string): string[] {
101103
return failures;
102104
}
103105

106+
/**
107+
* Checks the ng-update migration setup for the specified "package.json"
108+
* file if present.
109+
*/
110+
export function checkPackageJsonMigrations(
111+
packageJsonPath: string, currentVersion: Version): string[] {
112+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
113+
114+
if (packageJson['ng-update'] && packageJson['ng-update'].migrations) {
115+
return checkMigrationCollection(
116+
packageJson['ng-update'].migrations, dirname(packageJsonPath), currentVersion);
117+
}
118+
return [];
119+
}
120+
104121
/**
105122
* Checks the Angular Material release package and ensures that prebuilt themes
106123
* and the theming bundle are built properly.
@@ -126,8 +143,39 @@ export function checkMaterialPackage(packagePath: string): string[] {
126143
*/
127144
export function checkCdkPackage(packagePath: string): string[] {
128145
const prebuiltFiles = glob('*-prebuilt.css', {cwd: packagePath}).map(path => basename(path));
129-
130146
return ['overlay', 'a11y', 'text-field']
131147
.filter(name => !prebuiltFiles.includes(`${name}-prebuilt.css`))
132148
.map(name => `Could not find the prebuilt ${name} styles.`);
133149
}
150+
151+
/**
152+
* Checks if the migration collected referenced in the specified "package.json"
153+
* has a migration set up for the given target version.
154+
*/
155+
function checkMigrationCollection(
156+
collectionPath: string, packagePath: string, targetVersion: Version): string[] {
157+
const collection = JSON.parse(readFileSync(join(packagePath, collectionPath), 'utf8'));
158+
if (!collection.schematics) {
159+
return ['No schematics found in migration collection.'];
160+
}
161+
162+
const failures: string[] = [];
163+
const lowerBoundaryVersion = `${targetVersion.major}.0.0-0`;
164+
const schematics = collection.schematics;
165+
const targetSchematics = Object.keys(schematics).filter(name => {
166+
const schematicVersion = schematics[name].version;
167+
try {
168+
return schematicVersion && semver.gte(schematicVersion, lowerBoundaryVersion) &&
169+
semver.lte(schematicVersion, targetVersion.format());
170+
} catch {
171+
failures.push(`Could not parse version for migration: ${name}`);
172+
}
173+
});
174+
175+
if (targetSchematics.length === 0) {
176+
failures.push(`No migration configured that handles versions: ^${lowerBoundaryVersion}`);
177+
} else if (targetSchematics.length > 1) {
178+
failures.push(`Multiple migrations targeting the same major version: ${targetVersion.major}`);
179+
}
180+
return failures;
181+
}

tools/release/stage-release.ts

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {promptAndGenerateChangelog} from './changelog';
77
import {GitClient} from './git/git-client';
88
import {getGithubBranchCommitsUrl} from './git/github-urls';
99
import {promptForNewVersion} from './prompt/new-version-prompt';
10+
import {checkPackageJsonMigrations} from './release-output/output-validations';
11+
import {releasePackages} from './release-output/release-packages';
1012
import {parseVersionName, Version} from './version-name/parse-version';
1113

1214
/** Default filename for the changelog. */
@@ -51,7 +53,8 @@ class StageReleaseTask extends BaseReleaseTask {
5153
githubApi: OctokitApi;
5254

5355
constructor(
54-
public projectDir: string, public repositoryOwner: string, public repositoryName: string) {
56+
public projectDir: string, public packagesDir: string, public repositoryOwner: string,
57+
public repositoryName: string) {
5558
super(new GitClient(projectDir, `https://github.com/${repositoryOwner}/${repositoryName}.git`));
5659

5760
this.packageJsonPath = join(projectDir, 'package.json');
@@ -93,6 +96,7 @@ class StageReleaseTask extends BaseReleaseTask {
9396

9497
this.verifyLocalCommitsMatchUpstream(publishBranch);
9598
this._verifyAngularPeerDependencyVersion(newVersion);
99+
this._checkUpdateMigrationCollection(newVersion);
96100
await this._verifyPassingGithubStatus(publishBranch);
97101

98102
if (!this.git.checkoutNewBranch(stagingBranch)) {
@@ -159,17 +163,19 @@ class StageReleaseTask extends BaseReleaseTask {
159163
*/
160164
private _verifyAngularPeerDependencyVersion(newVersion: Version) {
161165
const currentVersionRange = this._getAngularVersionPlaceholderOrExit();
162-
const isMajorWithPrerelease = newVersion.minor === 0 && newVersion.patch === 0 &&
163-
newVersion.prereleaseLabel !== null;
166+
const isMajorWithPrerelease =
167+
newVersion.minor === 0 && newVersion.patch === 0 && newVersion.prereleaseLabel !== null;
164168
const requiredRange = isMajorWithPrerelease ?
165-
`^${newVersion.major}.0.0-0 || ^${newVersion.major + 1}.0.0-0` :
166-
`^${newVersion.major}.0.0 || ^${newVersion.major + 1}.0.0-0`;
169+
`^${newVersion.major}.0.0-0 || ^${newVersion.major + 1}.0.0-0` :
170+
`^${newVersion.major}.0.0 || ^${newVersion.major + 1}.0.0-0`;
167171

168172
if (requiredRange !== currentVersionRange) {
169-
console.error(chalk.red(` ✘ Cannot stage release. The required Angular version range ` +
170-
`is invalid. The version range should be: ${requiredRange}`));
171-
console.error(chalk.red(` Please manually update the version range ` +
172-
`in: ${BAZEL_RELEASE_CONFIG_PATH}`));
173+
console.error(chalk.red(
174+
` ✘ Cannot stage release. The required Angular version range ` +
175+
`is invalid. The version range should be: ${requiredRange}`));
176+
console.error(chalk.red(
177+
` Please manually update the version range ` +
178+
`in: ${BAZEL_RELEASE_CONFIG_PATH}`));
173179
return process.exit(1);
174180
}
175181
}
@@ -181,17 +187,19 @@ class StageReleaseTask extends BaseReleaseTask {
181187
private _getAngularVersionPlaceholderOrExit(): string {
182188
const bzlConfigPath = join(this.projectDir, BAZEL_RELEASE_CONFIG_PATH);
183189
if (!existsSync(bzlConfigPath)) {
184-
console.error(chalk.red(` ✘ Cannot stage release. Could not find the file which sets ` +
185-
`the Angular peerDependency placeholder value. Looked for: ${bzlConfigPath}`));
190+
console.error(chalk.red(
191+
` ✘ Cannot stage release. Could not find the file which sets ` +
192+
`the Angular peerDependency placeholder value. Looked for: ${bzlConfigPath}`));
186193
return process.exit(1);
187194
}
188195

189196
const configFileContent = readFileSync(bzlConfigPath, 'utf8');
190197
const matches = configFileContent.match(/ANGULAR_PACKAGE_VERSION = ["']([^"']+)/);
191198
if (!matches || !matches[1]) {
192-
console.error(chalk.red(` ✘ Cannot stage release. Could not find the ` +
193-
`"ANGULAR_PACKAGE_VERSION" variable. Please ensure this variable exists. ` +
194-
`Looked in: ${bzlConfigPath}`));
199+
console.error(chalk.red(
200+
` ✘ Cannot stage release. Could not find the ` +
201+
`"ANGULAR_PACKAGE_VERSION" variable. Please ensure this variable exists. ` +
202+
`Looked in: ${bzlConfigPath}`));
195203
return process.exit(1);
196204
}
197205
return matches[1];
@@ -236,9 +244,28 @@ class StageReleaseTask extends BaseReleaseTask {
236244

237245
console.info(chalk.green(` ✓ Upstream commit is passing all github status checks.`));
238246
}
247+
248+
/**
249+
* Checks if the update migration collections are set up to properly
250+
* handle the given new version.
251+
*/
252+
private _checkUpdateMigrationCollection(newVersion: Version) {
253+
const failures: string[] = [];
254+
releasePackages.forEach(packageName => {
255+
failures.push(...checkPackageJsonMigrations(
256+
join(this.packagesDir, packageName, 'package.json'), newVersion)
257+
.map(f => chalk.yellow(` ⮑ ${chalk.bold(packageName)}: ${f}`)));
258+
});
259+
if (failures.length) {
260+
console.error(chalk.red(` ✘ Failures in ng-update migration collection detected:`));
261+
failures.forEach(f => console.error(f));
262+
process.exit(1);
263+
}
264+
}
239265
}
240266

241267
/** Entry-point for the release staging script. */
242268
if (require.main === module) {
243-
new StageReleaseTask(join(__dirname, '../../'), 'angular', 'components').run();
269+
const projectDir = join(__dirname, '../../');
270+
new StageReleaseTask(projectDir, join(projectDir, 'src/'), 'angular', 'components').run();
244271
}

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1405,6 +1405,11 @@
14051405
resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-3.0.13.tgz#deb799c641773c5e367abafc92d1e733d62cddd7"
14061406
integrity sha512-rI0LGoMiZGUM+tjDakQpwZOvcmQoubiJ7hxqrYU12VRxBuGGvOThxrBOU/QmJKlKg1WG6FMzuvcEyLffvVSsmw==
14071407

1408+
"@types/semver@^6.2.0":
1409+
version "6.2.0"
1410+
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-6.2.0.tgz#d688d574400d96c5b0114968705366f431831e1a"
1411+
integrity sha512-1OzrNb4RuAzIT7wHSsgZRlMBlNsJl+do6UblR7JMW4oB7bbR+uBEYtUh7gEc/jM84GGilh68lSOokyM/zNUlBA==
1412+
14081413
"@types/send@^0.14.5":
14091414
version "0.14.5"
14101415
resolved "https://registry.yarnpkg.com/@types/send/-/send-0.14.5.tgz#653f7d25b93c3f7f51a8994addaf8a229de022a7"

0 commit comments

Comments
 (0)