fix(docs-infra): avoid version mismatch error when using local Angular packages (#34213)
The local Angular packages used to build `aio/` when running `yarn setup-local`/`yarn build-local` (and related commands), are built by bazel. Bazel, determines the version to use for these locally built packages based on the latest tag for a commit on the current branch. (This works as expected during the release, because the packages are built on the correct branch with up-to-date tags.) During local development, however, this often leads to generating older versions than what the current `@angular/cli` version is compatible with, if the user has not fetched the latest tags from `angular/angular` or the branch has not been rebased recently. Previously, the above (common) situation would result in a build error by the CLI. (Note that this would be a false error, because in this case the version set by bazel would not reflect the actual version of the local packages.) The solution would be for the user to fetch the latest tags from `angular/angular`, rebase their branch and run a bazel build again (ensuring that it would actually build anew and not emit cached artifacts). This was cumbersome and most people didn't even know about it. This commit avoids this error and the associated pain-points by overwriting the versions of the installed local packages with fake versions based on the ones in the lockfile, which are guaranteed to be compatible with the currently used CLI version. Fixes #34208 PR Close #34213
This commit is contained in:
parent
f84f362de3
commit
671deadde1
|
@ -70,8 +70,12 @@ class NgPackagesInstaller {
|
||||||
installLocalDependencies() {
|
installLocalDependencies() {
|
||||||
if (this.force || !this._checkLocalMarker()) {
|
if (this.force || !this._checkLocalMarker()) {
|
||||||
const pathToPackageConfig = path.resolve(this.projectDir, PACKAGE_JSON);
|
const pathToPackageConfig = path.resolve(this.projectDir, PACKAGE_JSON);
|
||||||
|
const packageConfigFile = fs.readFileSync(pathToPackageConfig, 'utf8');
|
||||||
|
const packageConfig = JSON.parse(packageConfigFile);
|
||||||
|
|
||||||
const pathToLockfile = path.resolve(this.projectDir, YARN_LOCK);
|
const pathToLockfile = path.resolve(this.projectDir, YARN_LOCK);
|
||||||
const parsedLockfile = this._parseLockfile(pathToLockfile);
|
const parsedLockfile = this._parseLockfile(pathToLockfile);
|
||||||
|
|
||||||
const packages = this._getDistPackages();
|
const packages = this._getDistPackages();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -96,12 +100,12 @@ class NgPackagesInstaller {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Overwrite the package's version to avoid version mismatch errors with the CLI.
|
||||||
|
this._overwritePackageVersion(key, tmpConfig, packageConfig, parsedLockfile);
|
||||||
|
|
||||||
fs.writeFileSync(pkg.packageJsonPath, JSON.stringify(tmpConfig, null, 2));
|
fs.writeFileSync(pkg.packageJsonPath, JSON.stringify(tmpConfig, null, 2));
|
||||||
});
|
});
|
||||||
|
|
||||||
const packageConfigFile = fs.readFileSync(pathToPackageConfig, 'utf8');
|
|
||||||
const packageConfig = JSON.parse(packageConfigFile);
|
|
||||||
|
|
||||||
const [dependencies, peers] = this._collectDependencies(packageConfig.dependencies || {}, packages);
|
const [dependencies, peers] = this._collectDependencies(packageConfig.dependencies || {}, packages);
|
||||||
const [devDependencies, devPeers] = this._collectDependencies(packageConfig.devDependencies || {}, packages);
|
const [devDependencies, devPeers] = this._collectDependencies(packageConfig.devDependencies || {}, packages);
|
||||||
|
|
||||||
|
@ -265,6 +269,35 @@ class NgPackagesInstaller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a package's version with the fake version based on the package's original version in the projects's
|
||||||
|
* lockfile.
|
||||||
|
*
|
||||||
|
* **Background:**
|
||||||
|
* This helps avoid version mismatch errors with the CLI.
|
||||||
|
* Since the version set by bazel on the locally built packages is determined based on the latest tag for a commit on
|
||||||
|
* the current branch, it is often the case that this version is older than what the current `@angular/cli` version is
|
||||||
|
* compatible with (e.g. if the user has not fetched the latest tags from `angular/angular` or the branch has not been
|
||||||
|
* rebased recently.
|
||||||
|
*
|
||||||
|
* @param {string} packageName - The name of the package we are updating (e.g. `'@angular/core'`).
|
||||||
|
* @param {{[key: string]: any}} packageConfig - The package's parsed `package.json`.
|
||||||
|
* @param {{[key: string]: any}} projectConfig - The project's parsed `package.json`.
|
||||||
|
* @param {import('@yarnpkg/lockfile').LockFileObject} projectLockfile - The projects's parsed `yarn.lock`.
|
||||||
|
*/
|
||||||
|
_overwritePackageVersion(packageName, packageConfig, projectConfig, projectLockfile) {
|
||||||
|
const originalVersionRange = (projectConfig.dependencies || {})[packageName] ||
|
||||||
|
(projectConfig.devDependencies || {})[packageName];
|
||||||
|
const originalVersion =
|
||||||
|
(projectLockfile[`${packageName}@${originalVersionRange}`] || {}).version;
|
||||||
|
|
||||||
|
if (originalVersion !== undefined) {
|
||||||
|
const newVersion = `${originalVersion}+locally-overwritten-by-ngPackagesInstaller`;
|
||||||
|
this._log(`Overwriting the version of '${packageName}': ${packageConfig.version} --> ${newVersion}`);
|
||||||
|
packageConfig.version = newVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the value for a boolean cli argument/option. When passing an option multiple times, `yargs` parses it as an
|
* Extract the value for a boolean cli argument/option. When passing an option multiple times, `yargs` parses it as an
|
||||||
* array of boolean values. In that case, we only care about the last occurrence.
|
* array of boolean values. In that case, we only care about the last occurrence.
|
||||||
|
|
|
@ -362,6 +362,82 @@ describe('NgPackagesInstaller', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('_overwritePackageVersion()', () => {
|
||||||
|
it('should do nothing if the specified package is not a dependency', () => {
|
||||||
|
const pkgConfig = {name: '@scope/missing', version: 'local-version'};
|
||||||
|
const lockFile = {
|
||||||
|
[`${pkgConfig.name}@project-range`]: {version: 'project-version'},
|
||||||
|
};
|
||||||
|
let projectConfig;
|
||||||
|
|
||||||
|
// No `dependencies`/`devDependencies` at all.
|
||||||
|
projectConfig = {};
|
||||||
|
installer._overwritePackageVersion(pkgConfig.name, pkgConfig, projectConfig, lockFile);
|
||||||
|
expect(pkgConfig.version).toBe('local-version');
|
||||||
|
|
||||||
|
// Not listed in `dependencies`/`devDependencies`.
|
||||||
|
projectConfig = {
|
||||||
|
dependencies: {otherPackage: 'foo'},
|
||||||
|
devDependencies: {yetAnotherPackage: 'bar'},
|
||||||
|
};
|
||||||
|
installer._overwritePackageVersion(pkgConfig.name, pkgConfig, projectConfig, lockFile);
|
||||||
|
expect(pkgConfig.version).toBe('local-version');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing if the specified package cannot be found in the lockfile', () => {
|
||||||
|
const pkgConfig = {name: '@scope/missing', version: 'local-version'};
|
||||||
|
const projectConfig = {
|
||||||
|
dependencies: {[pkgConfig.name]: 'project-range'},
|
||||||
|
};
|
||||||
|
let lockFile;
|
||||||
|
|
||||||
|
// Package missing from lockfile.
|
||||||
|
lockFile = {
|
||||||
|
'otherPackage@someRange': {version: 'some-version'},
|
||||||
|
};
|
||||||
|
installer._overwritePackageVersion(pkgConfig.name, pkgConfig, projectConfig, lockFile);
|
||||||
|
expect(pkgConfig.version).toBe('local-version');
|
||||||
|
|
||||||
|
// Package present in lockfile, but for a different version range.
|
||||||
|
lockFile = {
|
||||||
|
[`${pkgConfig.name}@other-range`]: {version: 'project-version'},
|
||||||
|
};
|
||||||
|
installer._overwritePackageVersion(pkgConfig.name, pkgConfig, projectConfig, lockFile);
|
||||||
|
expect(pkgConfig.version).toBe('local-version');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should overwrite the package version if it is a dependency and found in the lockfile', () => {
|
||||||
|
const pkgConfig = {name: '@scope/found', version: 'local-version'};
|
||||||
|
const lockFile = {
|
||||||
|
[`${pkgConfig.name}@project-range-prod`]: {version: 'project-version-prod'},
|
||||||
|
[`${pkgConfig.name}@project-range-dev`]: {version: 'project-version-dev'},
|
||||||
|
};
|
||||||
|
let projectConfig;
|
||||||
|
|
||||||
|
// Package in `dependencies`.
|
||||||
|
projectConfig = {
|
||||||
|
dependencies: {[pkgConfig.name]: 'project-range-prod'},
|
||||||
|
};
|
||||||
|
installer._overwritePackageVersion(pkgConfig.name, pkgConfig, projectConfig, lockFile);
|
||||||
|
expect(pkgConfig.version).toBe('project-version-prod+locally-overwritten-by-ngPackagesInstaller');
|
||||||
|
|
||||||
|
// // Package in `devDependencies`.
|
||||||
|
projectConfig = {
|
||||||
|
devDependencies: {[pkgConfig.name]: 'project-range-dev'},
|
||||||
|
};
|
||||||
|
installer._overwritePackageVersion(pkgConfig.name, pkgConfig, projectConfig, lockFile);
|
||||||
|
expect(pkgConfig.version).toBe('project-version-dev+locally-overwritten-by-ngPackagesInstaller');
|
||||||
|
|
||||||
|
// // Package in both `dependencies` and `devDependencies` (the former takes precedence).
|
||||||
|
projectConfig = {
|
||||||
|
devDependencies: {[pkgConfig.name]: 'project-range-dev'},
|
||||||
|
dependencies: {[pkgConfig.name]: 'project-range-prod'},
|
||||||
|
};
|
||||||
|
installer._overwritePackageVersion(pkgConfig.name, pkgConfig, projectConfig, lockFile);
|
||||||
|
expect(pkgConfig.version).toBe('project-version-prod+locally-overwritten-by-ngPackagesInstaller');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('_parseLockfile()', () => {
|
describe('_parseLockfile()', () => {
|
||||||
let originalLockfileParseDescriptor;
|
let originalLockfileParseDescriptor;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue