From 4b5bd07c3605b381cc356ca315e62d6a87534c2c Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Thu, 22 Aug 2019 16:20:21 -0400 Subject: [PATCH] fix(@angular-devkit/core): track workspace targets with no original collection Fixes #15403 --- .../angular_devkit/core/src/_golden-api.d.ts | 2 +- .../core/src/workspace/definitions.ts | 7 +-- .../core/src/workspace/json/reader.ts | 40 ++++++++++----- .../core/src/workspace/json/reader_spec.ts | 49 +++++++++++++++++++ 4 files changed, 83 insertions(+), 15 deletions(-) diff --git a/etc/api/angular_devkit/core/src/_golden-api.d.ts b/etc/api/angular_devkit/core/src/_golden-api.d.ts index 6e2467db8a5f..68800d4aacad 100644 --- a/etc/api/angular_devkit/core/src/_golden-api.d.ts +++ b/etc/api/angular_devkit/core/src/_golden-api.d.ts @@ -243,7 +243,7 @@ export declare function decamelize(str: string): string; export declare function deepCopy(value: T): T; -export declare type DefinitionCollectionListener = (name: string, action: 'add' | 'remove' | 'replace', newValue: V | undefined, oldValue: V | undefined) => void; +export declare type DefinitionCollectionListener = (name: string, action: 'add' | 'remove' | 'replace', newValue: V | undefined, oldValue: V | undefined, collection: DefinitionCollection) => void; export declare class DependencyNotFoundException extends BaseException { constructor(); diff --git a/packages/angular_devkit/core/src/workspace/definitions.ts b/packages/angular_devkit/core/src/workspace/definitions.ts index 75dec6286f4b..53b21259116b 100644 --- a/packages/angular_devkit/core/src/workspace/definitions.ts +++ b/packages/angular_devkit/core/src/workspace/definitions.ts @@ -29,11 +29,12 @@ export interface TargetDefinition { builder: string; } -export type DefinitionCollectionListener = ( +export type DefinitionCollectionListener = ( name: string, action: 'add' | 'remove' | 'replace', newValue: V | undefined, oldValue: V | undefined, + collection: DefinitionCollection, ) => void; class DefinitionCollection implements ReadonlyMap { @@ -50,7 +51,7 @@ class DefinitionCollection implements ReadonlyMap { const value = this._map.get(key); const result = this._map.delete(key); if (result && value !== undefined && this._listener) { - this._listener(key, 'remove', undefined, value); + this._listener(key, 'remove', undefined, value, this); } return result; @@ -61,7 +62,7 @@ class DefinitionCollection implements ReadonlyMap { this._map.set(key, value); if (this._listener) { - this._listener(key, existing !== undefined ? 'replace' : 'add', value, existing); + this._listener(key, existing !== undefined ? 'replace' : 'add', value, existing, this); } return this; diff --git a/packages/angular_devkit/core/src/workspace/json/reader.ts b/packages/angular_devkit/core/src/workspace/json/reader.ts index e54382c4acef..24c5f34afe74 100644 --- a/packages/angular_devkit/core/src/workspace/json/reader.ts +++ b/packages/angular_devkit/core/src/workspace/json/reader.ts @@ -213,17 +213,35 @@ function parseProject( } let collectionListener: DefinitionCollectionListener | undefined; - if (context.trackChanges && targetsNode) { - const parentNode = targetsNode; - collectionListener = (name, action, newValue) => { - jsonMetadata.addChange( - action, - `/projects/${projectName}/targets/${escapeKey(name)}`, - parentNode, - newValue, - 'target', - ); - }; + if (context.trackChanges) { + if (targetsNode) { + const parentNode = targetsNode; + collectionListener = (name, action, newValue) => { + jsonMetadata.addChange( + action, + `/projects/${projectName}/targets/${escapeKey(name)}`, + parentNode, + newValue, + 'target', + ); + }; + } else { + let added = false; + collectionListener = (_name, action, _new, _old, collection) => { + if (added || action !== 'add') { + return; + } + + jsonMetadata.addChange( + 'add', + `/projects/${projectName}/targets`, + projectNode, + collection, + 'targetcollection', + ); + added = true; + }; + } } const base = { diff --git a/packages/angular_devkit/core/src/workspace/json/reader_spec.ts b/packages/angular_devkit/core/src/workspace/json/reader_spec.ts index b5c84591dde6..c21550b07d07 100644 --- a/packages/angular_devkit/core/src/workspace/json/reader_spec.ts +++ b/packages/angular_devkit/core/src/workspace/json/reader_spec.ts @@ -710,4 +710,53 @@ describe('JSON ProjectDefinition Tracks Project Changes', () => { expect(change.value).toBe('valueA'); } }); + + it('tracks target additions with no original target collection', async () => { + const original = stripIndent` + { + "version": 1, + // Comment + "schematics": { + "@angular/schematics:component": { + "prefix": "abc" + } + }, + "projects": { + "p1": { + "root": "p1-root" + } + }, + "x-foo": { + "is": ["good", "great", "awesome"] + }, + "x-bar": 5, + } + `; + const host = createTestHost(original); + + const workspace = await readJsonWorkspace('', host); + + const project = workspace.projects.get('p1'); + expect(project).not.toBeUndefined(); + if (!project) { + return; + } + + project.targets.add({ + name: 't1', + builder: 't1-builder', + }); + + const metadata = getMetadata(workspace); + + expect(metadata.hasChanges).toBeTruthy(); + expect(metadata.changeCount).toBe(1); + + const change = metadata.findChangesForPath('/projects/p1/targets')[0]; + expect(change).not.toBeUndefined(); + if (change) { + expect(change.op).toBe('add'); + expect(change.type).toBe('targetcollection'); + } + }); });