@hubspot/cli 4.0.2-beta.6 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/accounts/list.js +4 -1
- package/commands/project/watch.js +10 -5
- package/commands/sandbox/create.js +3 -0
- package/commands/sandbox/delete.js +17 -3
- package/commands/secrets/addSecret.js +2 -1
- package/commands/secrets/deleteSecret.js +2 -1
- package/commands/secrets/listSecrets.js +2 -1
- package/commands/secrets/updateSecret.js +3 -1
- package/commands/theme/marketplace-validate.js +97 -59
- package/lib/prompts/accountsPrompt.js +3 -8
- package/lib/prompts/sandboxesPrompt.js +16 -1
- package/lib/validators/__tests__/validatorTestUtils.js +0 -28
- package/lib/validators/applyValidators.js +1 -17
- package/lib/validators/constants.js +0 -5
- package/lib/validators/index.js +0 -13
- package/package.json +4 -4
- package/lib/validators/__tests__/AbsoluteValidator.js +0 -28
- package/lib/validators/__tests__/TemplateValidator.js +0 -101
- package/lib/validators/__tests__/ThemeConfigValidator.js +0 -70
- package/lib/validators/__tests__/ThemeDependencyValidator.js +0 -89
- package/lib/validators/__tests__/ThemeModuleValidator.js +0 -84
- package/lib/validators/marketplaceValidators/AbsoluteValidator.js +0 -50
- package/lib/validators/marketplaceValidators/theme/SectionValidator.js +0 -96
- package/lib/validators/marketplaceValidators/theme/TemplateValidator.js +0 -175
- package/lib/validators/marketplaceValidators/theme/ThemeConfigValidator.js +0 -106
- package/lib/validators/marketplaceValidators/theme/ThemeDependencyValidator.js +0 -126
- package/lib/validators/marketplaceValidators/theme/ThemeModuleValidator.js +0 -117
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const templates = require('@hubspot/cli-lib/templates');
|
|
3
|
-
|
|
4
|
-
const TemplateValidator = require('../marketplaceValidators/theme/TemplateValidator');
|
|
5
|
-
const { VALIDATION_RESULT } = require('../constants');
|
|
6
|
-
const {
|
|
7
|
-
generateTemplatesList,
|
|
8
|
-
makeFindError,
|
|
9
|
-
THEME_PATH,
|
|
10
|
-
} = require('./validatorTestUtils');
|
|
11
|
-
|
|
12
|
-
jest.mock('fs');
|
|
13
|
-
jest.mock('@hubspot/cli-lib/templates');
|
|
14
|
-
|
|
15
|
-
const TEMPLATE_LIMIT = 50;
|
|
16
|
-
|
|
17
|
-
const mockGetAnnotationValue = (templateType, rest) => {
|
|
18
|
-
templates.buildAnnotationValueGetter.mockImplementation(() => {
|
|
19
|
-
return key => {
|
|
20
|
-
if (key === 'templateType') {
|
|
21
|
-
return templateType;
|
|
22
|
-
}
|
|
23
|
-
return rest;
|
|
24
|
-
};
|
|
25
|
-
});
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const findError = makeFindError('template');
|
|
29
|
-
|
|
30
|
-
describe('validators/marketplaceValidators/theme/TemplateValidator', () => {
|
|
31
|
-
beforeEach(() => {
|
|
32
|
-
TemplateValidator.setAbsolutePath(THEME_PATH);
|
|
33
|
-
templates.isCodedFile.mockReturnValue(true);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('returns error if template limit is exceeded', async () => {
|
|
37
|
-
mockGetAnnotationValue('page');
|
|
38
|
-
|
|
39
|
-
const validationErrors = TemplateValidator.validate(
|
|
40
|
-
generateTemplatesList(TEMPLATE_LIMIT + 1)
|
|
41
|
-
);
|
|
42
|
-
const limitError = findError(validationErrors, 'limitExceeded');
|
|
43
|
-
expect(limitError).toBeDefined();
|
|
44
|
-
expect(validationErrors[0].result).toBe(VALIDATION_RESULT.FATAL);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('returns no errors if template limit is not exceeded', async () => {
|
|
48
|
-
mockGetAnnotationValue('page');
|
|
49
|
-
|
|
50
|
-
const validationErrors = TemplateValidator.validate(
|
|
51
|
-
generateTemplatesList(TEMPLATE_LIMIT)
|
|
52
|
-
);
|
|
53
|
-
const limitError = findError(validationErrors, 'limitExceeded');
|
|
54
|
-
expect(limitError).not.toBeDefined();
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('returns error if template annotation is missing label and screenshotPath', async () => {
|
|
58
|
-
fs.readFileSync.mockReturnValue('mock');
|
|
59
|
-
mockGetAnnotationValue('page');
|
|
60
|
-
|
|
61
|
-
const validationErrors = TemplateValidator.validate(['template.html']);
|
|
62
|
-
expect(validationErrors.length).toBe(2);
|
|
63
|
-
expect(validationErrors[0].result).toBe(VALIDATION_RESULT.FATAL);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('returns error if template type is not allowed', async () => {
|
|
67
|
-
fs.readFileSync.mockReturnValue('mock');
|
|
68
|
-
mockGetAnnotationValue('starter_landing_pages', 'value');
|
|
69
|
-
|
|
70
|
-
const validationErrors = TemplateValidator.validate(['template.html']);
|
|
71
|
-
|
|
72
|
-
expect(validationErrors.length).toBe(1);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('returns error if template type is unknown', async () => {
|
|
76
|
-
fs.readFileSync.mockReturnValue('mock');
|
|
77
|
-
mockGetAnnotationValue('unknown-type', 'value');
|
|
78
|
-
|
|
79
|
-
const validationErrors = TemplateValidator.validate(['template.html']);
|
|
80
|
-
|
|
81
|
-
expect(validationErrors.length).toBe(1);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('returns error if template type is not found', async () => {
|
|
85
|
-
fs.readFileSync.mockReturnValue('mock');
|
|
86
|
-
mockGetAnnotationValue(null, 'value');
|
|
87
|
-
|
|
88
|
-
const validationErrors = TemplateValidator.validate(['template.html']);
|
|
89
|
-
|
|
90
|
-
expect(validationErrors.length).toBe(1);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('returns no error if template annotation has label and screenshotPath', async () => {
|
|
94
|
-
fs.readFileSync.mockReturnValue('mock');
|
|
95
|
-
mockGetAnnotationValue('page', 'value');
|
|
96
|
-
|
|
97
|
-
const validationErrors = TemplateValidator.validate(['template.html']);
|
|
98
|
-
|
|
99
|
-
expect(validationErrors.length).toBe(0);
|
|
100
|
-
});
|
|
101
|
-
});
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
const ThemeConfigValidator = require('../../validators/marketplaceValidators/theme/ThemeConfigValidator');
|
|
5
|
-
const { VALIDATION_RESULT } = require('../../validators/constants');
|
|
6
|
-
const { THEME_PATH } = require('./validatorTestUtils');
|
|
7
|
-
|
|
8
|
-
jest.mock('fs');
|
|
9
|
-
jest.mock('path');
|
|
10
|
-
|
|
11
|
-
describe('validators/marketplaceValidators/theme/ThemeConfigValidator', () => {
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
ThemeConfigValidator.setAbsolutePath(THEME_PATH);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('returns error if no theme.json file exists', async () => {
|
|
17
|
-
const validationErrors = ThemeConfigValidator.validate(['someFile.html']);
|
|
18
|
-
expect(validationErrors.length).toBe(1);
|
|
19
|
-
expect(validationErrors[0].result).toBe(VALIDATION_RESULT.FATAL);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('returns error if theme.json file has invalid json', async () => {
|
|
23
|
-
fs.readFileSync.mockReturnValue('{} bad json }');
|
|
24
|
-
|
|
25
|
-
const validationErrors = ThemeConfigValidator.validate(['theme.json']);
|
|
26
|
-
expect(validationErrors.length).toBe(1);
|
|
27
|
-
expect(validationErrors[0].result).toBe(VALIDATION_RESULT.FATAL);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('returns error if theme.json file is missing a label field', async () => {
|
|
31
|
-
fs.readFileSync.mockReturnValue('{ "screenshot_path": "./relative/path" }');
|
|
32
|
-
|
|
33
|
-
const validationErrors = ThemeConfigValidator.validate(['theme.json']);
|
|
34
|
-
expect(validationErrors.length).toBe(1);
|
|
35
|
-
expect(validationErrors[0].result).toBe(VALIDATION_RESULT.FATAL);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('returns error if theme.json has screenshot path that is non-relative', async () => {
|
|
39
|
-
fs.readFileSync.mockReturnValue(
|
|
40
|
-
'{ "label": "yay", "screenshot_path": "/absolute/path" }'
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
const validationErrors = ThemeConfigValidator.validate(['theme.json']);
|
|
44
|
-
expect(validationErrors.length).toBe(1);
|
|
45
|
-
expect(validationErrors[0].result).toBe(VALIDATION_RESULT.FATAL);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('returns error if theme.json has screenshot path that does not resolve', async () => {
|
|
49
|
-
fs.readFileSync.mockReturnValue(
|
|
50
|
-
'{ "label": "yay", "screenshot_path": "/absolute/path" }'
|
|
51
|
-
);
|
|
52
|
-
path.relative.mockReturnValue('theme.json');
|
|
53
|
-
fs.existsSync.mockReturnValue(false);
|
|
54
|
-
|
|
55
|
-
const validationErrors = ThemeConfigValidator.validate(['theme.json']);
|
|
56
|
-
expect(validationErrors.length).toBe(1);
|
|
57
|
-
expect(validationErrors[0].result).toBe(VALIDATION_RESULT.FATAL);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('returns no error if theme.json file exists and has all required fields', async () => {
|
|
61
|
-
fs.readFileSync.mockReturnValue(
|
|
62
|
-
'{ "label": "yay", "screenshot_path": "./relative/path" }'
|
|
63
|
-
);
|
|
64
|
-
path.relative.mockReturnValue('theme.json');
|
|
65
|
-
fs.existsSync.mockReturnValue(true);
|
|
66
|
-
|
|
67
|
-
const validationErrors = ThemeConfigValidator.validate(['theme.json']);
|
|
68
|
-
expect(validationErrors.length).toBe(0);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
const fs = require('fs-extra');
|
|
2
|
-
const marketplace = require('@hubspot/cli-lib/api/marketplace');
|
|
3
|
-
|
|
4
|
-
const ThemeDependencyValidator = require('../marketplaceValidators/theme/ThemeDependencyValidator');
|
|
5
|
-
const { VALIDATION_RESULT } = require('../constants');
|
|
6
|
-
const { THEME_PATH } = require('./validatorTestUtils');
|
|
7
|
-
|
|
8
|
-
jest.mock('fs-extra');
|
|
9
|
-
jest.mock('@hubspot/cli-lib/api/marketplace');
|
|
10
|
-
|
|
11
|
-
const getMockDependencyResult = (customPaths = []) => {
|
|
12
|
-
const result = {
|
|
13
|
-
dependencies: ['./relative/file-2.js', ...customPaths],
|
|
14
|
-
};
|
|
15
|
-
return Promise.resolve(result);
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
describe('validators/marketplaceValidators/theme/ThemeDependencyValidator', () => {
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
ThemeDependencyValidator.setAbsolutePath(THEME_PATH);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
describe('isExternalDep', () => {
|
|
24
|
-
beforeEach(() => {
|
|
25
|
-
ThemeDependencyValidator.setAbsolutePath(THEME_PATH);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('returns true if dep is external to the provided absolute path', () => {
|
|
29
|
-
const absoluteFilePath = `${THEME_PATH}/file.js`;
|
|
30
|
-
const relativeDepPath = '../external/dep/path/file2.js';
|
|
31
|
-
const isExternal = ThemeDependencyValidator.isExternalDep(
|
|
32
|
-
absoluteFilePath,
|
|
33
|
-
relativeDepPath
|
|
34
|
-
);
|
|
35
|
-
expect(isExternal).toBe(true);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('returns false if dep is not external to the provided absolute path', () => {
|
|
39
|
-
const absoluteFilePath = `${THEME_PATH}/file.js`;
|
|
40
|
-
const relativeDepPath = './internal/dep/path/file2.js';
|
|
41
|
-
const isExternal = ThemeDependencyValidator.isExternalDep(
|
|
42
|
-
absoluteFilePath,
|
|
43
|
-
relativeDepPath
|
|
44
|
-
);
|
|
45
|
-
expect(isExternal).toBe(false);
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe('validate', () => {
|
|
50
|
-
beforeEach(() => {
|
|
51
|
-
fs.readFile.mockImplementation(() => Promise.resolve('source'));
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('returns error if any referenced path is absolute', async () => {
|
|
55
|
-
marketplace.fetchTemplateDependencies.mockReturnValue(
|
|
56
|
-
getMockDependencyResult(['/absolute/file-3.js'])
|
|
57
|
-
);
|
|
58
|
-
const validationErrors = await ThemeDependencyValidator.validate([
|
|
59
|
-
`${THEME_PATH}/template.html`,
|
|
60
|
-
]);
|
|
61
|
-
|
|
62
|
-
expect(validationErrors.length).toBe(1);
|
|
63
|
-
expect(validationErrors[0].result).toBe(VALIDATION_RESULT.FATAL);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('returns error if any referenced path is external to the theme', async () => {
|
|
67
|
-
marketplace.fetchTemplateDependencies.mockReturnValue(
|
|
68
|
-
getMockDependencyResult(['../../external/file-3.js'])
|
|
69
|
-
);
|
|
70
|
-
const validationErrors = await ThemeDependencyValidator.validate([
|
|
71
|
-
`${THEME_PATH}/template.html`,
|
|
72
|
-
]);
|
|
73
|
-
|
|
74
|
-
expect(validationErrors.length).toBe(1);
|
|
75
|
-
expect(validationErrors[0].result).toBe(VALIDATION_RESULT.FATAL);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('returns no errors if paths are relative and internal', async () => {
|
|
79
|
-
marketplace.fetchTemplateDependencies.mockReturnValue(
|
|
80
|
-
getMockDependencyResult()
|
|
81
|
-
);
|
|
82
|
-
const validationErrors = await ThemeDependencyValidator.validate([
|
|
83
|
-
`${THEME_PATH}/template.html`,
|
|
84
|
-
]);
|
|
85
|
-
|
|
86
|
-
expect(validationErrors.length).toBe(0);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
});
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const ThemeModuleValidator = require('../marketplaceValidators/theme/ThemeModuleValidator');
|
|
3
|
-
const { VALIDATION_RESULT } = require('../constants');
|
|
4
|
-
const {
|
|
5
|
-
generateModulesList,
|
|
6
|
-
makeFindError,
|
|
7
|
-
THEME_PATH,
|
|
8
|
-
} = require('./validatorTestUtils');
|
|
9
|
-
|
|
10
|
-
jest.mock('fs');
|
|
11
|
-
|
|
12
|
-
const MODULE_LIMIT = 50;
|
|
13
|
-
|
|
14
|
-
const findError = makeFindError('themeModule');
|
|
15
|
-
|
|
16
|
-
describe('validators/marketplaceValidators/theme/ThemeModuleValidator', () => {
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
ThemeModuleValidator.setAbsolutePath(THEME_PATH);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('returns error if module limit is exceeded', async () => {
|
|
22
|
-
const validationErrors = ThemeModuleValidator.validate(
|
|
23
|
-
generateModulesList(MODULE_LIMIT + 1)
|
|
24
|
-
);
|
|
25
|
-
const limitError = findError(validationErrors, 'limitExceeded');
|
|
26
|
-
expect(limitError).toBeDefined();
|
|
27
|
-
expect(limitError.result).toBe(VALIDATION_RESULT.FATAL);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('returns no limit error if module limit is not exceeded', async () => {
|
|
31
|
-
const validationErrors = ThemeModuleValidator.validate(
|
|
32
|
-
generateModulesList(MODULE_LIMIT)
|
|
33
|
-
);
|
|
34
|
-
const limitError = findError(validationErrors, 'limitExceeded');
|
|
35
|
-
expect(limitError).not.toBeDefined();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('returns error if no module meta.json file exists', async () => {
|
|
39
|
-
const validationErrors = ThemeModuleValidator.validate([
|
|
40
|
-
'module.module/module.html',
|
|
41
|
-
]);
|
|
42
|
-
expect(validationErrors.length).toBe(1);
|
|
43
|
-
expect(validationErrors[0].result).toBe(VALIDATION_RESULT.FATAL);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('returns error if module meta.json file has invalid json', async () => {
|
|
47
|
-
fs.readFileSync.mockReturnValue('{} bad json }');
|
|
48
|
-
|
|
49
|
-
const validationErrors = ThemeModuleValidator.validate([
|
|
50
|
-
'module.module/meta.json',
|
|
51
|
-
]);
|
|
52
|
-
expect(validationErrors.length).toBe(1);
|
|
53
|
-
expect(validationErrors[0].result).toBe(VALIDATION_RESULT.FATAL);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('returns error if module meta.json file is missing a label field', async () => {
|
|
57
|
-
fs.readFileSync.mockReturnValue('{ "icon": "woo" }');
|
|
58
|
-
|
|
59
|
-
const validationErrors = ThemeModuleValidator.validate([
|
|
60
|
-
'module.module/meta.json',
|
|
61
|
-
]);
|
|
62
|
-
expect(validationErrors.length).toBe(1);
|
|
63
|
-
expect(validationErrors[0].result).toBe(VALIDATION_RESULT.FATAL);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('returns error if module meta.json file is missing an icon field', async () => {
|
|
67
|
-
fs.readFileSync.mockReturnValue('{ "label": "yay" }');
|
|
68
|
-
|
|
69
|
-
const validationErrors = ThemeModuleValidator.validate([
|
|
70
|
-
'module.module/meta.json',
|
|
71
|
-
]);
|
|
72
|
-
expect(validationErrors.length).toBe(1);
|
|
73
|
-
expect(validationErrors[0].result).toBe(VALIDATION_RESULT.FATAL);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('returns no error if module meta.json file exists and has all required fields', async () => {
|
|
77
|
-
fs.readFileSync.mockReturnValue('{ "label": "yay", "icon": "woo" }');
|
|
78
|
-
|
|
79
|
-
const validationErrors = ThemeModuleValidator.validate([
|
|
80
|
-
'module.module/meta.json',
|
|
81
|
-
]);
|
|
82
|
-
expect(validationErrors.length).toBe(0);
|
|
83
|
-
});
|
|
84
|
-
});
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
|
|
3
|
-
const { VALIDATION_RESULT } = require('../constants');
|
|
4
|
-
|
|
5
|
-
class AbsoluteValidator {
|
|
6
|
-
constructor({ name, key }) {
|
|
7
|
-
this.name = name;
|
|
8
|
-
this.key = key;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
clearAbsolutePath() {
|
|
12
|
-
this._absolutePath = null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
setAbsolutePath(path) {
|
|
16
|
-
this._absolutePath = path;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
getRelativePath(filePath) {
|
|
20
|
-
return this._absolutePath
|
|
21
|
-
? path.relative(this._absolutePath, filePath)
|
|
22
|
-
: filePath;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
getSuccess() {
|
|
26
|
-
return {
|
|
27
|
-
validatorKey: this.key,
|
|
28
|
-
validatorName: this.name,
|
|
29
|
-
result: VALIDATION_RESULT.SUCCESS,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
getError(errorObj, file, extraContext = {}) {
|
|
34
|
-
const relativeFilePath = file ? this.getRelativePath(file) : null;
|
|
35
|
-
const context = {
|
|
36
|
-
filePath: relativeFilePath,
|
|
37
|
-
...extraContext,
|
|
38
|
-
};
|
|
39
|
-
return {
|
|
40
|
-
validatorKey: this.key,
|
|
41
|
-
validatorName: this.name,
|
|
42
|
-
error: errorObj.getCopy(context),
|
|
43
|
-
result: errorObj.severity || VALIDATION_RESULT.FATAL,
|
|
44
|
-
key: `${this.key}.${errorObj.key}`,
|
|
45
|
-
context,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
module.exports = AbsoluteValidator;
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
const {
|
|
2
|
-
ANNOTATION_KEYS,
|
|
3
|
-
buildAnnotationValueGetter,
|
|
4
|
-
} = require('@hubspot/cli-lib/templates');
|
|
5
|
-
const AbsoluteValidator = require('../AbsoluteValidator');
|
|
6
|
-
const { VALIDATOR_KEYS } = require('../../constants');
|
|
7
|
-
|
|
8
|
-
const SECTION_LIMIT = 50;
|
|
9
|
-
|
|
10
|
-
class SectionValidator extends AbsoluteValidator {
|
|
11
|
-
constructor(options) {
|
|
12
|
-
super(options);
|
|
13
|
-
|
|
14
|
-
this.errors = {
|
|
15
|
-
LIMIT_EXCEEDED: {
|
|
16
|
-
key: 'limitExceeded',
|
|
17
|
-
getCopy: ({ limit, total }) =>
|
|
18
|
-
`Section limit exceeded. Themes can only have ${limit} sections, but this theme has ${total}`,
|
|
19
|
-
},
|
|
20
|
-
MISSING_LABEL: {
|
|
21
|
-
key: 'missingLabel',
|
|
22
|
-
getCopy: ({ filePath }) =>
|
|
23
|
-
`Missing required property for ${filePath}. The section is missing the "label" property`,
|
|
24
|
-
},
|
|
25
|
-
MISSING_SCREENSHOT_PATH: {
|
|
26
|
-
key: 'missingScreenshotPath',
|
|
27
|
-
getCopy: ({ filePath }) =>
|
|
28
|
-
`Missing required property for ${filePath}. The section is missing the "screenshotPath" property`,
|
|
29
|
-
},
|
|
30
|
-
MISSING_DESCRIPTION: {
|
|
31
|
-
key: 'missingDescription',
|
|
32
|
-
getCopy: ({ filePath }) =>
|
|
33
|
-
`Missing required property for ${filePath}. The section is missing the "description" property`,
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Validates:
|
|
39
|
-
// - All sections have a "description" annotation
|
|
40
|
-
// - All sections have a "label" annotation
|
|
41
|
-
// - All sections have a "screenshotPath" annotation
|
|
42
|
-
// - Theme does not have more than SECTION_LIMIT sections
|
|
43
|
-
|
|
44
|
-
validate(files) {
|
|
45
|
-
let validationErrors = [];
|
|
46
|
-
let sectionCount = 0;
|
|
47
|
-
|
|
48
|
-
files.forEach(file => {
|
|
49
|
-
if (file) {
|
|
50
|
-
const getAnnotationValue = buildAnnotationValueGetter(file);
|
|
51
|
-
const templateType = getAnnotationValue(ANNOTATION_KEYS.templateType);
|
|
52
|
-
|
|
53
|
-
if (templateType !== 'section') {
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
sectionCount++;
|
|
57
|
-
|
|
58
|
-
const description = getAnnotationValue(ANNOTATION_KEYS.description);
|
|
59
|
-
const label = getAnnotationValue(ANNOTATION_KEYS.label);
|
|
60
|
-
const screenshotPath = getAnnotationValue(
|
|
61
|
-
ANNOTATION_KEYS.screenshotPath
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
if (!description) {
|
|
65
|
-
validationErrors.push(
|
|
66
|
-
this.getError(this.errors.MISSING_DESCRIPTION, file)
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
if (!label) {
|
|
70
|
-
validationErrors.push(this.getError(this.errors.MISSING_LABEL, file));
|
|
71
|
-
}
|
|
72
|
-
if (!screenshotPath) {
|
|
73
|
-
validationErrors.push(
|
|
74
|
-
this.getError(this.errors.MISSING_SCREENSHOT_PATH, file)
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
if (sectionCount > SECTION_LIMIT) {
|
|
81
|
-
validationErrors.push(
|
|
82
|
-
this.getError(this.errors.LIMIT_EXCEEDED, null, {
|
|
83
|
-
limit: SECTION_LIMIT,
|
|
84
|
-
total: sectionCount,
|
|
85
|
-
})
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return validationErrors;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
module.exports = new SectionValidator({
|
|
94
|
-
name: 'Section',
|
|
95
|
-
key: VALIDATOR_KEYS.section,
|
|
96
|
-
});
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
const {
|
|
2
|
-
ANNOTATION_KEYS,
|
|
3
|
-
buildAnnotationValueGetter,
|
|
4
|
-
isCodedFile,
|
|
5
|
-
} = require('@hubspot/cli-lib/templates');
|
|
6
|
-
const AbsoluteValidator = require('../AbsoluteValidator');
|
|
7
|
-
const { VALIDATOR_KEYS } = require('../../constants');
|
|
8
|
-
|
|
9
|
-
const TEMPLATE_LIMIT = 50;
|
|
10
|
-
const TEMPLATE_IGNORE_LIST = ['section'];
|
|
11
|
-
const TEMPLATE_COUNT_IGNORE_LIST = ['global_partial', 'section', 'none'];
|
|
12
|
-
const VALIDATIONS_BY_TYPE = {
|
|
13
|
-
page: { allowed: true, label: true, screenshot: true },
|
|
14
|
-
starter_landing_pages: { allowed: false },
|
|
15
|
-
email: { allowed: false },
|
|
16
|
-
blog: { allowed: false },
|
|
17
|
-
none: { allowed: true, label: false, screenshot: false },
|
|
18
|
-
error_page: { allowed: true, label: true, screenshot: false },
|
|
19
|
-
password_prompt_page: { allowed: true, label: true, screenshot: false },
|
|
20
|
-
email_subscription_preferences_page: {
|
|
21
|
-
allowed: true,
|
|
22
|
-
label: true,
|
|
23
|
-
screenshot: false,
|
|
24
|
-
},
|
|
25
|
-
email_backup_unsubscribe_page: {
|
|
26
|
-
allowed: true,
|
|
27
|
-
label: true,
|
|
28
|
-
screenshot: false,
|
|
29
|
-
},
|
|
30
|
-
email_subscriptions_confirmation_page: {
|
|
31
|
-
allowed: true,
|
|
32
|
-
label: true,
|
|
33
|
-
screenshot: false,
|
|
34
|
-
},
|
|
35
|
-
search_results_page: { allowed: true, label: true, screenshot: false },
|
|
36
|
-
membership_login_page: { allowed: true, label: true, screenshot: false },
|
|
37
|
-
membership_register_page: { allowed: true, label: true, screenshot: false },
|
|
38
|
-
membership_reset_page: { allowed: true, label: true, screenshot: false },
|
|
39
|
-
membership_reset_request_page: {
|
|
40
|
-
allowed: true,
|
|
41
|
-
label: true,
|
|
42
|
-
screenshot: false,
|
|
43
|
-
},
|
|
44
|
-
membership_email_page: { allowed: true, label: true, screenshot: false },
|
|
45
|
-
global_partial: { allowed: true, label: true, screenshot: false },
|
|
46
|
-
knowledge_article: { allowed: false },
|
|
47
|
-
drag_drop_email: { allowed: false },
|
|
48
|
-
proposal: { allowed: false },
|
|
49
|
-
blog_listing: { allowed: true, label: true, screenshot: true },
|
|
50
|
-
blog_post: { allowed: true, label: true, screenshot: true },
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
class TemplateValidator extends AbsoluteValidator {
|
|
54
|
-
constructor(options) {
|
|
55
|
-
super(options);
|
|
56
|
-
|
|
57
|
-
this.errors = {
|
|
58
|
-
LIMIT_EXCEEDED: {
|
|
59
|
-
key: 'limitExceeded',
|
|
60
|
-
getCopy: ({ limit, total }) =>
|
|
61
|
-
`Template limit exceeded. Themes can only have ${limit} templates, but this theme has ${total}`,
|
|
62
|
-
},
|
|
63
|
-
MISSING_TEMPLATE_TYPE: {
|
|
64
|
-
key: 'missingTemplateType',
|
|
65
|
-
getCopy: ({ filePath }) =>
|
|
66
|
-
`Missing required property for ${filePath}. The template is missing the "templateType" property`,
|
|
67
|
-
},
|
|
68
|
-
UNKNOWN_TEMPLATE_TYPE: {
|
|
69
|
-
key: 'unknownTemplateType',
|
|
70
|
-
getCopy: ({ filePath, templateType }) =>
|
|
71
|
-
`Template ${filePath} has an unknown template type of ${templateType}`,
|
|
72
|
-
},
|
|
73
|
-
RESTRICTED_TEMPLATE_TYPE: {
|
|
74
|
-
key: 'restrictedTemplateType',
|
|
75
|
-
getCopy: ({ filePath, templateType }) =>
|
|
76
|
-
`Template ${filePath} has a restricted template type of ${templateType}`,
|
|
77
|
-
},
|
|
78
|
-
MISSING_LABEL: {
|
|
79
|
-
key: 'missingLabel',
|
|
80
|
-
getCopy: ({ filePath }) =>
|
|
81
|
-
`Missing required property for ${filePath}. The template is missing the "label" property`,
|
|
82
|
-
},
|
|
83
|
-
MISSING_SCREENSHOT_PATH: {
|
|
84
|
-
key: 'missingScreenshotPath',
|
|
85
|
-
getCopy: ({ filePath }) =>
|
|
86
|
-
`Missing required property for ${filePath}. The template is missing the "screenshotPath" property`,
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Validates:
|
|
92
|
-
// - Theme does not contain more than TEMPLATE_LIMIT templates
|
|
93
|
-
// - All templates have valid template types
|
|
94
|
-
// - All templates that require a label have a "label" annotation
|
|
95
|
-
// - All templates that require a screenshot have a "screenshotPath" annotation
|
|
96
|
-
validate(files) {
|
|
97
|
-
let validationErrors = [];
|
|
98
|
-
let templateCount = 0;
|
|
99
|
-
|
|
100
|
-
files.forEach(file => {
|
|
101
|
-
if (isCodedFile(file)) {
|
|
102
|
-
const getAnnotationValue = buildAnnotationValueGetter(file);
|
|
103
|
-
const isAvailableForNewContent = getAnnotationValue(
|
|
104
|
-
ANNOTATION_KEYS.isAvailableForNewContent
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
if (isAvailableForNewContent !== 'false') {
|
|
108
|
-
const templateType = getAnnotationValue(ANNOTATION_KEYS.templateType);
|
|
109
|
-
|
|
110
|
-
if (TEMPLATE_IGNORE_LIST.includes(templateType)) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (templateType) {
|
|
115
|
-
const label = getAnnotationValue(ANNOTATION_KEYS.label);
|
|
116
|
-
const screenshotPath = getAnnotationValue(
|
|
117
|
-
ANNOTATION_KEYS.screenshotPath
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
// Ignore global partials, sections, and templates with type of none in count
|
|
121
|
-
if (!TEMPLATE_COUNT_IGNORE_LIST.includes(templateType)) {
|
|
122
|
-
templateCount++;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const validations = VALIDATIONS_BY_TYPE[templateType];
|
|
126
|
-
|
|
127
|
-
if (validations) {
|
|
128
|
-
if (!validations.allowed) {
|
|
129
|
-
validationErrors.push(
|
|
130
|
-
this.getError(this.errors.RESTRICTED_TEMPLATE_TYPE, file, {
|
|
131
|
-
templateType,
|
|
132
|
-
})
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
if (validations.label && !label) {
|
|
136
|
-
validationErrors.push(
|
|
137
|
-
this.getError(this.errors.MISSING_LABEL, file)
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
if (validations.screenshot && !screenshotPath) {
|
|
141
|
-
validationErrors.push(
|
|
142
|
-
this.getError(this.errors.MISSING_SCREENSHOT_PATH, file)
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
} else {
|
|
146
|
-
validationErrors.push(
|
|
147
|
-
this.getError(this.errors.UNKNOWN_TEMPLATE_TYPE, file)
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
} else {
|
|
151
|
-
validationErrors.push(
|
|
152
|
-
this.getError(this.errors.MISSING_TEMPLATE_TYPE, file)
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
if (templateCount > TEMPLATE_LIMIT) {
|
|
160
|
-
validationErrors.push(
|
|
161
|
-
this.getError(this.errors.LIMIT_EXCEEDED, null, {
|
|
162
|
-
limit: TEMPLATE_LIMIT,
|
|
163
|
-
total: templateCount,
|
|
164
|
-
})
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return validationErrors;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
module.exports = new TemplateValidator({
|
|
173
|
-
name: 'Template',
|
|
174
|
-
key: VALIDATOR_KEYS.template,
|
|
175
|
-
});
|