Skip to content

Commit

Permalink
fix: strf-4307 Frontmatter/yaml validation and trailing symbols checks (
Browse files Browse the repository at this point in the history
  • Loading branch information
jairo-bc authored Nov 19, 2021
1 parent 9688536 commit c1cf1ce
Show file tree
Hide file tree
Showing 14 changed files with 460 additions and 22 deletions.
64 changes: 64 additions & 0 deletions lib/bundle-validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ const fs = require('fs');
const sizeOf = require('image-size');
const path = require('path');
const Validator = require('ajv');
const yamlValidator = require('js-yaml');

const { recursiveReadDir } = require('./utils/fsUtils');
const { getFrontmatterContent, interpolateThemeSettings } = require('./utils/frontmatter');
const ValidatorSchemaTranslations = require('./validator/schema-translations');
const privateThemeConfigValidationSchema = require('./schemas/privateThemeConfig.json');
const themeConfigValidationSchema = require('./schemas/themeConfig.json');
Expand Down Expand Up @@ -43,6 +46,7 @@ class BundleValidator {
this._validateThemeConfiguration.bind(this),
this._validateThemeSchema.bind(this),
this._validateSchemaTranslations.bind(this),
this._validateTemplatesFrontmatter.bind(this),
];

if (!this.isPrivate) {
Expand Down Expand Up @@ -371,6 +375,66 @@ class BundleValidator {
cb(null, true);
});
}

async _validateTemplatesFrontmatter() {
const config = await this.themeConfig.getRawConfig();
const filePaths = await recursiveReadDir(path.join(this.themePath, 'templates'), [
'!*.html',
]);

for await (const filePath of filePaths) {
const fileContent = await fs.promises.readFile(filePath, { encoding: 'utf-8' });
const frontmatter = getFrontmatterContent(fileContent);
if (frontmatter) {
const yaml = interpolateThemeSettings(frontmatter, config.settings);

try {
const result = yamlValidator.loadAll(yaml);
this.validateTrailingSymbols(result[0]);
if (filePath.includes('home.html')) {
console.log(filePath);
console.log(yaml);
console.log(result[0].products.new.limit);
}
} catch (e) {
throw new Error(
`Error: ${e.message}, while parsing frontmatter at "${filePath}".`.red,
);
}
}
}

return true;
}

validateTrailingSymbols(data) {
if (_.isObject(data)) {
return _.every(data, (value) => this.validateTrailingSymbols(value));
}
if (_.isArray(data)) {
return data.every((row) => this.validateTrailingSymbols(row));
}

return data ? this.hasFrontmatterValidValue(data) : true;
}

hasFrontmatterValidValue(value) {
if (this.hasUnallowedTrailingSymbol(value)) {
throw new Error(`Found unallowed trailing symbol in: "${value}"`);
}

return true;
}

getUnallowedTrailingSymbols() {
return [',', ';'];
}

hasUnallowedTrailingSymbol(value) {
const symbols = this.getUnallowedTrailingSymbols();
const trailingSymbol = value.toString().trim().slice(-1);
return symbols.includes(trailingSymbol);
}
}

module.exports = BundleValidator;
15 changes: 13 additions & 2 deletions lib/bundle-validator.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,12 @@ describe('BundleValidator', () => {
).rejects.toThrow('Missing required objects/properties: footer.scripts');
});

it('should validate theme schema successfully', async () => {
it('should validate theme schema and frontmatter successfully', async () => {
const validator = new BundleValidator(themePath, themeConfig, false);

const res = await promisify(validator.validateTheme.bind(validator))();

expect(res).toHaveLength(4); // 4 validation tasks
expect(res).toHaveLength(5); // 5 validation tasks
expect(res).not.toContain(false);
});

Expand All @@ -115,4 +115,15 @@ describe('BundleValidator', () => {
"schema[0].settings[0] should have required property 'content'",
);
});

it('should validate theme frontmatter and throw an error on invalid frontmatter', async () => {
const themePath2 = path.join(process.cwd(), 'test/_mocks/themes/invalid-frontmatter');
themeConfig = ThemeConfig.getInstance(themePath2);

const validator = new BundleValidator(themePath2, themeConfig, false);

await expect(promisify(validator.validateTheme.bind(validator))()).rejects.toThrow(
'while parsing frontmatter',
);
});
});
33 changes: 33 additions & 0 deletions lib/utils/frontmatter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const frontmatterRegex = /---\r?\n(?:.|\s)*?\r?\n---\r?\n/g;

/**
*
* @param {String} file
* @returns {String|null}
*/
function getFrontmatterContent(file) {
const frontmatterMatch = file.match(frontmatterRegex);
return frontmatterMatch !== null ? frontmatterMatch[0] : null;
}

/**
*
* @param {String} frontmatter
* @param {Object} settings
* @returns {String}
*/
function interpolateThemeSettings(frontmatter, settings) {
for (const [key, val] of Object.entries(settings)) {
const regex = `{{\\s*?theme_settings\\.${key}\\s*?}}`;
// eslint-disable-next-line no-param-reassign
frontmatter = frontmatter.replace(new RegExp(regex, 'g'), val);
}

return frontmatter;
}

module.exports = {
frontmatterRegex,
getFrontmatterContent,
interpolateThemeSettings,
};
81 changes: 72 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"graceful-fs": "^4.2.4",
"image-size": "^0.9.1",
"inquirer": "^8.1.5",
"js-yaml": "^4.1.0",
"lodash": "^4.17.20",
"memory-cache": "^0.2.0",
"npm-which": "^3.0.1",
Expand Down
22 changes: 11 additions & 11 deletions server/plugins/renderer/renderer.module.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ const { readFromStream } = require('../../../lib/utils/asyncUtils');
const NetworkUtils = require('../../../lib/utils/NetworkUtils');
const contentApiClient = require('../../../lib/content-api-client');
const { getPageType } = require('../../lib/page-type-util');
const {
frontmatterRegex,
getFrontmatterContent,
interpolateThemeSettings,
} = require('../../../lib/utils/frontmatter');

const networkUtils = new NetworkUtils();

Expand Down Expand Up @@ -262,7 +267,6 @@ internals.parseResponse = async (bcAppData, request, response, responseArgs) =>
* @returns {Object}
*/
internals.getResourceConfig = (data, request, configuration) => {
const frontmatterRegex = /---\r?\n(?:.|\s)*?\r?\n---\r?\n/g;
const missingThemeSettingsRegex = /{{\\s*?theme_settings\\..+?\\s*?}}/g;
let resourcesConfig = {};
const templatePath = data.template_file;
Expand All @@ -276,16 +280,12 @@ internals.getResourceConfig = (data, request, configuration) => {
templatePath,
);

const frontmatterMatch = rawTemplate.match(frontmatterRegex);
if (frontmatterMatch !== null) {
let frontmatterContent = frontmatterMatch[0];
// Interpolate theme settings for frontmatter
for (const [key, val] of Object.entries(configuration.settings)) {
const regex = `{{\\s*?theme_settings\\.${key}\\s*?}}`;

frontmatterContent = frontmatterContent.replace(new RegExp(regex, 'g'), val);
}

let frontmatterContent = getFrontmatterContent(rawTemplate);
if (frontmatterContent !== null) {
frontmatterContent = interpolateThemeSettings(
frontmatterContent,
configuration.settings,
);
// Remove any handlebars tags that weren't interpolated because there was no setting for it
frontmatterContent = frontmatterContent.replace(missingThemeSettingsRegex, '');
// Replace the frontmatter with the newly interpolated version
Expand Down
Loading

0 comments on commit c1cf1ce

Please sign in to comment.