Skip to content

Commit

Permalink
Version 6.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
nsteenbeek committed Feb 9, 2023
2 parents 9e77dce + 3820794 commit 4a6a2c3
Show file tree
Hide file tree
Showing 13 changed files with 1,347 additions and 791 deletions.
7 changes: 7 additions & 0 deletions bin/CustomApis/CustomApis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* This is a generated file, please regenerate and do not modify */

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import {WebApi} from '../WebApi/WebApi';

2 changes: 1 addition & 1 deletion bin/main.js

Large diffs are not rendered by default.

1,862 changes: 1,077 additions & 785 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hso/d365-cli",
"version": "6.0.5",
"version": "6.1.0",
"author": "HSO Innovation <[email protected]> (https://www.hso.com)",
"description": "Dynamics 365 Command Line Interface for TypeScript projects for Dataverse",
"repository": {
Expand Down
176 changes: 176 additions & 0 deletions src/commands/generators/CustomApis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import * as shell from 'shelljs';
import fs from 'fs';
import {WebresourcesCrmJson} from '../../root/Webresources/CrmJson';
import {SolutionService} from '../../node/Solution/Solution.service';
import {CustomApiService} from '../../node/CustomApi/CustomApi.service';
import {SolutionComponentService} from '../../node/SolutionComponent/SolutionComponent.service';
import {CustomApiRequestParameterService} from '../../node/CustomApiRequestParameter/CustomApiRequestParameter.service';
import {CustomApiResponsePropertyService} from '../../node/CustomApiResponseProperty/CustomApiResponseProperty.service';
import {CustomApiModel} from '../../node/CustomApi/CustomApi.model';
import {CustomApiRequestParameterModel} from '../../node/CustomApiRequestParameter/CustomApiRequestParameter.model';
import {CustomApiResponsePropertyModel} from '../../node/CustomApiResponseProperty/CustomApiResponseProperty.model';

export class CustomApis {
private readonly bearer: string;

constructor(bearer: string) {
this.bearer = bearer;
}

public async generate(): Promise<void> {
console.log(`Generating Custom Apis`);
await this.writeCustomApisFile();
console.log('Generated Custom Apis');
}

private async writeCustomApisFile(): Promise<void> {
console.log(`Generating CustomApis/CustomApis.ts`);
const customApisString = await this.getCustomApisString();
shell.cp('-r', `${__dirname}/CustomApis`, `src`);
const customApisFilepath = 'src/CustomApis/CustomApis.ts';
const fileData = String(fs.readFileSync(customApisFilepath));
shell.ShellString(fileData + customApisString).to(customApisFilepath);
shell.exec(`git add src/CustomApis`);
console.log(`Generated CustomApis/CustomApis.ts`);
}

// eslint-disable-next-line max-lines-per-function
private async getCustomApisString(): Promise<string> {
let customApiStrings = '';
const customApis = await this.getCustomApis();

for (const customApi of customApis) {
const pascalSchemaName = CustomApis.capitalize(customApi.uniquename);

const requestParameters = await this.getRequestParameters(customApi);

customApiStrings += `// eslint-disable-next-line @typescript-eslint/no-empty-interface\n`;
customApiStrings += `interface ${pascalSchemaName}Request {\n`;
for (const requestParameter of requestParameters) {
customApiStrings += ` // ${requestParameter.name}\n`;
customApiStrings += ` // ${requestParameter.description}\n`;
customApiStrings += ` // ${requestParameter.displayname}\n`;
// eslint-disable-next-line max-len
customApiStrings += ` ${requestParameter.uniquename}${requestParameter.isoptional ? '?' : ''}: ${CustomApis.getTypeString(requestParameter.type)};\n\n`;
}
if (requestParameters.length === 0) {
customApiStrings += ` //\n`;
}
customApiStrings += `}\n`;

const responseProperties = await this.getResponseProperties(customApi);

customApiStrings += `// eslint-disable-next-line @typescript-eslint/no-empty-interface\n`;
customApiStrings += `interface ${pascalSchemaName}Response {\n`;
for (const responseProperty of responseProperties) {
customApiStrings += ` // ${responseProperty.name}\n`;
customApiStrings += ` // ${responseProperty.description}\n`;
customApiStrings += ` // ${responseProperty.displayname}\n`;
// eslint-disable-next-line max-len
customApiStrings += ` ${responseProperty.uniquename}: ${CustomApis.getTypeString(responseProperty.type)};\n\n`;
}
customApiStrings += `}\n`;
customApiStrings += `// ${customApi.name}\n`;
customApiStrings += `// ${customApi.description}\n`;
customApiStrings += `// ${customApi.displayname}\n`;
customApiStrings += `export const ${pascalSchemaName} = async (request: ${pascalSchemaName}Request): Promise<${pascalSchemaName}Response> => {\n`;
customApiStrings += ` return WebApi.executeAction('${customApi.uniquename}', request);\n`;
customApiStrings += `};\n\n`;
}
if (customApiStrings) {
customApiStrings += '\n';
}
return customApiStrings;
}

private async getCustomApis(): Promise<CustomApiModel[]> {
const settings: WebresourcesCrmJson = JSON.parse(fs.readFileSync('./crm.json', 'utf8'));
const {solution_name_generate} = settings.crm;
const solution = await SolutionService.getSolution(solution_name_generate, ['solutionid'], this.bearer);
const solutionComponents = await SolutionComponentService.retrieveMultipleRecords({
select: ['objectid'],
filters: [{
conditions: [{
attribute: '_solutionid_value',
value: solution.solutionid
}]
}, {
type: 'or',
conditions: [{
attribute: 'componenttype',
value: 10051 // CustomApis
}]
}]
}, this.bearer);
const conditions: Condition[] = [];
for (const solutionComponent of solutionComponents) {
const objectid = solutionComponent.objectid;
conditions.push({
attribute: 'customapiid',
value: objectid,
});
}
return CustomApiService.retrieveMultipleRecords({
select: ['customapiid', 'description', 'displayname', 'name', 'solutionid', 'uniquename'],
filters: [{
type: 'or',
conditions: conditions
}]
}, this.bearer);
}

private getRequestParameters(customApi: CustomApiModel): Promise<CustomApiRequestParameterModel[]> {
return CustomApiRequestParameterService.retrieveMultipleRecords({
select: ['customapirequestparameterid', 'description', 'displayname', 'name', 'solutionid', 'uniquename', 'isoptional', 'type'],
filters: [{
conditions: [{
attribute: '_customapiid_value',
value: customApi.customapiid
}]
}]
}, this.bearer);
}

private getResponseProperties(customApi: CustomApiModel): Promise<CustomApiResponsePropertyModel[]> {
return CustomApiResponsePropertyService.retrieveMultipleRecords({
select: ['customapiresponsepropertyid', 'description', 'displayname', 'name', 'solutionid', 'uniquename', 'type'],
filters: [{
conditions: [{
attribute: '_customapiid_value',
value: customApi.customapiid
}]
}]
}, this.bearer);
}

private static getTypeString(value: number): string {
if (value === 0) {
return 'boolean';
}
if (value === 1) {
return 'Date';
}
if ([2, 6, 7].includes(value)) {
return 'number';
}
if ([3].includes(value)) {
return 'Model';
}
if ([4].includes(value)) {
return 'Model[]';
}
if ([5, 8, 9].includes(value)) {
return 'any';
}
if ([10, 12].includes(value)) {
return 'string';
}
if ([11].includes(value)) {
return 'string[]';
}
}

private static capitalize(text: string): string {
return text.charAt(0).toUpperCase() + text.slice(1);
}
}
11 changes: 7 additions & 4 deletions src/commands/generators/Generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import {ModelRouter} from '../../routers/ModelRouter';
import {LicenseValidator} from './LicenseValidator';
import {EntityRouter} from '../../routers/EntityRouter';
import {GlobalOptionSetRouter} from '../../routers/GlobalOptionSetRouter';
import {CustomApisRouter} from '../../routers/CustomApisRouter';
import {EnvironmentVariableRouter} from '../../routers/EnvironmentVariableRouter';

export class Generator {
public static generate(schematic: string, name: string, options: unknown): Promise<void> {
const supportedSchematics = ['entity', 'webresource', 'model', 'licensevalidator', 'environmentvariable', 'globaloptionsets'];
const supportedSchematics = ['entity', 'webresource', 'model', 'licensevalidator', 'environmentvariable', 'globaloptionsets', 'customapis'];
if (!shell.test('-e', 'src')) {
console.log(colors.red(`You are not inside the project Webresources folder!`));
} else if (!schematic) {
Expand All @@ -20,14 +21,16 @@ export class Generator {
return EntityRouter.generateEntity(name, options);
} else if (schematic.toLowerCase() === 'webresource') {
return Webresource.generateWebresource(name);
} else if(schematic.toLocaleLowerCase() === 'model') {
} else if (schematic.toLocaleLowerCase() === 'model') {
return ModelRouter.generateModel(name);
} else if (schematic.toLowerCase() === 'licensevalidator') {
return LicenseValidator.generateLicenseValidator(name);
} else if(schematic.toLowerCase() === 'environmentvariable') {
} else if (schematic.toLowerCase() === 'environmentvariable') {
return EnvironmentVariableRouter.generateEnvironmentVariable();
} else if(schematic.toLowerCase() === 'globaloptionsets') {
} else if (schematic.toLowerCase() === 'globaloptionsets') {
return GlobalOptionSetRouter.generateGlobalOptionSets();
} else if (schematic.toLowerCase() === 'customapis') {
return CustomApisRouter.generateCustomApis();
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/node/CustomApi/CustomApi.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

export interface CustomApiModel extends Model {
customapiid?: string;
description?: string;
displayname?: string;
name?: string;
solutionid?: string;
uniquename?: string;
}
10 changes: 10 additions & 0 deletions src/node/CustomApi/CustomApi.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {NodeApi} from '../NodeApi/NodeApi';
import {CustomApiModel} from './CustomApi.model';

export class CustomApiService {
private static entitySetName = 'customapis';

public static async retrieveMultipleRecords(multipleSystemQueryOptions: MultipleSystemQueryOptions, bearer: string): Promise<CustomApiModel[]> {
return NodeApi.retrieveMultipleRecords(CustomApiService.entitySetName, multipleSystemQueryOptions, bearer);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

// https://learn.microsoft.com/en-us/power-apps/developer/data-platform/webapi/reference/customapirequestparameter?view=dataverse-latest
export type CustomApiType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;

export interface CustomApiRequestParameterModel extends Model {
customapirequestparameterid?: string;
description?: string;
displayname?: string;
name?: string;
solutionid?: string;
uniquename?: string;
isoptional?: boolean;
type?: CustomApiType;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {NodeApi} from '../NodeApi/NodeApi';
import {CustomApiRequestParameterModel} from './CustomApiRequestParameter.model';

export class CustomApiRequestParameterService {
private static entitySetName = 'customapirequestparameters';

public static async retrieveMultipleRecords(multipleSystemQueryOptions: MultipleSystemQueryOptions, bearer: string): Promise<CustomApiRequestParameterModel[]> {
return NodeApi.retrieveMultipleRecords(CustomApiRequestParameterService.entitySetName, multipleSystemQueryOptions, bearer);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {CustomApiType} from '../CustomApiRequestParameter/CustomApiRequestParameter.model';

export interface CustomApiResponsePropertyModel extends Model {
customapiresponsepropertyid?: string;
description?: string;
displayname?: string;
name?: string;
solutionid?: string;
uniquename?: string;
type?: CustomApiType;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {NodeApi} from '../NodeApi/NodeApi';
import {CustomApiResponsePropertyModel} from './CustomApiResponseProperty.model';

export class CustomApiResponsePropertyService {
private static entitySetName = 'customapiresponseproperties';

public static async retrieveMultipleRecords(multipleSystemQueryOptions: MultipleSystemQueryOptions, bearer: string): Promise<CustomApiResponsePropertyModel[]> {
return NodeApi.retrieveMultipleRecords(CustomApiResponsePropertyService.entitySetName, multipleSystemQueryOptions, bearer);
}
}
14 changes: 14 additions & 0 deletions src/routers/CustomApisRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {MsalRouter} from './MsalRouter';
import {CustomApis} from '../commands/generators/CustomApis';

export class CustomApisRouter extends MsalRouter {
public static generateCustomApis(): Promise<void> {
new CustomApisRouter();
return null;
}

protected async onAuthenticated(): Promise<void> {
const customApis = new CustomApis(this.bearer);
await customApis.generate();
}
}

0 comments on commit 4a6a2c3

Please sign in to comment.