Skip to content

Commit 19b6b56

Browse files
committed
fix(@schematics/update): support Angular 6.x when peerDep is 5.x
Fix #10367
1 parent 4960ac9 commit 19b6b56

File tree

3 files changed

+105
-3
lines changed

3 files changed

+105
-3
lines changed

packages/schematics/update/update/index.ts

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
*/
88
import { logging } from '@angular-devkit/core';
99
import {
10-
Rule, SchematicContext, SchematicsException, TaskId,
10+
Rule,
11+
SchematicContext,
12+
SchematicsException,
13+
TaskId,
1114
Tree,
1215
} from '@angular-devkit/schematics';
1316
import { NodePackageInstallTask, RunSchematicTask } from '@angular-devkit/schematics/tasks';
@@ -19,7 +22,30 @@ import { NpmRepositoryPackageJson } from './npm-package-json';
1922
import { JsonSchemaForNpmPackageJsonFiles } from './package-json';
2023
import { UpdateSchema } from './schema';
2124

22-
type VersionRange = string & { __: void; };
25+
type VersionRange = string & { __VERSION_RANGE: void; };
26+
type PeerVersionTransform = string | ((range: string) => string);
27+
28+
// This is a map of packageGroupName to range extending function. If it isn't found, the range is
29+
// kept the same.
30+
// Angular guarantees that a major is compatible with its following major (so packages that depend
31+
// on Angular 5 are also compatible with Angular 6). This is, in code, represented by verifying
32+
// that all other packages that have a peer dependency of `"@angular/core": "^5.0.0"` actually
33+
// supports 6.0, by adding that compatibility to the range, so it is `^5.0.0 || ^6.0.0`.
34+
const peerCompatibleWhitelist: { [name: string]: PeerVersionTransform } = {
35+
'@angular/core': (range: string) => {
36+
range = semver.validRange(range);
37+
let major = 1;
38+
while (semver.ltr(major + '.0.0', range)) {
39+
major++;
40+
if (major >= 99) {
41+
throw new SchematicsException(`Invalid range: ${JSON.stringify(range)}`);
42+
}
43+
}
44+
45+
// Add the major - 1 version as compatible with the angular compatible.
46+
return semver.validRange(`^${major + 1}.0.0-rc.0 || ${range}`) || range;
47+
},
48+
};
2349

2450
interface PackageVersionInfo {
2551
version: VersionRange;
@@ -41,6 +67,30 @@ interface UpdateMetadata {
4167
migrations?: string;
4268
}
4369

70+
function _updatePeerVersion(infoMap: Map<string, PackageInfo>, name: string, range: string) {
71+
// Resolve packageGroupName.
72+
const maybePackageInfo = infoMap.get(name);
73+
if (!maybePackageInfo) {
74+
return range;
75+
}
76+
if (maybePackageInfo.target) {
77+
name = maybePackageInfo.target.updateMetadata.packageGroup[0] || name;
78+
} else {
79+
name = maybePackageInfo.installed.updateMetadata.packageGroup[0] || name;
80+
}
81+
82+
const maybeTransform = peerCompatibleWhitelist[name];
83+
if (maybeTransform) {
84+
if (typeof maybeTransform == 'function') {
85+
return maybeTransform(range);
86+
} else {
87+
return maybeTransform;
88+
}
89+
}
90+
91+
return range;
92+
}
93+
4494
function _validateForwardPeerDependencies(
4595
name: string,
4696
infoMap: Map<string, PackageInfo>,
@@ -90,13 +140,16 @@ function _validateReversePeerDependencies(
90140
installedLogger.debug(`${installed}...`);
91141
const peers = (installedInfo.target || installedInfo.installed).packageJson.peerDependencies;
92142

93-
for (const [peer, range] of Object.entries(peers || {})) {
143+
for (let [peer, range] of Object.entries(peers || {})) {
94144
if (peer != name) {
95145
// Only check peers to the packages we're updating. We don't care about peers
96146
// that are unmet but we have no effect on.
97147
continue;
98148
}
99149

150+
// Override the peer version range if it's whitelisted.
151+
range = _updatePeerVersion(infoMap, peer, range);
152+
100153
if (!semver.satisfies(version, range)) {
101154
logger.error([
102155
`Package ${JSON.stringify(installed)} has an incompatible peer dependency to`,

packages/schematics/update/update/index_spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,47 @@ describe('@schematics/update', () => {
8888
).subscribe(undefined, done.fail, done);
8989
}, 45000);
9090

91+
it('updates Angular as compatible with Angular N-1', done => {
92+
// Add the basic migration package.
93+
const content = virtualFs.fileBufferToString(host.sync.read(normalize('/package.json')));
94+
const packageJson = JSON.parse(content);
95+
const dependencies = packageJson['dependencies'];
96+
dependencies['@angular-devkit-tests/update-peer-dependencies-angular-5'] = '1.0.0';
97+
dependencies['@angular/core'] = '5.1.0';
98+
dependencies['rxjs'] = '5.5.0';
99+
dependencies['zone.js'] = '0.8.26';
100+
host.sync.write(
101+
normalize('/package.json'),
102+
virtualFs.stringToFileBuffer(JSON.stringify(packageJson)),
103+
);
104+
105+
schematicRunner.runSchematicAsync('update', {
106+
packages: ['@angular/core'],
107+
next: true,
108+
}, appTree).pipe(
109+
map(tree => {
110+
const packageJson = JSON.parse(tree.readContent('/package.json'));
111+
expect(packageJson['dependencies']['@angular/core'][0]).toBe('6');
112+
113+
// Check install task.
114+
expect(schematicRunner.tasks).toEqual([
115+
{
116+
name: 'node-package',
117+
options: jasmine.objectContaining({
118+
command: 'install',
119+
}),
120+
},
121+
{
122+
name: 'run-schematic',
123+
options: jasmine.objectContaining({
124+
name: 'migrate',
125+
}),
126+
},
127+
]);
128+
}),
129+
).subscribe(undefined, done.fail, done);
130+
}, 45000);
131+
91132
it('can migrate only', done => {
92133
// Add the basic migration package.
93134
const content = virtualFs.fileBufferToString(host.sync.read(normalize('/package.json')));
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "@angular-devkit-tests/update-peer-dependencies-angular-5",
3+
"version": "1.0.0",
4+
"description": "Tests",
5+
"peerDependencies": {
6+
"@angular/core": "^5.0.0"
7+
}
8+
}

0 commit comments

Comments
 (0)