Skip to content

Commit

Permalink
Merge pull request #758 from HubSpot/refactor/theme-validation
Browse files Browse the repository at this point in the history
Refactor theme validation
  • Loading branch information
kemmerle authored Oct 28, 2022
2 parents 0fe4855 + 12fd385 commit cf8899a
Show file tree
Hide file tree
Showing 18 changed files with 146 additions and 1,169 deletions.
42 changes: 42 additions & 0 deletions packages/cli-lib/api/marketplaceValidation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const http = require('../http');

const VALIDATION_API_BASE = 'quality-engine/v1/validation';

/**
* @param {number} accountId
* @returns {Promise}
*/
function requestValidation(accountId, body = {}) {
return http.post(accountId, {
uri: `${VALIDATION_API_BASE}/request`,
body,
});
}

/**
* @param {number} accountId
* @returns {Promise}
*/
function getValidationStatus(accountId, query = {}) {
return http.get(accountId, {
uri: `${VALIDATION_API_BASE}/status`,
query,
});
}

/**
* @param {number} accountId
* @returns {Promise}
*/
function getValidationResults(accountId, query = {}) {
return http.get(accountId, {
uri: `${VALIDATION_API_BASE}/results`,
query,
});
}

module.exports = {
requestValidation,
getValidationStatus,
getValidationResults,
};
11 changes: 6 additions & 5 deletions packages/cli-lib/lang/en.lyaml
Original file line number Diff line number Diff line change
Expand Up @@ -697,15 +697,16 @@ en:
marketplaceValidate:
describe: "Validate a theme for the marketplace"
errors:
invalidPath: "The path \"{{ path }}\" is not a path to a folder"
invalidPath: "The path \"{{ path }}\" is not a path to a folder in the Design Manager"
logs:
validatingTheme: "Validating theme \"{{ path }}\" \n"
options:
json:
describe: "Output raw json data"
results:
required: "Required validation results:"
recommended: "Recommended validation results:"
noErrors: "No errors"
positionals:
src:
describe: "Path to the local theme, relative to your current working directory."
describe: "Path to the theme within the Design Manager."
module:
describe: "Commands for working with modules, including marketplace validation with the marketplace-validate subcommand."
subcommands:
Expand Down
156 changes: 97 additions & 59 deletions packages/cli/commands/theme/marketplace-validate.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
const fs = require('fs');
const path = require('path');
const Spinnies = require('spinnies');
const chalk = require('chalk');

const { getCwd } = require('@hubspot/cli-lib/path');
const { logger } = require('@hubspot/cli-lib/logger');
const { walk } = require('@hubspot/cli-lib');

const {
addConfigOptions,
Expand All @@ -14,17 +12,16 @@ const {
const { loadAndValidateOptions } = require('../../lib/validation');
const { trackCommandUsage } = require('../../lib/usageTracking');
const {
logValidatorResults,
} = require('../../lib/validators/logValidatorResults');
const {
applyAbsoluteValidators,
} = require('../../lib/validators/applyValidators');
const MARKETPLACE_VALIDATORS = require('../../lib/validators');
const { VALIDATION_RESULT } = require('../../lib/validators/constants');
requestValidation,
getValidationStatus,
getValidationResults,
} = require('@hubspot/cli-lib/api/marketplaceValidation');

const { i18n } = require('@hubspot/cli-lib/lib/lang');

const i18nKey = 'cli.commands.theme.subcommands.marketplaceValidate';
const { EXIT_CODES } = require('../../lib/enums/exitCodes');
const SLEEP_TIME = 2000;

exports.command = 'marketplace-validate <src>';
exports.describe = i18n(`${i18nKey}.describe`);
Expand All @@ -35,67 +32,108 @@ exports.handler = async options => {
await loadAndValidateOptions(options);

const accountId = getAccountId(options);
const absoluteSrcPath = path.resolve(getCwd(), src);
let stats;

trackCommandUsage('validate', null, accountId);

const spinnies = new Spinnies();

spinnies.add('marketplaceValidation', {
text: i18n(`${i18nKey}.logs.validatingTheme`, {
path: src,
}),
});

// Kick off validation
let requestResult;
const assetType = 'THEME';
const requestGroup = 'EXTERNAL_DEVELOPER';
try {
stats = fs.statSync(absoluteSrcPath);
if (!stats.isDirectory()) {
logger.error(
i18n(`${i18nKey}.errors.invalidPath`, {
path: src,
})
);
return;
}
} catch (e) {
logger.error(
i18n(`${i18nKey}.errors.invalidPath`, {
path: src,
})
);
return;
requestResult = await requestValidation(accountId, {
path: src,
assetType,
requestGroup,
});
} catch (err) {
logger.debug(err);
process.exit(EXIT_CODES.ERROR);
}

// Poll till validation is finished
try {
const checkValidationStatus = async () => {
const validationStatus = await getValidationStatus(accountId, {
validationId: requestResult,
});

if (validationStatus === 'REQUESTED') {
await new Promise(resolve => setTimeout(resolve, SLEEP_TIME));
await checkValidationStatus();
}
};

await checkValidationStatus();

spinnies.remove('marketplaceValidation');
} catch (err) {
logger.debug(err);
process.exit(EXIT_CODES.ERROR);
}

if (!options.json) {
logger.log(
i18n(`${i18nKey}.logs.validatingTheme`, {
path: src,
})
);
// Fetch the validation results
let validationResults;
try {
validationResults = await getValidationResults(accountId, {
validationId: requestResult,
});
} catch (err) {
logger.debug(err);
process.exit(EXIT_CODES.ERROR);
}

if (validationResults.errors.length) {
const { errors } = validationResults;

errors.forEach(err => {
logger.error(`${err.context}`);
});
process.exit(EXIT_CODES.ERROR);
}
trackCommandUsage('validate', null, accountId);

const themeFiles = await walk(absoluteSrcPath);

applyAbsoluteValidators(
MARKETPLACE_VALIDATORS.theme,
absoluteSrcPath,
themeFiles,
accountId
).then(groupedResults => {
logValidatorResults(groupedResults, { logAsJson: options.json });

if (
groupedResults
.flat()
.some(result => result.result === VALIDATION_RESULT.FATAL)
) {
process.exit(EXIT_CODES.WARNING);
const displayResults = checks => {
if (checks) {
const { status, results } = checks;

if (status === 'FAIL') {
const failedValidations = results.filter(
test => test.status === 'FAIL'
);
failedValidations.forEach(val => {
logger.error(`${val.message}`);
});
}

if (status === 'PASS') {
logger.success(i18n(`${i18nKey}.results.noErrors`));
}
}
});
return null;
};

logger.log(chalk.bold(i18n(`${i18nKey}.results.required`)));
displayResults(validationResults.results['REQUIRED']);
logger.log();
logger.log(chalk.bold(i18n(`${i18nKey}.results.recommended`)));
displayResults(validationResults.results['RECOMMENDED']);
logger.log();

process.exit();
};

exports.builder = yargs => {
addConfigOptions(yargs, true);
addAccountOptions(yargs, true);
addUseEnvironmentOptions(yargs, true);

yargs.options({
json: {
describe: i18n(`${i18nKey}.options.json.describe`),
type: 'boolean',
},
});
yargs.positional('src', {
describe: i18n(`${i18nKey}.positionals.src.describe`),
type: 'string',
Expand Down
28 changes: 0 additions & 28 deletions packages/cli/lib/validators/__tests__/AbsoluteValidator.js

This file was deleted.

101 changes: 0 additions & 101 deletions packages/cli/lib/validators/__tests__/TemplateValidator.js

This file was deleted.

Loading

0 comments on commit cf8899a

Please sign in to comment.