diff --git a/libs/feature-run/src/lib/graphql/projects.graphql b/libs/feature-run/src/lib/graphql/projects.graphql index 99e64c518b..4ed29161bb 100644 --- a/libs/feature-run/src/lib/graphql/projects.graphql +++ b/libs/feature-run/src/lib/graphql/projects.graphql @@ -7,8 +7,18 @@ query Projects($path: String!, $project: String!, $target: String!) { architect(name: $target) { name builder + options { + defaultValues { + name + defaultValue + } + } configurations { name + defaultValues { + name + defaultValue + } } schema { name diff --git a/libs/feature-run/src/lib/target/target.component.html b/libs/feature-run/src/lib/target/target.component.html index 67fd718063..5ea1d71688 100644 --- a/libs/feature-run/src/lib/target/target.component.html +++ b/libs/feature-run/src/lib/target/target.component.html @@ -4,6 +4,7 @@ > ; +} + export interface ArchitectConfigurations { name: string; + + defaultValues: FieldValue[]; } export interface RecentAction { @@ -1158,6 +1172,8 @@ export namespace ArchitectResolvers { description?: DescriptionResolver; + options?: OptionsResolver; + configurations?: ConfigurationsResolver; schema?: SchemaResolver; @@ -1183,6 +1199,11 @@ export namespace ArchitectResolvers { Parent = any, Context = any > = Resolver; + export type OptionsResolver = Resolver< + R, + Parent, + Context + >; export type ConfigurationsResolver< R = any[], Parent = any, @@ -1195,9 +1216,42 @@ export namespace ArchitectResolvers { >; } +export namespace OptionsResolvers { + export interface Resolvers { + defaultValues?: DefaultValuesResolver; + } + + export type DefaultValuesResolver< + R = any[], + Parent = any, + Context = any + > = Resolver; +} + +export namespace FieldValueResolvers { + export interface Resolvers { + name?: NameResolver; + + defaultValue?: DefaultValueResolver, TypeParent, Context>; + } + + export type NameResolver = Resolver< + R, + Parent, + Context + >; + export type DefaultValueResolver< + R = Maybe, + Parent = any, + Context = any + > = Resolver; +} + export namespace ArchitectConfigurationsResolvers { export interface Resolvers { name?: NameResolver; + + defaultValues?: DefaultValuesResolver; } export type NameResolver = Resolver< @@ -1205,6 +1259,11 @@ export namespace ArchitectConfigurationsResolvers { Parent, Context >; + export type DefaultValuesResolver< + R = any[], + Parent = any, + Context = any + > = Resolver; } export namespace RecentActionResolvers { @@ -1918,6 +1977,8 @@ export interface IResolvers { NpmScript?: NpmScriptResolvers.Resolvers; Project?: ProjectResolvers.Resolvers; Architect?: ArchitectResolvers.Resolvers; + Options?: OptionsResolvers.Resolvers; + FieldValue?: FieldValueResolvers.Resolvers; ArchitectConfigurations?: ArchitectConfigurationsResolvers.Resolvers; RecentAction?: RecentActionResolvers.Resolvers; Docs?: DocsResolvers.Resolvers; diff --git a/libs/server/src/assets/schema.graphql b/libs/server/src/assets/schema.graphql index c56b41079e..6dbf3f7221 100644 --- a/libs/server/src/assets/schema.graphql +++ b/libs/server/src/assets/schema.graphql @@ -3,12 +3,18 @@ type Architect { project: String! builder: String! description: String! + options: Options!, configurations: [ArchitectConfigurations!]! schema: [Schema!]! } +type Options { + defaultValues: [FieldValue!]! +} + type ArchitectConfigurations { name: String! + defaultValues: [FieldValue!]! } type CommandResponse { @@ -207,6 +213,11 @@ type SchematicCollectionForNgNew { schema: [Schema!]! } +type FieldValue { + name: String! + defaultValue: String +} + type Schema { name: String! type: String! diff --git a/libs/server/src/lib/api/read-projects.ts b/libs/server/src/lib/api/read-projects.ts index c8c4478ade..648c8b868a 100644 --- a/libs/server/src/lib/api/read-projects.ts +++ b/libs/server/src/lib/api/read-projects.ts @@ -1,5 +1,9 @@ -import { normalizeSchema, readJsonFile } from '../utils/utils'; -import { Project, Architect } from '@angular-console/schema'; +import { + getPrimitiveValue, + normalizeSchema, + readJsonFile +} from '../utils/utils'; +import { Architect, Project } from '@angular-console/schema'; import * as path from 'path'; import { Store } from '@nrwl/angular-console-enterprise-electron'; import { readRecentActions } from './read-recent-actions'; @@ -32,11 +36,23 @@ function compareProjects(a: Project, b: Project) { function readArchitect(project: string, architect: any): Architect[] { if (!architect) return []; return Object.entries(architect).map(([key, value]: [string, any]) => { + const options = { + defaultValues: serializeDefaultsForConfig(value.options) + }; const configurations = value.configurations - ? Object.keys(value.configurations).map(name => ({ name })) + ? Object.keys(value.configurations).map(name => ({ + name, + defaultValues: readDefaultValues( + value.options, + value.configurations, + name + ) + })) : []; + return { schema: [], + options, configurations, name: key, project, @@ -46,6 +62,18 @@ function readArchitect(project: string, architect: any): Architect[] { }); } +function readDefaultValues(options: any, configurations: any, name: string) { + return serializeDefaultsForConfig({ ...options, ...configurations[name] }); +} + +function serializeDefaultsForConfig(config: any) { + if (!config) return []; + return Object.keys(config).reduce( + (m, k) => [...m, { name: k, defaultValue: getPrimitiveValue(config[k]) }], + [] as any[] + ); +} + export function readSchema(basedir: string, builder: string) { const [npmPackage, builderName] = builder.split(':'); return readBuildersFile(basedir, npmPackage)[builderName].schema; diff --git a/libs/server/src/lib/utils/utils.ts b/libs/server/src/lib/utils/utils.ts index 3850464d4f..fe915bde2b 100644 --- a/libs/server/src/lib/utils/utils.ts +++ b/libs/server/src/lib/utils/utils.ts @@ -222,6 +222,19 @@ export function normalizeSchema( } } +export function getPrimitiveValue(value: any) { + if ( + typeof value === 'string' || + typeof value === 'number' || + typeof value === 'boolean' || + value === null + ) { + return value.toString(); + } else { + return undefined; + } +} + function getDefault(prop: any): any { if (prop.default === undefined && prop.$default === undefined) { return undefined; diff --git a/libs/ui/src/lib/flags/flags.component.html b/libs/ui/src/lib/flags/flags.component.html index e358c4c23a..287ff520a7 100644 --- a/libs/ui/src/lib/flags/flags.component.html +++ b/libs/ui/src/lib/flags/flags.component.html @@ -12,6 +12,7 @@ name="configurations" fxLayout="row" fxLayoutGap="16px" + (change)="createFormUsingConfiguration($event.value)" > Default = []; @Input() path: string; - @Input() configurations: { name: string }[]; + @Input() options: { defaultValues: { name: string; defaultValue: string }[] }; + @Input() configurations: { + name: string; + defaultValues: { name: string; defaultValue: string }[]; + }[]; @Input() prefix: string[]; @Input() init: { [k: string]: any }; @Input() runSyntax = false; @@ -54,9 +59,8 @@ export class FlagsComponent { return this._fields; } set fields(f: Schema[]) { - this._fields = f; - this.fieldGroups = this.toFieldGroups(f); - this.setForm(); + this._rawFields = f; + this.createFormUsingConfiguration(null); } @Output() readonly value = new EventEmitter(); @@ -109,13 +113,14 @@ export class FlagsComponent { } } - private setForm() { + private setForm(configuration: string | null) { this.formGroup = schematicFieldsToFormGroup({ fields: this._fields, configurations: this.configurations && this.configurations.length > 0, init: this.init, getCompletions: (f, v) => - this.completions.completionsFor(this.path, f, v || '') + this.completions.completionsFor(this.path, f, v || ''), + selectedConfiguration: configuration }); if (this.subscription) { @@ -166,4 +171,34 @@ export class FlagsComponent { }); } } + + createFormUsingConfiguration(configuration: string | null) { + this._fields = this.withConfigurationValues(configuration, this._rawFields); + + this.fieldGroups = this.toFieldGroups(this._fields); + this.setForm(configuration); + } + + private withConfigurationValues(configuration: string | null, f: Schema[]) { + const selectedConfiguration = + this.configurations && + this.configurations.find(c => c.name === configuration); + const selectedOptions = selectedConfiguration + ? selectedConfiguration + : this.options; + return f.map(ff => { + const defaultFromOptions = + selectedOptions && + selectedOptions.defaultValues.find(v => v.name === ff.name); + if (defaultFromOptions) { + const defaultValue = this.serializer.normalizeDefaultValue( + ff.type, + defaultFromOptions.defaultValue + ); + return { ...ff, defaultValue }; + } else { + return ff; + } + }); + } } diff --git a/libs/ui/src/lib/schematic-fields/schematic-fields.component.ts b/libs/ui/src/lib/schematic-fields/schematic-fields.component.ts index daf9954faf..7ad57f7b1b 100644 --- a/libs/ui/src/lib/schematic-fields/schematic-fields.component.ts +++ b/libs/ui/src/lib/schematic-fields/schematic-fields.component.ts @@ -43,6 +43,7 @@ export interface Payload { ) => Observable; init?: { [k: string]: any }; configurations?: boolean; + selectedConfiguration: string | null; } export const schematicFieldsToFormGroup = (payload: Payload): FormGroup => { @@ -106,7 +107,9 @@ export const schematicFieldsToFormGroup = (payload: Payload): FormGroup => { {} as any ); if (configurations) { - children.configurations = new FormControl(null); + children.configurations = new FormControl( + payload.selectedConfiguration ? payload.selectedConfiguration : null + ); } return new FormGroup(children); diff --git a/libs/utils/src/lib/serializer.service.ts b/libs/utils/src/lib/serializer.service.ts index 8059bdcd88..983b02c9ea 100644 --- a/libs/utils/src/lib/serializer.service.ts +++ b/libs/utils/src/lib/serializer.service.ts @@ -45,16 +45,23 @@ export class Serializer { ...fields.filter(r => !r.positional && !r.important) ].map(f => { let d = f.defaultValue as any; - if (f.type === 'boolean' && f.defaultValue === 'false') { - d = (f as any).defaultValue = false; - } - if (f.type === 'boolean' && f.defaultValue === 'true') { - d = (f as any).defaultValue = true; - } + d = (f as any).defaultValue = this.normalizeDefaultValue(f.type, d); return { ...f, defaultValue: d }; }); } + normalizeDefaultValue(type: string, defaultValue: string): any { + if (type === 'boolean' && defaultValue === 'false') { + return false; + } else if (type === 'boolean' && defaultValue === 'true') { + return true; + } else if (type === 'number') { + return Number(defaultValue); + } else { + return defaultValue; + } + } + serializeArgs(value: { [p: string]: any }, schema: Schema[]): string[] { const fields = schema.filter( s =>