Skip to content

Commit

Permalink
feat(@angular-devkit/architect): merge object options from CLI
Browse files Browse the repository at this point in the history
We recently introduced the ability to pass object values from the
command line (angular#28362). @clydin noticed that the initial behavior
didn't work well for `--define`: It completely replaced all values
even if just one of multiple defines is specified.

This updates the architect to support merging of object options.
If both the base option (e.g. from `angular.json`) and the override
(e.g. from a CLI `--flag`) are objects, the objects are merged.

See: angular#28362
  • Loading branch information
jkrems committed Sep 11, 2024
1 parent 27c1c77 commit 97e7b66
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 4 deletions.
6 changes: 2 additions & 4 deletions packages/angular_devkit/architect/src/architect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
SimpleScheduler,
createJobHandler,
} from './jobs';
import { mergeOptions } from './options';
import { scheduleByName, scheduleByTarget } from './schedule-by-name';

const inputSchema = require('./input-schema.json');
Expand All @@ -71,10 +72,7 @@ function _createJobHandlerFromBuilderInfo(
concatMap(async (message) => {
if (message.kind === JobInboundMessageKind.Input) {
const v = message.value as BuilderInput;
const options = {
...baseOptions,
...v.options,
};
const options = mergeOptions(baseOptions, v.options);

// Validate v against the options schema.
const validation = await registry.compile(info.optionSchema);
Expand Down
39 changes: 39 additions & 0 deletions packages/angular_devkit/architect/src/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @license
* Copyright Google LLC 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.dev/license
*/

import { json } from '@angular-devkit/core';

import { BuilderInput } from './api';

type OverrideOptions = BuilderInput['options'];

export function mergeOptions(
baseOptions: json.JsonObject,
overrideOptions: OverrideOptions,
): json.JsonObject {
if (!overrideOptions) {
return { ...baseOptions };
}

const options = {
...baseOptions,
...overrideOptions,
};

// For object-object overrides, we merge one layer deep.
for (const key of Object.keys(overrideOptions)) {
const override = overrideOptions[key];
const base = baseOptions[key];

if (json.isJsonObject(base) && json.isJsonObject(override)) {
options[key] = { ...base, ...override };
}
}

return options;
}
70 changes: 70 additions & 0 deletions packages/angular_devkit/architect/src/options_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* @license
* Copyright Google LLC 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.dev/license
*/

import { mergeOptions } from './options';

describe('mergeOptions', () => {
it('overwrites literal values', () => {
expect(
mergeOptions(
{
onlyBase: 'base',
a: 'foo',
b: 42,
c: true,
},
{
onlyOverride: 'override',
a: 'bar',
b: 43,
c: false,
},
),
).toEqual({
onlyBase: 'base',
a: 'bar',
b: 43,
c: false,
onlyOverride: 'override',
});
});

it('merges object values one layer deep', () => {
expect(
mergeOptions(
{
obj: {
nested: {
fromBase: true,
},
fromBase: true,
overridden: false,
},
},
{
obj: {
nested: {
fromOverride: true,
},
overridden: true,
fromOverride: true,
},
},
),
).toEqual({
obj: {
nested: {
fromOverride: true,
},
fromBase: true,
overridden: true,
fromOverride: true,
},
});
});
});

0 comments on commit 97e7b66

Please sign in to comment.