Pete Bacon Darwin 2bfddcf29f feat(ngcc): automatically clean outdated ngcc artifacts ()
If ngcc gets updated to a new version then the artifacts
left in packages that were processed by the previous
version are possibly invalid.

Previously we just errored if we found packages that
had already been processed by an outdated version.

Now we automatically clean the packages that have
outdated artifacts so that they can be reprocessed
correctly with the current ngcc version.

Fixes 

PR Close 
2020-01-31 17:02:44 -08:00

236 lines
10 KiB
TypeScript

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AbsoluteFsPath, FileSystem, PathSegment, absoluteFrom, getFileSystem} from '../../../../src/ngtsc/file_system';
import {runInEachFileSystem} from '../../../../src/ngtsc/file_system/testing';
import {EntryPointPackageJson} from '../../../src/packages/entry_point';
import {BackupFileCleaner, NgccDirectoryCleaner, PackageJsonCleaner} from '../../../src/writing/cleaning/cleaning_strategies';
runInEachFileSystem(() => {
describe('cleaning strategies', () => {
let fs: FileSystem;
let _abs: typeof absoluteFrom;
beforeEach(() => {
fs = getFileSystem();
_abs = absoluteFrom;
});
describe('PackageJsonCleaner', () => {
let packageJsonPath: AbsoluteFsPath;
beforeEach(() => { packageJsonPath = _abs('/node_modules/pkg/package.json'); });
describe('canClean()', () => {
it('should return true if the basename is package.json', () => {
const strategy = new PackageJsonCleaner(fs);
expect(strategy.canClean(packageJsonPath, fs.basename(packageJsonPath))).toBe(true);
});
it('should return false if the basename is not package.json', () => {
const filePath = _abs('/node_modules/pkg/index.js');
const fileName = fs.basename(filePath);
const strategy = new PackageJsonCleaner(fs);
expect(strategy.canClean(filePath, fileName)).toBe(false);
});
});
describe('clean()', () => {
it('should not touch the file if there is no build marker', () => {
const strategy = new PackageJsonCleaner(fs);
const packageJson: EntryPointPackageJson = {name: 'test-package'};
fs.ensureDir(fs.dirname(packageJsonPath));
fs.writeFile(packageJsonPath, JSON.stringify(packageJson));
strategy.clean(packageJsonPath, fs.basename(packageJsonPath));
const newPackageJson: EntryPointPackageJson = JSON.parse(fs.readFile(packageJsonPath));
expect(newPackageJson).toEqual({name: 'test-package'});
});
it('should remove the processed marker', () => {
const strategy = new PackageJsonCleaner(fs);
const packageJson: EntryPointPackageJson = {
name: 'test-package',
__processed_by_ivy_ngcc__: {'fesm2015': '8.0.0'}
};
fs.ensureDir(fs.dirname(packageJsonPath));
fs.writeFile(packageJsonPath, JSON.stringify(packageJson));
strategy.clean(packageJsonPath, fs.basename(packageJsonPath));
const newPackageJson: EntryPointPackageJson = JSON.parse(fs.readFile(packageJsonPath));
expect(newPackageJson).toEqual({name: 'test-package'});
});
it('should remove the new entry points', () => {
const strategy = new PackageJsonCleaner(fs);
const packageJson: EntryPointPackageJson = {
name: 'test-package',
__processed_by_ivy_ngcc__: {'fesm2015': '8.0.0'}
};
fs.ensureDir(fs.dirname(packageJsonPath));
fs.writeFile(packageJsonPath, JSON.stringify(packageJson));
strategy.clean(packageJsonPath, fs.basename(packageJsonPath));
const newPackageJson: EntryPointPackageJson = JSON.parse(fs.readFile(packageJsonPath));
expect(newPackageJson).toEqual({name: 'test-package'});
});
it('should remove the prepublish script if there was a processed marker', () => {
const strategy = new PackageJsonCleaner(fs);
const packageJson: EntryPointPackageJson = {
name: 'test-package',
__processed_by_ivy_ngcc__: {'fesm2015': '8.0.0'},
scripts: {prepublishOnly: 'added by ngcc', test: 'do testing'},
};
fs.ensureDir(fs.dirname(packageJsonPath));
fs.writeFile(packageJsonPath, JSON.stringify(packageJson));
strategy.clean(packageJsonPath, fs.basename(packageJsonPath));
const newPackageJson: EntryPointPackageJson = JSON.parse(fs.readFile(packageJsonPath));
expect(newPackageJson).toEqual({
name: 'test-package',
scripts: {test: 'do testing'},
});
});
it('should revert and remove the backup for the prepublish script if there was a processed marker',
() => {
const strategy = new PackageJsonCleaner(fs);
const packageJson: EntryPointPackageJson = {
name: 'test-package',
__processed_by_ivy_ngcc__: {'fesm2015': '8.0.0'},
scripts: {
prepublishOnly: 'added by ngcc',
prepublishOnly__ivy_ngcc_bak: 'original',
test: 'do testing'
},
};
fs.ensureDir(fs.dirname(packageJsonPath));
fs.writeFile(packageJsonPath, JSON.stringify(packageJson));
strategy.clean(packageJsonPath, fs.basename(packageJsonPath));
const newPackageJson: EntryPointPackageJson = JSON.parse(fs.readFile(packageJsonPath));
expect(newPackageJson).toEqual({
name: 'test-package',
scripts: {prepublishOnly: 'original', test: 'do testing'},
});
});
it('should not touch the scripts if there was not processed marker', () => {
const strategy = new PackageJsonCleaner(fs);
const packageJson: EntryPointPackageJson = {
name: 'test-package',
scripts: {
prepublishOnly: 'added by ngcc',
prepublishOnly__ivy_ngcc_bak: 'original',
test: 'do testing'
},
};
fs.ensureDir(fs.dirname(packageJsonPath));
fs.writeFile(packageJsonPath, JSON.stringify(packageJson));
strategy.clean(packageJsonPath, fs.basename(packageJsonPath));
const newPackageJson: EntryPointPackageJson = JSON.parse(fs.readFile(packageJsonPath));
expect(newPackageJson).toEqual({
name: 'test-package',
scripts: {
prepublishOnly: 'added by ngcc',
prepublishOnly__ivy_ngcc_bak: 'original',
test: 'do testing'
}
});
});
});
});
describe('BackupFileCleaner', () => {
let filePath: AbsoluteFsPath;
let backupFilePath: AbsoluteFsPath;
beforeEach(() => {
filePath = _abs('/node_modules/pkg/index.js');
backupFilePath = _abs('/node_modules/pkg/index.js.__ivy_ngcc_bak');
});
describe('canClean()', () => {
it('should return true if the file name ends in .__ivy_ngcc_bak and the processed file exists',
() => {
const strategy = new BackupFileCleaner(fs);
fs.ensureDir(fs.dirname(filePath));
fs.writeFile(filePath, 'processed file');
fs.writeFile(backupFilePath, 'original file');
expect(strategy.canClean(backupFilePath, fs.basename(backupFilePath))).toBe(true);
});
it('should return false if the file does not end in .__ivy_ngcc_bak', () => {
const strategy = new BackupFileCleaner(fs);
fs.ensureDir(fs.dirname(filePath));
fs.writeFile(filePath, 'processed file');
fs.writeFile(backupFilePath, 'original file');
expect(strategy.canClean(filePath, fs.basename(filePath))).toBe(false);
});
it('should return false if the file ends in .__ivy_ngcc_bak but the processed file does not exist',
() => {
const strategy = new BackupFileCleaner(fs);
fs.ensureDir(fs.dirname(filePath));
fs.writeFile(backupFilePath, 'original file');
expect(strategy.canClean(backupFilePath, fs.basename(backupFilePath))).toBe(false);
});
});
describe('clean()', () => {
it('should move the backup file back to its original file path', () => {
const strategy = new BackupFileCleaner(fs);
fs.ensureDir(fs.dirname(filePath));
fs.writeFile(filePath, 'processed file');
fs.writeFile(backupFilePath, 'original file');
strategy.clean(backupFilePath, fs.basename(backupFilePath));
expect(fs.exists(backupFilePath)).toBe(false);
expect(fs.readFile(filePath)).toEqual('original file');
});
});
});
describe('NgccDirectoryCleaner', () => {
let ivyDirectory: AbsoluteFsPath;
beforeEach(() => { ivyDirectory = _abs('/node_modules/pkg/__ivy_ngcc__'); });
describe('canClean()', () => {
it('should return true if the path is a directory and is called __ivy_ngcc__', () => {
const strategy = new NgccDirectoryCleaner(fs);
fs.ensureDir(ivyDirectory);
expect(strategy.canClean(ivyDirectory, fs.basename(ivyDirectory))).toBe(true);
});
it('should return false if the path is a directory and not called __ivy_ngcc__', () => {
const strategy = new NgccDirectoryCleaner(fs);
const filePath = _abs('/node_modules/pkg/other');
fs.ensureDir(ivyDirectory);
expect(strategy.canClean(filePath, fs.basename(filePath))).toBe(false);
});
it('should return false if the path is called __ivy_ngcc__ but does not exist', () => {
const strategy = new NgccDirectoryCleaner(fs);
expect(strategy.canClean(ivyDirectory, fs.basename(ivyDirectory))).toBe(false);
});
it('should return false if the path is called __ivy_ngcc__ but is not a directory', () => {
const strategy = new NgccDirectoryCleaner(fs);
fs.ensureDir(fs.dirname(ivyDirectory));
fs.writeFile(ivyDirectory, 'some contents');
expect(strategy.canClean(ivyDirectory, fs.basename(ivyDirectory))).toBe(false);
});
});
describe('clean()', () => {
it('should remove the __ivy_ngcc__ directory', () => {
const strategy = new NgccDirectoryCleaner(fs);
fs.ensureDir(ivyDirectory);
fs.ensureDir(fs.resolve(ivyDirectory, 'subfolder'));
fs.writeFile(fs.resolve(ivyDirectory, 'subfolder', 'file.txt'), 'file contents');
strategy.clean(ivyDirectory, fs.basename(ivyDirectory));
expect(fs.exists(ivyDirectory)).toBe(false);
});
});
});
});
});