To persist some of its state, `ngcc` needs to update `package.json` files (both in memory and on disk). This refactoring abstracts these operations behind the `PackageJsonUpdater` interface, making it easier to orchestrate them from different contexts (e.g. when running tasks in parallel on multiple processes). Inspired by/Based on @alxhub's prototype: alxhub/angular@cb631bdb1 PR Close #32427
		
			
				
	
	
		
			156 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			156 lines
		
	
	
		
			5.0 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, absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
 | |
| import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
 | |
| import {loadTestFiles} from '../../../test/helpers';
 | |
| import {DirectPackageJsonUpdater, PackageJsonUpdater} from '../../src/writing/package_json_updater';
 | |
| 
 | |
| runInEachFileSystem(() => {
 | |
|   describe('DirectPackageJsonUpdater', () => {
 | |
|     let _: typeof absoluteFrom;
 | |
|     let fs: FileSystem;
 | |
|     let updater: PackageJsonUpdater;
 | |
| 
 | |
|     // Helpers
 | |
|     const readJson = (path: AbsoluteFsPath) => JSON.parse(fs.readFile(path));
 | |
| 
 | |
|     beforeEach(() => {
 | |
|       _ = absoluteFrom;
 | |
|       fs = getFileSystem();
 | |
|       updater = new DirectPackageJsonUpdater(fs);
 | |
|     });
 | |
| 
 | |
|     it('should update a `package.json` file on disk', () => {
 | |
|       const jsonPath = _('/foo/package.json');
 | |
|       loadTestFiles([
 | |
|         {name: jsonPath, contents: '{"foo": true, "bar": {"baz": "OK"}}'},
 | |
|       ]);
 | |
| 
 | |
|       const update = updater.createUpdate().addChange(['foo'], false).addChange(['bar', 'baz'], 42);
 | |
| 
 | |
|       // Not updated yet.
 | |
|       expect(readJson(jsonPath)).toEqual({
 | |
|         foo: true,
 | |
|         bar: {baz: 'OK'},
 | |
|       });
 | |
| 
 | |
|       update.writeChanges(jsonPath);
 | |
| 
 | |
|       // Updated now.
 | |
|       expect(readJson(jsonPath)).toEqual({
 | |
|         foo: false,
 | |
|         bar: {baz: 42},
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('should update an in-memory representation (if provided)', () => {
 | |
|       const jsonPath = _('/foo/package.json');
 | |
|       loadTestFiles([
 | |
|         {name: jsonPath, contents: '{"foo": true, "bar": {"baz": "OK"}}'},
 | |
|       ]);
 | |
| 
 | |
|       const pkg = readJson(jsonPath);
 | |
|       const update = updater.createUpdate().addChange(['foo'], false).addChange(['bar', 'baz'], 42);
 | |
| 
 | |
|       // Not updated yet.
 | |
|       expect(pkg).toEqual({
 | |
|         foo: true,
 | |
|         bar: {baz: 'OK'},
 | |
|       });
 | |
| 
 | |
|       update.writeChanges(jsonPath, pkg);
 | |
| 
 | |
|       // Updated now.
 | |
|       expect(pkg).toEqual({
 | |
|         foo: false,
 | |
|         bar: {baz: 42},
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('should create the `package.json` file, if it does not exist', () => {
 | |
|       const jsonPath = _('/foo/package.json');
 | |
|       expect(fs.exists(jsonPath)).toBe(false);
 | |
| 
 | |
|       updater.createUpdate()
 | |
|           .addChange(['foo'], false)
 | |
|           .addChange(['bar', 'baz'], 42)
 | |
|           .writeChanges(jsonPath);
 | |
| 
 | |
|       expect(fs.exists(jsonPath)).toBe(true);
 | |
|       expect(readJson(jsonPath)).toEqual({
 | |
|         foo: false,
 | |
|         bar: {baz: 42},
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('should create any missing ancestor objects', () => {
 | |
|       const jsonPath = _('/foo/package.json');
 | |
|       loadTestFiles([
 | |
|         {name: jsonPath, contents: '{"foo": {}}'},
 | |
|       ]);
 | |
| 
 | |
|       const pkg = readJson(jsonPath);
 | |
|       updater.createUpdate()
 | |
|           .addChange(['foo', 'bar', 'baz', 'qux'], 'updated')
 | |
|           .writeChanges(jsonPath, pkg);
 | |
| 
 | |
|       expect(readJson(jsonPath)).toEqual(pkg);
 | |
|       expect(pkg).toEqual({
 | |
|         foo: {
 | |
|           bar: {
 | |
|             baz: {
 | |
|               qux: 'updated',
 | |
|             },
 | |
|           },
 | |
|         },
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('should throw, if no changes have been recorded', () => {
 | |
|       const jsonPath = _('/foo/package.json');
 | |
| 
 | |
|       expect(() => updater.createUpdate().writeChanges(jsonPath))
 | |
|           .toThrowError(`No changes to write to '${jsonPath}'.`);
 | |
|     });
 | |
| 
 | |
|     it('should throw, if a property-path is empty', () => {
 | |
|       const jsonPath = _('/foo/package.json');
 | |
| 
 | |
|       expect(() => updater.createUpdate().addChange([], 'missing').writeChanges(jsonPath))
 | |
|           .toThrowError(`Missing property path for writing value to '${jsonPath}'.`);
 | |
|     });
 | |
| 
 | |
|     it('should throw, if a property-path points to a non-object intermediate value', () => {
 | |
|       const jsonPath = _('/foo/package.json');
 | |
|       loadTestFiles([
 | |
|         {name: jsonPath, contents: '{"foo": null, "bar": 42, "baz": {"qux": []}}'},
 | |
|       ]);
 | |
| 
 | |
|       const writeToProp = (propPath: string[]) =>
 | |
|           updater.createUpdate().addChange(propPath, 'updated').writeChanges(jsonPath);
 | |
| 
 | |
|       expect(() => writeToProp(['foo', 'child']))
 | |
|           .toThrowError('Property path \'foo.child\' does not point to an object.');
 | |
|       expect(() => writeToProp(['bar', 'child']))
 | |
|           .toThrowError('Property path \'bar.child\' does not point to an object.');
 | |
|       expect(() => writeToProp(['baz', 'qux', 'child']))
 | |
|           .toThrowError('Property path \'baz.qux.child\' does not point to an object.');
 | |
|     });
 | |
| 
 | |
|     it('should throw, if trying to re-apply an already applied update', () => {
 | |
|       const update = updater.createUpdate().addChange(['foo'], 'updated');
 | |
| 
 | |
|       expect(() => update.writeChanges(_('/foo/package.json'))).not.toThrow();
 | |
|       expect(() => update.writeChanges(_('/foo/package.json')))
 | |
|           .toThrowError('Trying to apply a `PackageJsonUpdate` that has already been applied.');
 | |
|       expect(() => update.writeChanges(_('/bar/package.json')))
 | |
|           .toThrowError('Trying to apply a `PackageJsonUpdate` that has already been applied.');
 | |
|     });
 | |
|   });
 | |
| });
 |