powershell/internal/project.ts (384 lines of code) (raw):

/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Dictionary, values } from '@azure-tools/linq'; import { SchemaDetails, LanguageDetails, EnhancedTypeDeclaration, Boolean, SchemaDefinitionResolver, } from '../llcsharp/exports'; import { State } from './state'; import { Project as codeDomProject } from '@azure-tools/codegen-csharp'; import { ModelExtensionsNamespace } from '../models/model-extensions'; import { pwshHeaderText } from '../utils/powershell-comment'; import { ModuleNamespace } from '../module/module-namespace'; import { CmdletNamespace } from '../cmdlets/namespace'; import { AutorestExtensionHost as Host } from '@autorest/extension-base'; import { codemodel, PropertyDetails, exportedModels as T, } from '@azure-tools/codemodel-v3'; import { DeepPartial, comment } from '@azure-tools/codegen'; import { PwshModel } from '../utils/PwshModel'; import { ModelState } from '../utils/model-state'; import { BooleanSchema, ChoiceSchema, ConstantSchema, Schema as NewSchema, SchemaType, } from '@autorest/codemodel'; import { TspHost } from '../utils/tsp-host'; export type Schema = T.SchemaT< LanguageDetails<SchemaDetails>, LanguageDetails<PropertyDetails> >; export interface Metadata { authors: string; owners: string; requireLicenseAcceptance: boolean; description: string; copyright: string; tags: string; companyName: string; licenseUri: string; projectUri: string; requiredModules: Array<PsRequiredModule>; requiredModulesAsString: string; requiredAssemblies: Array<string>; requiredAssembliesAsString: string; nestedModules: Array<string>; nestedModulesAsString: string; formatsToProcess: Array<string>; formatsToProcessAsString: string; typesToProcess: Array<string>; typesToProcessAsString: string; scriptsToProcess: Array<string>; scriptsToProcessAsString: string; functionsToExport: Array<string>; functionsToExportAsString: string; cmdletsToExport: Array<string>; cmdletsToExportAsString: string; aliasesToExport: Array<string>; aliasesToExportAsString: string; } export interface PsRequiredModule { name: string; version: string; } interface ModelCmdletDirective { 'model-name': string; 'cmdlet-name'?: string } export class NewPSSwitch extends Boolean { get declaration(): string { return `global::System.Management.Automation.SwitchParameter${this.isRequired ? '' : '?'}`; } } export class PSSchemaResolver extends SchemaDefinitionResolver { inResolve = false; resolveTypeDeclaration( schema: NewSchema | undefined, required: boolean, state: ModelState<PwshModel>, isFixedArray?: boolean ): EnhancedTypeDeclaration { const before = this.inResolve; try { if (!this.inResolve) { this.inResolve = true; if ( schema && (schema.type === SchemaType.Boolean || (schema.type === SchemaType.Constant && (<ConstantSchema>schema).valueType.type === SchemaType.Boolean) || (schema.type === SchemaType.Choice && (<any>schema).choiceType.type === SchemaType.Boolean)) ) { return new NewPSSwitch(<BooleanSchema>schema, required); } } return super.resolveTypeDeclaration(schema, required, state, isFixedArray); } finally { this.inResolve = before; } } } export class Project extends codeDomProject { public azure!: boolean; public addToString!: boolean; public license!: string; public pwshCommentHeader!: string; public pwshCommentHeaderForCsharp!: string; public csharpCommentHeader!: string; public csharpCommentHeaderForCsharp!: string; public cmdletFolder!: string; public modelCmdletFolder!: string; public endpointResourceIdKeyName!: string; public endpointSuffixKeyName!: string; public customFolder!: string; public utilsFolder!: string; public internalFolder!: string; public testFolder!: string; public runtimeFolder!: string; public binFolder!: string; public objFolder!: string; public exportsFolder!: string; public docsFolder!: string; public examplesFolder!: string; public resourcesFolder!: string; public uxFolder!: string; public serviceName!: string; public moduleName!: string; public title!: string; public rootModuleName!: string; public csproj!: string; public nuspec!: string; public gitIgnore!: string; public gitAttributes!: string; public propertiesExcludedForTableview!: string; public readme!: string; public afterBuildTasksPath!: string; public afterBuildTasksArgs!: string; public assemblyInfoPath!: string; public assemblyCompany!: string; public assemblyProduct!: string; public assemblyCopyright!: string; public dllName!: string; public dll!: string; public psd1!: string; public psm1!: string; public psm1Custom!: string; public psm1Internal!: string; public formatPs1xml!: string; public autoSwitchView!: boolean; public apiFolder!: string; public baseFolder!: string; public moduleFolder!: string; public schemaDefinitionResolver!: SchemaDefinitionResolver; public moduleVersion!: string; public profiles!: Array<string>; public modelCmdlets!: Array<ModelCmdletDirective>; public modelCmdletsInPS!: string; public inputHandlers!: Array<string>; public prefix!: string; public subjectPrefix!: string; public projectNamespace!: string; public overrides!: Dictionary<string>; public serviceNamespace!: ModuleNamespace; public cmdlets!: CmdletNamespace; public modelsExtensions!: ModelExtensionsNamespace; public accountsVersionMinimum!: string; public dependencyModuleFolder!: string; public metadata!: Metadata; public state!: State; public helpLinkPrefix!: string; public fixedArray!: boolean; get model() { return <PwshModel>this.state.model; } constructor( protected service: Host | TspHost, objectInitializer?: DeepPartial<Project> ) { super(); this.apply(objectInitializer); } public async init(state?: ModelState<PwshModel>): Promise<this> { await super.init(); this.state = await new State(this.service).init(this); if (state) { this.state.model = state.model; } this.schemaDefinitionResolver = new PSSchemaResolver(this.state.project.fixedArray); this.projectNamespace = this.state.model.language.csharp?.namespace || ''; this.overrides = { 'Carbon.Json.Converters': `${this.projectNamespace}.Runtime.Json`, 'Carbon.Internal.Extensions': `${this.projectNamespace}.Runtime.Json`, 'Carbon.Internal': `${this.projectNamespace}.Runtime.Json`, 'Carbon.Data': `${this.projectNamespace}.Runtime.Json`, 'using Data;': '', 'using Parser;': '', 'using Converters;': '', 'using Internal.Extensions;': '', 'Carbon.Json.Parser': `${this.projectNamespace}.Runtime.Json`, 'Carbon.Json': `${this.projectNamespace}.Runtime.Json`, 'Microsoft.Rest.ClientRuntime': `${this.projectNamespace}.Runtime`, 'Microsoft.Message.ClientRuntime': `${this.projectNamespace}.Runtime`, 'Microsoft.generated.runtime.Properties': `${this.projectNamespace}.generated.runtime.Properties`, 'Microsoft.Rest': `${this.projectNamespace}`, }; // Values this.moduleVersion = await this.state.getValue('module-version'); // skip-for-time-being //this.profiles = this.model.info.extensions['x-ms-metadata'].profiles || []; this.profiles = []; this.accountsVersionMinimum = '2.7.5'; this.helpLinkPrefix = await this.state.getValue('help-link-prefix'); this.metadata = await this.state.getValue<Metadata>('metadata'); this.preprocessMetadata(); this.license = await this.state.getValue('header-text', ''); const pwshLicenseHeader = await this.state.getValue( 'pwsh-license-header', '' ); // if pwsh license header is not set, use the license set by license-header this.pwshCommentHeader = comment( pwshLicenseHeader ? pwshHeaderText( pwshLicenseHeader, await this.service.getValue('header-definitions') ) : this.license, '#' ); this.pwshCommentHeaderForCsharp = this.pwshCommentHeader.replace( /"/g, '""' ); this.csharpCommentHeader = comment( pwshLicenseHeader ? pwshHeaderText( pwshLicenseHeader, await this.service.getValue('header-definitions') ) : this.license, '//' ); this.csharpCommentHeaderForCsharp = this.csharpCommentHeader.replace(/"/g, '""'); // modelcmdlets are models that we will create cmdlets for. this.modelCmdlets = []; let directives: Array<any> = []; const allDirectives = await this.state.service.getValue('directive'); directives = values(<any>allDirectives).toArray(); for (const directive of directives.filter((each) => each['model-cmdlet'])) { this.modelCmdlets = this.modelCmdlets.concat( <ConcatArray<ModelCmdletDirective>>values(directive['model-cmdlet']).toArray() ); } this.modelCmdletsInPS = this.modelCmdlets.map(each => `@{modelName="${each['model-name']}"; cmdletName="${each['cmdlet-name'] || ''}"}`).join(', ') || ''; // input handlers this.inputHandlers = await this.state.getValue('input-handlers', []); // Flags this.azure = this.model.language.default.isAzure; this.addToString = await this.state.getValue('nested-object-to-string', this.azure ? true : false); // Names this.prefix = this.model.language.default.prefix; this.serviceName = this.model.language.default.serviceName; this.subjectPrefix = this.model.language.default.subjectPrefix; this.moduleName = await this.state.getValue('module-name'); this.title = await this.state.getValue('title'); this.rootModuleName = await this.state.getValue('root-module-name', ''); this.dllName = await this.state.getValue('dll-name'); // Azure PowerShell data plane configuration if (this.azure) { this.endpointResourceIdKeyName = await this.state.getValue( 'endpoint-resource-id-key-name', '' ); this.endpointSuffixKeyName = await this.state.getValue( 'endpoint-suffix-key-name', '' ); } // Folders this.baseFolder = await this.state.getValue('current-folder'); this.moduleFolder = await this.state.getValue('module-folder'); this.cmdletFolder = await this.state.getValue('cmdlet-folder'); this.modelCmdletFolder = await this.state.getValue('model-cmdlet-folder'); this.customFolder = await this.state.getValue('custom-cmdlet-folder'); this.utilsFolder = await this.state.getValue('utils-cmdlet-folder'); this.internalFolder = await this.state.getValue('internal-cmdlet-folder'); this.testFolder = await this.state.getValue('test-folder'); this.runtimeFolder = await this.state.getValue('runtime-folder'); this.apiFolder = await this.state.getValue('api-folder'); this.binFolder = await this.state.getValue('bin-folder'); this.objFolder = await this.state.getValue('obj-folder'); this.exportsFolder = await this.state.getValue('exports-folder'); this.docsFolder = await this.state.getValue('docs-folder'); this.dependencyModuleFolder = await this.state.getValue( 'dependency-module-folder' ); this.examplesFolder = await this.state.getValue('examples-folder'); this.resourcesFolder = await this.state.getValue('resources-folder'); this.uxFolder = await this.state.getValue('ux-folder'); // configuration for whether to use fixed array in generated code of model, default is false this.fixedArray = await this.state.getValue('fixed-array', false); // File paths this.csproj = await this.state.getValue('csproj'); this.dll = await this.state.getValue('dll'); this.psd1 = await this.state.getValue('psd1'); this.psm1 = await this.state.getValue('psm1'); this.psm1Custom = await this.state.getValue('psm1-custom'); this.psm1Internal = await this.state.getValue('psm1-internal'); this.formatPs1xml = await this.state.getValue('format-ps1xml'); this.autoSwitchView = await this.state.getValue('auto-switch-view', true); this.nuspec = await this.state.getValue('nuspec'); this.gitIgnore = `${this.baseFolder}/.gitignore`; this.gitAttributes = `${this.baseFolder}/.gitattributes`; this.readme = `${this.baseFolder}/README.md`; this.afterBuildTasksPath = await this.state.getValue('after-build-tasks-path', ''); const afterBuildTasksArgsDictionary: Dictionary<string> = await this.state.getValue<Dictionary<string>>('after-build-tasks-args', {}); this.afterBuildTasksArgs = JSON.stringify(afterBuildTasksArgsDictionary); this.assemblyInfoPath = await this.state.getValue('assemblyInfo-path', ''); this.assemblyCompany = await this.state.getValue('assembly-company', ''); this.assemblyProduct = await this.state.getValue('assembly-product', ''); this.assemblyCopyright = await this.state.getValue('assembly-copyright', ''); // excluded properties in table view const excludedList = <Array<string>>( values( <any>await this.state.getValue('exclude-tableview-properties', []) ).toArray() ); this.propertiesExcludedForTableview = excludedList ? excludedList.join(',') : ''; // add project namespace this.serviceNamespace = new ModuleNamespace(this.state); this.serviceNamespace.header = this.license; this.addNamespace(this.serviceNamespace); this.modelsExtensions = new ModelExtensionsNamespace( this.serviceNamespace, <any>this.state.model.schemas, this.state.path('components', 'schemas') ); this.modelsExtensions.header = this.license; this.addNamespace(this.modelsExtensions); // add cmdlet namespace this.cmdlets = await new CmdletNamespace( this.serviceNamespace, this.state ).init(); this.cmdlets.header = this.license; this.addNamespace(this.cmdlets); // abort now if we have any errors. this.state.checkpoint(); return this; } /** * Preprocess some list properties in metadata to string properties, * so they can be easily used in csharp templates. */ private preprocessMetadata() { if (this.metadata) { this.metadata = { ...this.metadata, requiredModulesAsString: this.metadata.requiredModules ? this.metadata.requiredModules .map( (m) => `@{ModuleName = '${m.name}'; ModuleVersion = '${m.version}'}` ) ?.join(', ') : 'undefined', requiredAssembliesAsString: this.convertToPsListAsString( this.metadata.requiredAssemblies ), nestedModulesAsString: this.convertToPsListAsString( this.metadata.nestedModules ), formatsToProcessAsString: this.convertToPsListAsString( this.metadata.formatsToProcess ), typesToProcessAsString: this.convertToPsListAsString( this.metadata.typesToProcess ), scriptsToProcessAsString: this.convertToPsListAsString( this.metadata.scriptsToProcess ), functionsToExportAsString: this.convertToPsListAsString( this.metadata.functionsToExport ), cmdletsToExportAsString: this.convertToPsListAsString( this.metadata.cmdletsToExport ), aliasesToExportAsString: this.convertToPsListAsString( this.metadata.aliasesToExport ), }; } } private convertToPsListAsString(items: Array<string>): string { return items ? items.map((i) => `'${i}'`).join(', ') : 'undefined'; } }