Skip to content

Commit

Permalink
Merge pull request #167 from entando/ENG-4883-ent-bundle-cli-custom-ftl
Browse files Browse the repository at this point in the history
ENG-4883: makes customElement conditional to ftl file and customUiPath
  • Loading branch information
antromeo authored Jun 1, 2023
2 parents 5817dda + 7181638 commit cf4f6cc
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 14 deletions.
15 changes: 9 additions & 6 deletions src/models/bundle-descriptor-constraints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import {
maxLength,
exclusive,
JsonPath,
JsonValidationError
JsonValidationError,
validateCustomElement
} from '../services/constraints-validator-service'

export const ALLOWED_NAME_REGEXP = /^[\da-z]+(?:(\.|_{1,2}|-+)[\da-z]+)*$/
Expand Down Expand Up @@ -328,7 +329,7 @@ const WIDGET_MICROFRONTEND_CONSTRAINTS: ObjectConstraints<WidgetMicroFrontend> =
children: COMMANDS_CONSTRAINTS
},
customElement: {
required: true,
required: false,
type: 'string'
},
type: {
Expand Down Expand Up @@ -414,7 +415,7 @@ const WIDGETCONFIG_MICROFRONTEND_CONSTRAINTS: ObjectConstraints<WidgetConfigMicr
children: COMMANDS_CONSTRAINTS
},
customElement: {
required: true,
required: false,
type: 'string'
},
type: {
Expand Down Expand Up @@ -487,7 +488,7 @@ const APPBUILDER_MICROFRONTEND_CONSTRAINTS: Array<
children: COMMANDS_CONSTRAINTS
},
customElement: {
required: true,
required: false,
type: 'string'
},
type: {
Expand Down Expand Up @@ -561,7 +562,7 @@ const APPBUILDER_MICROFRONTEND_CONSTRAINTS: Array<
children: COMMANDS_CONSTRAINTS
},
customElement: {
required: true,
required: false,
type: 'string'
},
type: {
Expand Down Expand Up @@ -636,7 +637,9 @@ const MICROFRONTEND_CONSTRAINTS: UnionTypeConstraints<MicroFrontend> = {
{ key: 'paths' },
{ key: 'slot', value: MicroFrontendAppBuilderSlot.Content }
),
exclusive('parentName', 'parentCode')
exclusive('parentName', 'parentCode'),
exclusive('customElement', 'customUiPath'),
validateCustomElement()
]
}

Expand Down
2 changes: 1 addition & 1 deletion src/models/bundle-descriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ type BaseMicroFrontend = {
apiClaims?: Array<ApiClaim | ExternalApiClaim>
commands?: Commands
nav?: Nav[]
customElement: string
customElement?: string
parentName?: string
parentCode?: string
params?: WidgetParam[]
Expand Down
2 changes: 1 addition & 1 deletion src/models/yaml-bundle-descriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type BaseYamlWidgetDescriptor<T extends MicroFrontendType> = {
descriptorVersion: string
type: T
apiClaims?: Array<YamlInternalApiClaim | YamlExternalApiClaim>
customElement: string
customElement?: string
customUiPath?: string
parentName?: string
parentCode?: string
Expand Down
21 changes: 15 additions & 6 deletions src/services/bundle-descriptor-converter-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,19 @@ export class BundleDescriptorConverterService {
`Custom widget template FTL found for MFE ${microFrontend.name}, including it`
)

fs.copyFileSync(
customUiFile,
const { dir, base: fileName } = path.parse(
path.resolve(
...DESCRIPTORS_OUTPUT_FOLDER,
WIDGETS_FOLDER,
path.basename(customUiFile)
)
)

if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
}

fs.copyFileSync(customUiFile, path.join(dir, fileName))
}

const filePath = path.join(
Expand Down Expand Up @@ -153,13 +158,17 @@ export class BundleDescriptorConverterService {
...('titles' in microFrontend && { titles: microFrontend.titles }),
group: microFrontend.group,
descriptorVersion: WIDGET_DESCRIPTOR_VERSION,
customElement: microFrontend.customElement,
...(microFrontend.customElement
? { customElement: microFrontend.customElement }
: {}),
apiClaims: microFrontend.apiClaims
? this.generateYamlApiClaims(microFrontend.apiClaims)
: undefined,
...(customUiFileExists && {
customUiPath: `${microFrontend.name}${CUSTOM_WIDGET_TEMPLATE_EXTENSION}`
}),
...(customUiFileExists
? {
customUiPath: `${microFrontend.name}${CUSTOM_WIDGET_TEMPLATE_EXTENSION}`
}
: {}),
parentName: microFrontend.parentName,
parentCode: microFrontend.parentCode,
params: microFrontend.params || [],
Expand Down
26 changes: 26 additions & 0 deletions src/services/constraints-validator-service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { CLIError } from '@oclif/errors'
import * as fs from 'node:fs'
import path = require('node:path')
import { MICROFRONTENDS_FOLDER } from '../paths'

// Extracts the type wrapped by an array type
type TypeOfArray<T> = T extends Array<infer A>
Expand Down Expand Up @@ -241,6 +244,29 @@ export function exclusive(
}
}

export function validateCustomElement(): UnionTypeValidator {
return function (object: any, jsonPath: JsonPath) {
const mfeName = object.name
const ftlFilePath = `${MICROFRONTENDS_FOLDER}/${mfeName}/${mfeName}.ftl`

const hasFtlFile = fs.existsSync(path.resolve(ftlFilePath))

if (hasFtlFile && object.customElement !== undefined) {
throw new JsonValidationError(
`Field "customElement" is not allowed with custom template (ftl)`,
jsonPath
)
}

if (!hasFtlFile && object.customElement === undefined) {
throw new JsonValidationError(
`Field "customElement" is required without custom template (ftl)`,
jsonPath
)
}
}
}

export class ConstraintsValidatorService {
public static validateObjectConstraints<T>(
parsedObject: unknown,
Expand Down
14 changes: 14 additions & 0 deletions test/helpers/mocks/validator-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as sinon from 'sinon'
import * as fs from 'node:fs'

export function existsSyncMock(pattern: string, result: boolean): void {
const existsSyncStub = sinon.stub(fs, 'existsSync')

existsSyncStub.callsFake(filePath => {
if (new RegExp(pattern).test(filePath.toString())) {
return result
}

return existsSyncStub.wrappedMethod.apply(fs, [filePath])
})
}
111 changes: 111 additions & 0 deletions test/services/constraints-validator-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ import {
MicroFrontendStack,
MicroserviceStack
} from '../../src/models/component'
import * as sinon from 'sinon'
import { existsSyncMock } from '../helpers/mocks/validator-helper'

describe('BundleDescriptorValidatorService', () => {
afterEach(() => {
sinon.restore()
})

test.it('No error thrown with valid object', () => {
ConstraintsValidatorService.validateObjectConstraints(
BundleDescriptorHelper.newBundleDescriptor(),
Expand Down Expand Up @@ -974,6 +980,111 @@ describe('BundleDescriptorValidatorService', () => {
.it(
'Validates microservice with custom stack and all the commands but without version'
)

test
.do(() => {
const validDescriptor: any = BundleDescriptorHelper.newBundleDescriptor()
ConstraintsValidatorService.validateObjectConstraints(
validDescriptor,
BUNDLE_DESCRIPTOR_CONSTRAINTS
)
})
.it('Validates microfrontend with "customElement" and no ftl file')

test
.do(() => {
const invalidDescriptor: any = {
...BundleDescriptorHelper.newBundleDescriptor()
}
const mfe = invalidDescriptor.microfrontends[1]

existsSyncMock(mfe.name, true)

ConstraintsValidatorService.validateObjectConstraints(
invalidDescriptor,
BUNDLE_DESCRIPTOR_CONSTRAINTS
)
})
.catch(error => {
expect(error.message).contain(
'Field "customElement" is not allowed with custom template (ftl)'
)
})
.it('Validates microfrontend with "customElement" and ftl file')

test
.do(() => {
const invalidDescriptor: any = {
...BundleDescriptorHelper.newBundleDescriptor()
}
delete invalidDescriptor.microfrontends[1].customElement

ConstraintsValidatorService.validateObjectConstraints(
invalidDescriptor,
BUNDLE_DESCRIPTOR_CONSTRAINTS
)
})
.catch(error => {
expect(error.message).contain(
'Field "customElement" is required without custom template (ftl)'
)
})
.it('Validates microfrontend with no "customElement" and no ftl file')

test
.do(() => {
const validDescriptor: any = {
...BundleDescriptorHelper.newBundleDescriptor()
}
const mfe = validDescriptor.microfrontends[1]
delete mfe.customElement

existsSyncMock(mfe.name, true)

ConstraintsValidatorService.validateObjectConstraints(
validDescriptor,
BUNDLE_DESCRIPTOR_CONSTRAINTS
)
})
.it('Validates microfrontend with no "customElement" and with ftl file')

test
.do(() => {
const validDescriptor: any = BundleDescriptorHelper.newBundleDescriptor()
const mfe = validDescriptor.microfrontends[1]
mfe.customUiPath = `${mfe.name}.ftl`

ConstraintsValidatorService.validateObjectConstraints(
validDescriptor,
BUNDLE_DESCRIPTOR_CONSTRAINTS
)
})
.catch(error => {
expect(error.message).contain(
'Field "customElement" cannot be present alongside field "customUiPath"'
)
})
.it('Validates microfrontend with "customElement" alongside "customUiPath"')

test
.do(() => {
const validDescriptor: any = {
...BundleDescriptorHelper.newBundleDescriptor()
}
const mfe = validDescriptor.microfrontends[1]
delete mfe.customElement
mfe.customUiPath = `${mfe.name}.ftl`

existsSyncMock(mfe.name, true)

ConstraintsValidatorService.validateObjectConstraints(
validDescriptor,
BUNDLE_DESCRIPTOR_CONSTRAINTS
)
})
.it(
'Validates microfrontend with no "customElement", with ftl file and "customUiPath"'
)
})

describe('Validates YAML descriptor', () => {
Expand Down

0 comments on commit cf4f6cc

Please sign in to comment.